From fe422a45af0fb85b682bd4c108f50e7f248d9161 Mon Sep 17 00:00:00 2001 From: Jinank Jain Date: Fri, 20 Jun 2025 18:10:52 +0530 Subject: [PATCH 0001/1893] build: Move away from actions-rs/cross Since action-rs/cross is deprecrated, thus move to houseabsolute/actions-rust-cross. We should pin the cross-version to the latest version to fix the build issues with virtio-bindings crate. Signed-off-by: Jinank Jain --- .github/workflows/quality.yaml | 91 +++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 19a4981a4d..0ece7fa690 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -50,88 +50,97 @@ jobs: git checkout ${{ github.sha }} - name: Clippy (kvm) - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: - use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }} command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (mshv) - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: - use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }} command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (mshv + kvm) - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: - use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }} command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (default features) - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: - use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }} command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (default features + guest_debug) - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: - use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }} command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (default features + pvmemcontrol) - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: - use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }} command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (default features + tracing) - 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 "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - - - name: Clippy (mshv) - if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - - - name: Clippy (mshv + kvm) - if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (sev_snp) if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (igvm) if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (kvm + tdx) if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - uses: actions-rs/cargo@v1 + uses: houseabsolute/actions-rust-cross@v1 with: command: clippy - args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Check build did not modify any files run: test -z "$(git status --porcelain)" From 190d90196fff389b60b93b57acf958957b71b249 Mon Sep 17 00:00:00 2001 From: Jinank Jain Date: Tue, 10 Jun 2025 15:14:00 +0530 Subject: [PATCH 0002/1893] build: Bump vfio and all the dependent crates to latest version Recently vfio crates have moved to crates.io, thus we should start consuming the crate from crates.io instead git url. This results in better versioning instead of tracking some git commit sha. Signed-off-by: Jinank Jain --- Cargo.lock | 69 +++++----- Cargo.toml | 20 +-- block/Cargo.toml | 2 +- block/src/lib.rs | 2 +- fuzz/Cargo.lock | 130 +++++++++--------- fuzz/Cargo.toml | 4 +- hypervisor/src/kvm/mod.rs | 36 ++--- virtio-devices/Cargo.toml | 2 +- virtio-devices/src/vdpa.rs | 5 +- .../src/vhost_user/vu_common_ctrl.rs | 5 +- vm-virtio/src/queue.rs | 3 +- 11 files changed, 135 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afa7538716..afd186fa63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1042,20 +1042,20 @@ dependencies = [ [[package]] name = "kvm-bindings" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4933174d0cc4b77b958578cd45784071cc5ae212c2d78fbd755aaaa6dfa71a" +checksum = "d4b153a59bb3ca930ff8148655b2ef68c34259a623ae08cf2fb9b570b2e45363" dependencies = [ "serde", "vmm-sys-util", - "zerocopy 0.7.35", + "zerocopy 0.8.26", ] [[package]] name = "kvm-ioctls" -version = "0.19.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e013ae7fcd2c6a8f384104d16afe7ea02969301ea2bb2a56e44b011ebc907cab" +checksum = "b702df98508cb63ad89dd9beb9f6409761b30edca10d48e57941d3f11513a006" dependencies = [ "bitflags 2.9.0", "kvm-bindings", @@ -1183,7 +1183,7 @@ dependencies = [ [[package]] name = "micro_http" version = "0.1.0" -source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#4f621532e81ee2ad096a9c9592fdacc40d19de48" +source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#bf5098916006912f8dd35aaa6daa5579c6c297b2" dependencies = [ "libc", "vmm-sys-util", @@ -2188,21 +2188,9 @@ dependencies = [ "getrandom 0.3.3", "js-sys", "rand", - "uuid-macro-internal", "wasm-bindgen", ] -[[package]] -name = "uuid-macro-internal" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b682e8c381995ea03130e381928e0e005b7c9eb483c6c8682f50e07b33c2b7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -2211,16 +2199,18 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vfio-bindings" -version = "0.4.0" -source = "git+https://github.com/rust-vmm/vfio?branch=main#3d158a14460cac7ca3c99c2effa0a46880935cb0" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b565663f62e091ca47db9a674c8c95c9686a000e82970f391a3cacf6470ff060" dependencies = [ "vmm-sys-util", ] [[package]] name = "vfio-ioctls" -version = "0.4.0" -source = "git+https://github.com/rust-vmm/vfio?branch=main#3d158a14460cac7ca3c99c2effa0a46880935cb0" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61316b5e308faa8ed4a87c4130256f765e46de3442eb2e2e619840ef73456738" dependencies = [ "byteorder", "kvm-bindings", @@ -2229,7 +2219,7 @@ dependencies = [ "log", "mshv-bindings", "mshv-ioctls", - "thiserror 1.0.62", + "thiserror 2.0.12", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2238,15 +2228,16 @@ dependencies = [ [[package]] name = "vfio_user" version = "0.1.0" -source = "git+https://github.com/rust-vmm/vfio-user?branch=main#3febcdd3fa2531623865663ca1721e1962ed9979" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed81c5ed8224d468a322e923777ed0615cad433fe61177126098af995f89cecf" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.0", "libc", "log", "serde", "serde_derive", "serde_json", - "thiserror 1.0.62", + "thiserror 2.0.12", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2254,8 +2245,9 @@ dependencies = [ [[package]] name = "vhost" -version = "0.12.1" -source = "git+https://github.com/rust-vmm/vhost?rev=d983ae0#d983ae07f78663b7d24059667376992460b571a2" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ "bitflags 2.9.0", "libc", @@ -2266,8 +2258,9 @@ dependencies = [ [[package]] name = "vhost-user-backend" -version = "0.16.1" -source = "git+https://github.com/rust-vmm/vhost?rev=d983ae0#d983ae07f78663b7d24059667376992460b571a2" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e183205a9ba7cb9c47fcb0fc0a07fc295a110efbb11ab78ad0d793b0a38a7bde" dependencies = [ "libc", "log", @@ -2319,9 +2312,9 @@ dependencies = [ [[package]] name = "virtio-bindings" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1711e61c00f8cb450bd15368152a1e37a12ef195008ddc7d0f4812f9e2b30a68" +checksum = "804f498a26d5a63be7bbb8bdcd3869c3f286c4c4a17108905276454da0caf8cb" [[package]] name = "virtio-devices" @@ -2359,9 +2352,9 @@ dependencies = [ [[package]] name = "virtio-queue" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872e2f3fbd70a7e6f01689720cce3d5c2c5efe52b484dd07b674246ada0e9a8d" +checksum = "fb0479158f863e59323771a1f684d843962f76960b86fecfec2bfa9c8f0f9180" dependencies = [ "log", "virtio-bindings", @@ -2398,9 +2391,9 @@ source = "git+https://github.com/rust-vmm/vm-fdt?branch=main#ef5bd734f5f66fb0772 [[package]] name = "vm-memory" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1720e7240cdc739f935456eb77f370d7e9b2a3909204da1e2b47bef1137a013" +checksum = "1fd5e56d48353c5f54ef50bd158a0452fc82f5383da840f7b8efc31695dd3b9d" dependencies = [ "arc-swap", "libc", @@ -2489,9 +2482,9 @@ dependencies = [ [[package]] name = "vmm-sys-util" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1435039746e20da4f8d507a72ee1b916f7b4b05af7a91c093d2c6561934ede" +checksum = "d21f366bf22bfba3e868349978766a965cbe628c323d58e026be80b8357ab789" dependencies = [ "bitflags 1.3.2", "libc", diff --git a/Cargo.toml b/Cargo.toml index fefbd227e4..0cc8e53a9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,22 +103,22 @@ members = [ [workspace.dependencies] # rust-vmm crates acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" } -kvm-bindings = "0.10.0" -kvm-ioctls = "0.19.1" +kvm-bindings = "0.12.0" +kvm-ioctls = "0.22.0" linux-loader = "0.13.0" mshv-bindings = "0.5.1" mshv-ioctls = "0.5.1" seccompiler = "0.5.0" -vfio-bindings = { git = "https://github.com/rust-vmm/vfio", branch = "main" } -vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false } -vfio_user = { git = "https://github.com/rust-vmm/vfio-user", branch = "main" } -vhost = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" } -vhost-user-backend = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" } -virtio-bindings = "0.2.4" -virtio-queue = "0.14.0" +vfio-bindings = { version = "0.5.0", default-features = false } +vfio-ioctls = { version = "0.5.0", default-features = false } +vfio_user = { version = "0.1.0", default-features = false } +vhost = { version = "0.14.0", default-features = false } +vhost-user-backend = { version = "0.20.0", default-features = false } +virtio-bindings = "0.2.6" +virtio-queue = "0.16.0" vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" } vm-memory = "0.16.1" -vmm-sys-util = "0.12.1" +vmm-sys-util = "0.14.0" # igvm crates # TODO: bump to 0.3.5 release diff --git a/block/Cargo.toml b/block/Cargo.toml index 02bf37eb03..48551251a5 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -19,7 +19,7 @@ serde = { version = "1.0.208", features = ["derive"] } smallvec = "1.13.2" thiserror = { workspace = true } uuid = { workspace = true, features = ["v4"] } -virtio-bindings = { workspace = true, features = ["virtio-v5_0_0"] } +virtio-bindings = { workspace = true } virtio-queue = { workspace = true } vm-memory = { workspace = true, features = [ "backend-atomic", diff --git a/block/src/lib.rs b/block/src/lib.rs index 1424848ba3..b081c87734 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -57,7 +57,7 @@ use vm_memory::{ }; use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; -use vmm_sys_util::{aio, ioctl_io_nr, ioctl_ioc_nr}; +use vmm_sys_util::{aio, ioctl_io_nr}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; use crate::vhdx::VhdxError; diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 967c057442..85596958ce 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -7,7 +7,7 @@ name = "acpi_tables" version = "0.1.0" source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#e08a3f0b0a59b98859dbf59f5aa7fd4d2eb4018a" dependencies = [ - "zerocopy 0.8.24", + "zerocopy 0.8.26", ] [[package]] @@ -480,7 +480,7 @@ dependencies = [ "vfio-ioctls", "vm-memory", "vmm-sys-util", - "zerocopy 0.8.24", + "zerocopy 0.8.26", ] [[package]] @@ -531,29 +531,30 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "kvm-bindings" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4933174d0cc4b77b958578cd45784071cc5ae212c2d78fbd755aaaa6dfa71a" +checksum = "d4b153a59bb3ca930ff8148655b2ef68c34259a623ae08cf2fb9b570b2e45363" dependencies = [ "serde", "vmm-sys-util", - "zerocopy 0.7.35", + "zerocopy 0.8.26", ] [[package]] name = "kvm-ioctls" -version = "0.19.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e013ae7fcd2c6a8f384104d16afe7ea02969301ea2bb2a56e44b011ebc907cab" +checksum = "b702df98508cb63ad89dd9beb9f6409761b30edca10d48e57941d3f11513a006" dependencies = [ "bitflags 2.9.0", "kvm-bindings", @@ -635,7 +636,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "micro_http" version = "0.1.0" -source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#4f621532e81ee2ad096a9c9592fdacc40d19de48" +source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#bf5098916006912f8dd35aaa6daa5579c6c297b2" dependencies = [ "libc", "vmm-sys-util", @@ -652,7 +653,7 @@ dependencies = [ "serde", "serde_derive", "vmm-sys-util", - "zerocopy 0.8.24", + "zerocopy 0.8.26", ] [[package]] @@ -830,7 +831,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha", "rand_core", - "zerocopy 0.8.24", + "zerocopy 0.8.26", ] [[package]] @@ -865,15 +866,21 @@ dependencies = [ [[package]] name = "remain" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46aef80f842736de545ada6ec65b81ee91504efd6853f4b96de7414c42ae7443" +checksum = "d7ef12e84481ab4006cb942f8682bba28ece7270743e649442027c5db87df126" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "ryu" version = "1.0.18" @@ -1105,45 +1112,37 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.15.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.3", + "js-sys", "rand", - "uuid-macro-internal", -] - -[[package]] -name = "uuid-macro-internal" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9521621447c21497fac206ffe6e9f642f977c4f82eeba9201055f64884d9cb01" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "wasm-bindgen", ] [[package]] name = "vfio-bindings" -version = "0.4.0" -source = "git+https://github.com/rust-vmm/vfio?branch=main#b135b8305c2cc8ec333e0cf77a780445cc98dcee" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b565663f62e091ca47db9a674c8c95c9686a000e82970f391a3cacf6470ff060" dependencies = [ "vmm-sys-util", ] [[package]] name = "vfio-ioctls" -version = "0.2.0" -source = "git+https://github.com/rust-vmm/vfio?branch=main#b135b8305c2cc8ec333e0cf77a780445cc98dcee" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61316b5e308faa8ed4a87c4130256f765e46de3442eb2e2e619840ef73456738" dependencies = [ "byteorder", "kvm-bindings", "kvm-ioctls", "libc", "log", - "thiserror 1.0.64", + "thiserror 2.0.12", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1152,15 +1151,16 @@ dependencies = [ [[package]] name = "vfio_user" version = "0.1.0" -source = "git+https://github.com/rust-vmm/vfio-user?branch=main#3febcdd3fa2531623865663ca1721e1962ed9979" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed81c5ed8224d468a322e923777ed0615cad433fe61177126098af995f89cecf" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.0", "libc", "log", "serde", "serde_derive", "serde_json", - "thiserror 1.0.64", + "thiserror 2.0.12", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1168,8 +1168,9 @@ dependencies = [ [[package]] name = "vhost" -version = "0.12.1" -source = "git+https://github.com/rust-vmm/vhost?rev=d983ae0#d983ae07f78663b7d24059667376992460b571a2" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ "bitflags 2.9.0", "libc", @@ -1180,9 +1181,9 @@ dependencies = [ [[package]] name = "virtio-bindings" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1711e61c00f8cb450bd15368152a1e37a12ef195008ddc7d0f4812f9e2b30a68" +checksum = "804f498a26d5a63be7bbb8bdcd3869c3f286c4c4a17108905276454da0caf8cb" [[package]] name = "virtio-devices" @@ -1219,9 +1220,9 @@ dependencies = [ [[package]] name = "virtio-queue" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872e2f3fbd70a7e6f01689720cce3d5c2c5efe52b484dd07b674246ada0e9a8d" +checksum = "fb0479158f863e59323771a1f684d843962f76960b86fecfec2bfa9c8f0f9180" dependencies = [ "log", "virtio-bindings", @@ -1258,9 +1259,9 @@ source = "git+https://github.com/rust-vmm/vm-fdt?branch=main#ef5bd734f5f66fb0772 [[package]] name = "vm-memory" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1720e7240cdc739f935456eb77f370d7e9b2a3909204da1e2b47bef1137a013" +checksum = "1fd5e56d48353c5f54ef50bd158a0452fc82f5383da840f7b8efc31695dd3b9d" dependencies = [ "arc-swap", "libc", @@ -1335,14 +1336,14 @@ dependencies = [ "vm-migration", "vm-virtio", "vmm-sys-util", - "zerocopy 0.8.24", + "zerocopy 0.8.26", ] [[package]] name = "vmm-sys-util" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1435039746e20da4f8d507a72ee1b916f7b4b05af7a91c093d2c6561934ede" +checksum = "d21f366bf22bfba3e868349978766a965cbe628c323d58e026be80b8357ab789" dependencies = [ "bitflags 1.3.2", "libc", @@ -1367,24 +1368,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -1393,9 +1394,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1403,9 +1404,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -1416,9 +1417,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "winapi" @@ -1545,11 +1549,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "zerocopy-derive 0.8.24", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1565,9 +1569,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c4536bcfed..27bfc87edb 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -28,13 +28,13 @@ mshv-bindings = "0.5.0" net_util = { path = "../net_util" } seccompiler = "0.5.0" virtio-devices = { path = "../virtio-devices" } -virtio-queue = "0.14.0" +virtio-queue = "0.16.0" vm-device = { path = "../vm-device" } vm-memory = "0.16.0" vm-migration = { path = "../vm-migration" } vm-virtio = { path = "../vm-virtio" } vmm = { path = "../vmm", features = ["guest_debug"] } -vmm-sys-util = "0.12.1" +vmm-sys-util = "0.14.0" # Prevent this from interfering with workspaces [workspace] diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 591a09586f..8bb49e52ca 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -44,7 +44,7 @@ pub use crate::riscv64::{ use crate::vm::{self, InterruptSourceConfig, VmOps}; #[cfg(target_arch = "aarch64")] use crate::{arm64_core_reg_id, offset_of}; -use crate::{cpu, hypervisor, vec_with_array_field, HypervisorType}; +use crate::{cpu, hypervisor, HypervisorType}; #[cfg(target_arch = "riscv64")] use crate::{offset_of, riscv64_reg_id}; // x86_64 dependencies @@ -106,7 +106,7 @@ pub use kvm_ioctls::{Cap, Kvm, VcpuExit}; use thiserror::Error; use vfio_ioctls::VfioDeviceFd; #[cfg(feature = "tdx")] -use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_ioc_nr, ioctl_iowr_nr}; +use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_iowr_nr}; pub use {kvm_bindings, kvm_ioctls}; #[cfg(target_arch = "aarch64")] @@ -119,8 +119,6 @@ const KVM_CAP_SGX_ATTRIBUTE: u32 = 196; #[cfg(target_arch = "x86_64")] use vmm_sys_util::ioctl_io_nr; -#[cfg(all(not(feature = "tdx"), target_arch = "x86_64"))] -use vmm_sys_util::ioctl_ioc_nr; #[cfg(target_arch = "x86_64")] ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a); @@ -693,10 +691,6 @@ impl vm::Vm for KvmVm { /// entries, as per the `KVM_SET_GSI_ROUTING` ioctl. /// fn set_gsi_routing(&self, entries: &[IrqRoutingEntry]) -> vm::Result<()> { - let mut irq_routing = - vec_with_array_field::(entries.len()); - irq_routing[0].nr = entries.len() as u32; - irq_routing[0].flags = 0; let entries: Vec = entries .iter() .map(|entry| match entry { @@ -706,17 +700,11 @@ impl vm::Vm for KvmVm { }) .collect(); - // SAFETY: irq_routing initialized with entries.len() and now it is being turned into - // entries_slice with entries.len() again. It is guaranteed to be large enough to hold - // everything from entries. - unsafe { - let entries_slice: &mut [kvm_irq_routing_entry] = - irq_routing[0].entries.as_mut_slice(entries.len()); - entries_slice.copy_from_slice(&entries); - } + let irq_routing = + kvm_bindings::fam_wrappers::KvmIrqRouting::from_entries(&entries).unwrap(); self.fd - .set_gsi_routing(&irq_routing[0]) + .set_gsi_routing(&irq_routing) .map_err(|e| vm::HypervisorVmError::SetGsiRouting(e.into())) } @@ -2956,11 +2944,15 @@ impl KvmVcpu { /// fn set_xsave(&self, xsave: &XsaveState) -> cpu::Result<()> { let xsave: kvm_bindings::kvm_xsave = (*xsave).clone().into(); - self.fd - .lock() - .unwrap() - .set_xsave(&xsave) - .map_err(|e| cpu::HypervisorCpuError::SetXsaveState(e.into())) + // SAFETY: Here we trust the kernel not to read past the end of the kvm_xsave struct + // when calling the kvm-ioctl library function. + unsafe { + self.fd + .lock() + .unwrap() + .set_xsave(&xsave) + .map_err(|e| cpu::HypervisorCpuError::SetXsaveState(e.into())) + } } #[cfg(target_arch = "x86_64")] diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index fd86b5e3cf..5056fd1acf 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -36,7 +36,7 @@ vhost = { workspace = true, features = [ "vhost-user-frontend", "vhost-vdpa", ] } -virtio-bindings = { workspace = true, features = ["virtio-v5_0_0"] } +virtio-bindings = { workspace = true } virtio-queue = { workspace = true } vm-allocator = { path = "../vm-allocator" } vm-device = { path = "../vm-device" } diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 7a8952f211..decd6ede64 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -16,7 +16,8 @@ use vhost::vhost_kern::vdpa::VhostKernVdpa; use vhost::vhost_kern::vhost_binding::VHOST_BACKEND_F_SUSPEND; use vhost::vhost_kern::VhostKernFeatures; use vhost::{VhostBackend, VringConfigData}; -use virtio_queue::{Descriptor, Queue, QueueT}; +use virtio_queue::desc::RawDescriptor; +use virtio_queue::{Queue, QueueT}; use vm_device::dma_mapping::ExternalDmaMapping; use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; @@ -245,7 +246,7 @@ impl Vdpa { flags: 0u32, desc_table_addr: queue.desc_table().translate_gpa( self.common.access_platform.as_ref(), - queue_size as usize * std::mem::size_of::(), + queue_size as usize * std::mem::size_of::(), ), used_ring_addr: queue.used_ring().translate_gpa( self.common.access_platform.as_ref(), diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 60ee6b32dc..e1204fbd7d 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -18,7 +18,8 @@ use vhost::vhost_user::{ Frontend, FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler, }; use vhost::{VhostBackend, VhostUserDirtyLogRegion, VhostUserMemoryRegionInfo, VringConfigData}; -use virtio_queue::{Descriptor, Queue, QueueT}; +use virtio_queue::desc::RawDescriptor; +use virtio_queue::{Queue, QueueT}; use vm_memory::{ Address, Error as MmapError, FileOffset, GuestAddress, GuestMemory, GuestMemoryRegion, }; @@ -212,7 +213,7 @@ impl VhostUserHandle { desc_table_addr: get_host_address_range( mem, GuestAddress(queue.desc_table()), - actual_size * std::mem::size_of::(), + actual_size * std::mem::size_of::(), ) .ok_or(Error::DescriptorTableAddress)? as u64, // The used ring is {flags: u16; idx: u16; virtq_used_elem [{id: u16, len: u16}; actual_size]}, diff --git a/vm-virtio/src/queue.rs b/vm-virtio/src/queue.rs index 4e55cc4b5d..c33f6e5996 100644 --- a/vm-virtio/src/queue.rs +++ b/vm-virtio/src/queue.rs @@ -12,7 +12,8 @@ pub mod testing { use std::marker::PhantomData; use std::mem; - use virtio_queue::{Queue, QueueT, VirtqUsedElem}; + use virtio_queue::desc::split::VirtqUsedElem; + use virtio_queue::{Queue, QueueT}; use vm_memory::bitmap::AtomicBitmap; use vm_memory::{Address, Bytes, GuestAddress, GuestUsize}; From ea32b67098ffd57b2afef9163e98c83d47dba805 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 03:29:54 +0000 Subject: [PATCH 0003/1893] build: Bump proc-macro-crate from 3.2.0 to 3.3.0 Bumps [proc-macro-crate](https://github.com/bkchr/proc-macro-crate) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/bkchr/proc-macro-crate/releases) - [Commits](https://github.com/bkchr/proc-macro-crate/compare/v3.2.0...v3.3.0) --- updated-dependencies: - dependency-name: proc-macro-crate dependency-version: 3.3.0 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afd186fa63..cb55afc8e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1637,9 +1637,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] From 3d5b4d0b0c6b73e3ba7eb2727f6e01e5e64f01c3 Mon Sep 17 00:00:00 2001 From: ninollei Date: Wed, 4 Jun 2025 13:43:37 +0800 Subject: [PATCH 0004/1893] vmm: acpi: Use correct table name in error message Fix a copy-paste error using the wrong table name in the assertion Signed-off-by: ninollei --- vmm/src/acpi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index d45f6a7196..e59b63f58b 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -780,7 +780,7 @@ pub fn create_acpi_tables( let slit_offset = srat_offset.checked_add(srat.len() as u64).unwrap(); guest_mem .write_slice(slit.as_slice(), slit_offset) - .expect("Error writing SRAT table"); + .expect("Error writing SLIT table"); tables.push(slit_offset.0); prev_tbl_len = slit.len() as u64; From f9c134471ac88ea307d0baae8a8d1434b56b81ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20G=C3=BCntner?= Date: Mon, 7 Jul 2025 11:05:46 +0200 Subject: [PATCH 0005/1893] vmm: warn about deprecation of default IP address + mask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: #7083 Signed-off-by: Maximilian Güntner --- vmm/src/vm_config.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index a2c5b996b4..3e6cf447da 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -350,10 +350,12 @@ pub fn default_netconfig_tap() -> Option { } pub fn default_netconfig_ip() -> IpAddr { + warn!("Deprecation warning: No IP address provided. A default IP address is assigned. This behavior will be deprecated soon."); IpAddr::V4(Ipv4Addr::new(192, 168, 249, 1)) } pub fn default_netconfig_mask() -> IpAddr { + warn!("Deprecation warning: No network mask provided. A default network mask is assigned. This behavior will be deprecated soon."); IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0)) } From 9d4408ba7613d5c8da4e2a4db3bf0a794ed42d17 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 10 Jul 2025 16:26:38 +0200 Subject: [PATCH 0006/1893] vmm: add directory path to error message Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/migration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/migration.rs b/vmm/src/migration.rs index 2752e82e5b..d93b028055 100644 --- a/vmm/src/migration.rs +++ b/vmm/src/migration.rs @@ -27,7 +27,7 @@ pub fn url_to_path(url: &str) -> std::result::Result { if !path.is_dir() { return Err(MigratableError::MigrateSend(anyhow!( - "Destination is not a directory" + "Destination is not a directory: {path:?}" ))); } From 6ba949d7415a9d7d9a5272563e0ce122a6ac48e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20G=C3=BCntner?= Date: Wed, 9 Jul 2025 12:00:08 +0200 Subject: [PATCH 0007/1893] build: consolidate env_logger to workspace, update to 0.11.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maximilian Güntner --- Cargo.lock | 51 ++++++++++++++++++++++++++++++------- Cargo.toml | 1 + hypervisor/Cargo.toml | 2 +- vhost_user_block/Cargo.toml | 2 +- vhost_user_net/Cargo.toml | 2 +- 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb55afc8e1..a0ce308144 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -621,14 +621,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] @@ -900,12 +900,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hypervisor" version = "0.1.0" @@ -1030,6 +1024,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -1626,6 +1644,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.20" diff --git a/Cargo.toml b/Cargo.toml index 0cc8e53a9b..5f21b99a0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,6 +129,7 @@ igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } serde_json = "1.0.120" # other crates +env_logger = "0.11.8" thiserror = "2.0.12" uuid = { version = "1.17.0" } zerocopy = { version = "0.8.26", default-features = false } diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index fed24b6862..60f53c7d4d 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -62,4 +62,4 @@ optional = true version = "1.21.0" [dev-dependencies] -env_logger = "0.11.3" +env_logger = { workspace = true } diff --git a/vhost_user_block/Cargo.toml b/vhost_user_block/Cargo.toml index 4c63cfa4d0..d9aa7e105d 100644 --- a/vhost_user_block/Cargo.toml +++ b/vhost_user_block/Cargo.toml @@ -8,7 +8,7 @@ version = "0.1.0" [dependencies] block = { path = "../block" } clap = { version = "4.5.13", features = ["cargo", "wrap_help"] } -env_logger = "0.11.3" +env_logger = { workspace = true } epoll = "4.3.3" libc = "0.2.167" log = "0.4.22" diff --git a/vhost_user_net/Cargo.toml b/vhost_user_net/Cargo.toml index d36763b790..515287e2cc 100644 --- a/vhost_user_net/Cargo.toml +++ b/vhost_user_net/Cargo.toml @@ -7,7 +7,7 @@ version = "0.1.0" [dependencies] clap = { version = "4.5.13", features = ["cargo", "wrap_help"] } -env_logger = "0.11.3" +env_logger = { workspace = true } epoll = "4.3.3" libc = "0.2.167" log = "0.4.22" From 19dc733267fecd378e28bff3a0946839e1c15a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20G=C3=BCntner?= Date: Wed, 9 Jul 2025 12:03:38 +0200 Subject: [PATCH 0008/1893] ch-remote: add env_logger, log messages to stderr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now all messages generated using `log::level!` (e.g., `warn!`) have not been printed as `ch-remote` did not register a logger. Furthermore, replace all `eprintln!` with `error!` to align formatting for consistency. Signed-off-by: Maximilian Güntner --- Cargo.lock | 1 + Cargo.toml | 1 + src/bin/ch-remote.rs | 12 +++++++----- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0ce308144..1dfcd3afef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,6 +409,7 @@ dependencies = [ "clap", "dhat", "dirs", + "env_logger", "epoll", "event_monitor", "hypervisor", diff --git a/Cargo.toml b/Cargo.toml index 5f21b99a0f..a913cb6002 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ anyhow = "1.0.94" api_client = { path = "api_client" } clap = { version = "4.5.13", features = ["string"] } dhat = { version = "0.3.3", optional = true } +env_logger = { workspace = true } epoll = "4.3.3" event_monitor = { path = "event_monitor" } hypervisor = { path = "hypervisor" } diff --git a/src/bin/ch-remote.rs b/src/bin/ch-remote.rs index 9d8c4f68b4..4278af6175 100644 --- a/src/bin/ch-remote.rs +++ b/src/bin/ch-remote.rs @@ -17,6 +17,7 @@ use api_client::{ Error as ApiClientError, }; use clap::{Arg, ArgAction, ArgMatches, Command}; +use log::error; use option_parser::{ByteSized, ByteSizedParseError}; use thiserror::Error; use vmm::config::RestoreConfig; @@ -1071,6 +1072,7 @@ fn get_cli_commands_sorted() -> Box<[Command]> { } fn main() { + env_logger::init(); let app = Command::new("ch-remote") .author(env!("CARGO_PKG_AUTHORS")) .version(env!("BUILD_VERSION")) @@ -1092,7 +1094,7 @@ fn main() { #[cfg(not(feature = "dbus_api"))] (Some(api_sock),) => TargetApi::HttpApi( UnixStream::connect(api_sock).unwrap_or_else(|e| { - eprintln!("Error opening HTTP socket: {e}"); + error!("Error opening HTTP socket: {e}"); process::exit(1) }), PhantomData, @@ -1100,7 +1102,7 @@ fn main() { #[cfg(feature = "dbus_api")] (Some(api_sock), None, None) => TargetApi::HttpApi( UnixStream::connect(api_sock).unwrap_or_else(|e| { - eprintln!("Error opening HTTP socket: {e}"); + error!("Error opening HTTP socket: {e}"); process::exit(1) }), PhantomData, @@ -1114,19 +1116,19 @@ fn main() { ) .map_err(Error::DBusApiClient) .unwrap_or_else(|e| { - eprintln!("Error creating D-Bus proxy: {e}"); + error!("Error creating D-Bus proxy: {e}"); process::exit(1) }), ), #[cfg(feature = "dbus_api")] (Some(_), Some(_) | None, Some(_) | None) => { - println!( + error!( "`api-socket` and (dbus-service-name or dbus-object-path) are mutually exclusive" ); process::exit(1); } _ => { - println!("Please either provide the api-socket option or dbus-service-name and dbus-object-path options"); + error!("Please either provide the api-socket option or dbus-service-name and dbus-object-path options"); process::exit(1); } }; From 50b33db718094421c085645941406b13a49eaf09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20G=C3=BCntner?= Date: Tue, 8 Jul 2025 22:29:29 +0200 Subject: [PATCH 0009/1893] vmm: replace eprintln with log::error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify log formatting and printing as `eprintln!` and `log::error!` would be used alongside each other. When using e.g. `env_logger` lines printed with `eprintln!` would lack formatting / colors. Currently only relevant in `ch-remote` + `cli_print_error_chain`. Note that the replaced messages now also end up in the logfile of `cloud-hypervisor` when configured and not any longer in stderr. Signed-off-by: Maximilian Güntner --- src/lib.rs | 16 +++++++++------- src/main.rs | 8 ++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1596a13f47..355f0a9cfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ use std::error::Error; +use log::error; + /// Prints a chain of errors to the user in a consistent manner. /// The user will see a clear chain of errors, followed by debug output /// for opening issues. @@ -19,10 +21,10 @@ pub fn cli_print_error_chain<'a>( ) { eprint!("Error: {component} exited with the following "); if top_error.source().is_none() { - eprintln!("error:"); - eprintln!(" {top_error}"); + error!("error:"); + error!(" {top_error}"); } else { - eprintln!("chain of errors:"); + error!("chain of errors:"); std::iter::successors(Some(top_error), |sub_error| { // Dereference necessary to mitigate rustc compiler bug. // See @@ -32,13 +34,13 @@ pub fn cli_print_error_chain<'a>( .for_each(|(level, error)| { // Special case: handling of HTTP Server responses in ch-remote if let Some(message) = display_modifier(level, 2, error) { - eprintln!("{message}"); + error!("{message}"); } else { - eprintln!(" {level}: {error}"); + error!(" {level}: {error}"); } }); } - eprintln!(); - eprintln!("Debug Info: {top_error:?}"); + error!(""); + error!("Debug Info: {top_error:?}"); } diff --git a/src/main.rs b/src/main.rs index 4a0fbe91f9..101da1706b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use std::{env, io}; use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command}; use event_monitor::event; use libc::EFD_NONBLOCK; -use log::{warn, LevelFilter}; +use log::{error, warn, LevelFilter}; use option_parser::OptionParser; use seccompiler::SeccompAction; use signal_hook::consts::SIGSYS; @@ -561,7 +561,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { signal_hook::low_level::emulate_default_handler(SIGSYS).unwrap(); }) } - .map_err(|e| eprintln!("Error adding SIGSYS signal handler: {e}")) + .map_err(|e| error!("Error adding SIGSYS signal handler: {e}")) .ok(); } @@ -575,13 +575,13 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { // dedicated signal handling thread we'll start in a bit. for sig in &vmm::vm::Vm::HANDLED_SIGNALS { if let Err(e) = block_signal(*sig) { - eprintln!("Error blocking signals: {e}"); + error!("Error blocking signals: {e}"); } } for sig in &vmm::Vmm::HANDLED_SIGNALS { if let Err(e) = block_signal(*sig) { - eprintln!("Error blocking signals: {e}"); + error!("Error blocking signals: {e}"); } } From 072f06ff4c8ecf0a473d15237e942f833629565d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20G=C3=BCntner?= Date: Tue, 8 Jul 2025 22:36:55 +0200 Subject: [PATCH 0010/1893] misc: vhost_user_net: replace eprintln with log::error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Other lines are already logged using `log::error!` and `env_logger` is initialized before calling `start_net_backend` in `main()`. Signed-off-by: Maximilian Güntner --- vhost_user_net/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vhost_user_net/src/lib.rs b/vhost_user_net/src/lib.rs index 433f0c7a29..56b54ad6d8 100644 --- a/vhost_user_net/src/lib.rs +++ b/vhost_user_net/src/lib.rs @@ -348,7 +348,7 @@ pub fn start_net_backend(backend_command: &str) { let backend_config = match VhostUserNetBackendConfig::parse(backend_command) { Ok(config) => config, Err(e) => { - eprintln!("Failed parsing parameters {e:?}"); + error!("Failed parsing parameters {e:?}"); process::exit(1); } }; From 87e74719ec5dd0548bfb5003f6656876eca16712 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Wed, 9 Jul 2025 15:18:17 +0000 Subject: [PATCH 0011/1893] hypervisor: riscv64: Use offset_of from std::mem `std::mem::offset_of` supports calculating offset of nested structures, let's use implementation provided by std instead of manual implementation. Signed-off-by: Ruoqing He --- hypervisor/src/kvm/mod.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 8bb49e52ca..c7888cb580 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -14,6 +14,8 @@ use std::any::Any; use std::collections::HashMap; #[cfg(target_arch = "x86_64")] use std::fs::File; +#[cfg(target_arch = "riscv64")] +use std::mem::offset_of; #[cfg(target_arch = "x86_64")] use std::os::unix::io::AsRawFd; #[cfg(feature = "tdx")] @@ -41,12 +43,12 @@ pub use crate::riscv64::{ aia::AiaImsicsState as AiaState, check_required_kvm_extensions, is_non_core_register, VcpuKvmState, }; +#[cfg(target_arch = "riscv64")] +use crate::riscv64_reg_id; use crate::vm::{self, InterruptSourceConfig, VmOps}; #[cfg(target_arch = "aarch64")] use crate::{arm64_core_reg_id, offset_of}; use crate::{cpu, hypervisor, HypervisorType}; -#[cfg(target_arch = "riscv64")] -use crate::{offset_of, riscv64_reg_id}; // x86_64 dependencies #[cfg(target_arch = "x86_64")] pub mod x86_64; @@ -99,7 +101,7 @@ use kvm_bindings::{ KVM_REG_ARM_CORE, KVM_REG_SIZE_U128, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64, }; #[cfg(target_arch = "riscv64")] -use kvm_bindings::{kvm_riscv_core, user_regs_struct, KVM_REG_RISCV_CORE}; +use kvm_bindings::{kvm_riscv_core, KVM_REG_RISCV_CORE}; #[cfg(feature = "tdx")] use kvm_bindings::{kvm_run__bindgen_ty_1, KVMIO}; pub use kvm_ioctls::{Cap, Kvm, VcpuExit}; @@ -1495,7 +1497,7 @@ impl cpu::Vcpu for KvmVcpu { state.mode = u64::from_le_bytes(bytes); }; ($reg_name:ident) => { - let off = offset_of!(kvm_riscv_core, regs, user_regs_struct, $reg_name); + let off = offset_of!(kvm_riscv_core, regs.$reg_name); let mut bytes = [0_u8; 8]; self.fd .lock() @@ -1703,7 +1705,7 @@ impl cpu::Vcpu for KvmVcpu { .map_err(|e| cpu::HypervisorCpuError::SetRiscvCoreRegister(e.into()))?; }; ($reg_name:ident) => { - let off = offset_of!(kvm_riscv_core, regs, user_regs_struct, $reg_name); + let off = offset_of!(kvm_riscv_core, regs.$reg_name); self.fd .lock() .unwrap() @@ -2328,7 +2330,7 @@ impl cpu::Vcpu for KvmVcpu { /// fn setup_regs(&self, cpu_id: u8, boot_ip: u64, fdt_start: u64) -> cpu::Result<()> { // Setting the A0 () to the hartid of this CPU. - let a0 = offset_of!(kvm_riscv_core, regs, user_regs_struct, a0); + let a0 = offset_of!(kvm_riscv_core, regs.a0); self.fd .lock() .unwrap() @@ -2339,7 +2341,7 @@ impl cpu::Vcpu for KvmVcpu { .map_err(|e| cpu::HypervisorCpuError::SetRiscvCoreRegister(e.into()))?; // Setting the PC (Processor Counter) to the current program address (kernel address). - let pc = offset_of!(kvm_riscv_core, regs, user_regs_struct, pc); + let pc = offset_of!(kvm_riscv_core, regs.pc); self.fd .lock() .unwrap() @@ -2352,7 +2354,7 @@ impl cpu::Vcpu for KvmVcpu { // Last mandatory thing to set -> the address pointing to the FDT (also called DTB). // "The device tree blob (dtb) must be placed on an 8-byte boundary and must // not exceed 64 kilobytes in size." -> https://www.kernel.org/doc/Documentation/arch/riscv/boot.txt. - let a1 = offset_of!(kvm_riscv_core, regs, user_regs_struct, a1); + let a1 = offset_of!(kvm_riscv_core, regs.a1); self.fd .lock() .unwrap() @@ -3019,7 +3021,7 @@ mod tests { let vcpu0 = vm.create_vcpu(0, None).unwrap(); let core_regs = StandardRegisters::from(kvm_riscv_core { - regs: user_regs_struct { + regs: kvm_bindings::user_regs_struct { pc: 0x00, ra: 0x01, sp: 0x02, From aa6fefa80f041907a5d264eed8cbca7d1426d6c1 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Wed, 9 Jul 2025 15:20:07 +0000 Subject: [PATCH 0012/1893] hypervisor: riscv64: Remove manually implemented offset_of Manually implemented `_offset_of` and `offset_of` in `arch/riscv64/mod.rs` are not used now, remove them. Signed-off-by: Ruoqing He --- hypervisor/src/kvm/riscv64/mod.rs | 35 ------------------------------- 1 file changed, 35 deletions(-) diff --git a/hypervisor/src/kvm/riscv64/mod.rs b/hypervisor/src/kvm/riscv64/mod.rs index 94017d827b..c589b488a3 100644 --- a/hypervisor/src/kvm/riscv64/mod.rs +++ b/hypervisor/src/kvm/riscv64/mod.rs @@ -13,41 +13,6 @@ use serde::{Deserialize, Serialize}; use crate::kvm::{KvmError, KvmResult}; -// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having -// an instance of that structure. -#[macro_export] -macro_rules! _offset_of { - ($str:ty, $field:ident) => {{ - let tmp: std::mem::MaybeUninit<$str> = std::mem::MaybeUninit::uninit(); - let base = tmp.as_ptr(); - - // Avoid warnings when nesting `unsafe` blocks. - #[allow(unused_unsafe)] - // SAFETY: The pointer is valid and aligned, just not initialised. Using `addr_of` ensures - // that we don't actually read from `base` (which would be UB) nor create an intermediate - // reference. - let member = unsafe { core::ptr::addr_of!((*base).$field) } as *const u8; - - // Avoid warnings when nesting `unsafe` blocks. - #[allow(unused_unsafe)] - // SAFETY: The two pointers are within the same allocated object `tmp`. All requirements - // from offset_from are upheld. - unsafe { - member.offset_from(base as *const u8) as usize - } - }}; -} - -#[macro_export] -macro_rules! offset_of { - ($reg_struct:ty, $field:ident) => { - $crate::_offset_of!($reg_struct, $field) - }; - ($outer_reg_struct:ty, $outer_field:ident, $($inner_reg_struct:ty, $inner_field:ident), +) => { - $crate::_offset_of!($outer_reg_struct, $outer_field) + offset_of!($($inner_reg_struct, $inner_field), +) - }; -} - // Following are macros that help with getting the ID of a riscv64 register, including config registers, core registers and timer registers. // The register of core registers are wrapped in the `user_regs_struct` structure. See: // https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h#L62 From 008f259aff8f6fc044f5c6de5c6162546eb488cb Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Thu, 10 Jul 2025 01:54:23 +0000 Subject: [PATCH 0013/1893] hypervisor: aarch64: Use offset_of from std::mem `std::mem::offset_of` is stabilized since Rust 1.77, let's use implementation provided by std instead of manual implementation. Signed-off-by: Ruoqing He --- hypervisor/src/kvm/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index c7888cb580..fa0ed209df 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -14,7 +14,7 @@ use std::any::Any; use std::collections::HashMap; #[cfg(target_arch = "x86_64")] use std::fs::File; -#[cfg(target_arch = "riscv64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use std::mem::offset_of; #[cfg(target_arch = "x86_64")] use std::os::unix::io::AsRawFd; @@ -36,6 +36,8 @@ pub use crate::aarch64::{check_required_kvm_extensions, is_system_register, Vcpu use crate::arch::aarch64::gic::{Vgic, VgicConfig}; #[cfg(target_arch = "riscv64")] use crate::arch::riscv64::aia::{Vaia, VaiaConfig}; +#[cfg(target_arch = "aarch64")] +use crate::arm64_core_reg_id; #[cfg(target_arch = "riscv64")] use crate::riscv64::aia::KvmAiaImsics; #[cfg(target_arch = "riscv64")] @@ -46,8 +48,6 @@ pub use crate::riscv64::{ #[cfg(target_arch = "riscv64")] use crate::riscv64_reg_id; use crate::vm::{self, InterruptSourceConfig, VmOps}; -#[cfg(target_arch = "aarch64")] -use crate::{arm64_core_reg_id, offset_of}; use crate::{cpu, hypervisor, HypervisorType}; // x86_64 dependencies #[cfg(target_arch = "x86_64")] From 07cc1f654518aafb51563b2bcbdf6684a6be37b1 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Thu, 10 Jul 2025 01:56:26 +0000 Subject: [PATCH 0014/1893] hypervisor: aarch64: Remove manually implemented offset_of Manually implemented `offset_of` in `arch/aarch64/mod.rs` is not used now, remove it. Signed-off-by: Ruoqing He --- hypervisor/src/kvm/aarch64/mod.rs | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/hypervisor/src/kvm/aarch64/mod.rs b/hypervisor/src/kvm/aarch64/mod.rs index 0bef5e07d3..20fef72444 100644 --- a/hypervisor/src/kvm/aarch64/mod.rs +++ b/hypervisor/src/kvm/aarch64/mod.rs @@ -19,31 +19,6 @@ use serde::{Deserialize, Serialize}; use crate::kvm::{KvmError, KvmResult}; -// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having -// an instance of that structure. -#[macro_export] -macro_rules! offset_of { - ($str:ty, $field:ident) => {{ - let tmp: std::mem::MaybeUninit<$str> = std::mem::MaybeUninit::uninit(); - let base = tmp.as_ptr(); - - // Avoid warnings when nesting `unsafe` blocks. - #[allow(unused_unsafe)] - // SAFETY: The pointer is valid and aligned, just not initialised. Using `addr_of` ensures - // that we don't actually read from `base` (which would be UB) nor create an intermediate - // reference. - let member = unsafe { core::ptr::addr_of!((*base).$field) } as *const u8; - - // Avoid warnings when nesting `unsafe` blocks. - #[allow(unused_unsafe)] - // SAFETY: The two pointers are within the same allocated object `tmp`. All requirements - // from offset_from are upheld. - unsafe { - member.offset_from(base as *const u8) as usize - } - }}; -} - // Following are macros that help with getting the ID of a aarch64 core register. // The core register are represented by the user_pt_regs structure. Look for it in // arch/arm64/include/uapi/asm/ptrace.h. From 6da5c32fd9dd6cc175a811e4b77f7e95fc633eea Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Thu, 10 Jul 2025 02:16:44 +0000 Subject: [PATCH 0015/1893] hypervisor: aarch64: Use offset_of for nested fields `std::mem::offset_of` could be used for calculating nested fields, use this feature to shorten aarch64 reg offset calculation. Signed-off-by: Ruoqing He --- hypervisor/src/kvm/mod.rs | 28 +++++++++++++--------------- vmm/src/cpu.rs | 6 +++--- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index fa0ed209df..8b8351b6de 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -95,10 +95,10 @@ pub use kvm_bindings::{ }; #[cfg(target_arch = "aarch64")] use kvm_bindings::{ - kvm_regs, user_fpsimd_state, user_pt_regs, KVM_GUESTDBG_USE_HW, KVM_NR_SPSR, KVM_REG_ARM64, - KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_CRM_MASK, KVM_REG_ARM64_SYSREG_CRN_MASK, - KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP1_MASK, KVM_REG_ARM64_SYSREG_OP2_MASK, - KVM_REG_ARM_CORE, KVM_REG_SIZE_U128, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64, + kvm_regs, user_pt_regs, KVM_GUESTDBG_USE_HW, KVM_NR_SPSR, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, + KVM_REG_ARM64_SYSREG_CRM_MASK, KVM_REG_ARM64_SYSREG_CRN_MASK, KVM_REG_ARM64_SYSREG_OP0_MASK, + KVM_REG_ARM64_SYSREG_OP1_MASK, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM_CORE, + KVM_REG_SIZE_U128, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64, }; #[cfg(target_arch = "riscv64")] use kvm_bindings::{kvm_riscv_core, KVM_REG_RISCV_CORE}; @@ -1440,7 +1440,7 @@ impl cpu::Vcpu for KvmVcpu { // Now moving on to floating point registers which are stored in the user_fpsimd_state in the kernel: // https://elixir.free-electrons.com/linux/v4.9.62/source/arch/arm64/include/uapi/asm/kvm.h#L53 - let mut off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, vregs); + let mut off = offset_of!(kvm_regs, fp_regs.vregs); for i in 0..32 { let mut bytes = [0_u8; 16]; self.fd @@ -1453,7 +1453,7 @@ impl cpu::Vcpu for KvmVcpu { } // Floating-point Status Register - let off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, fpsr); + let off = offset_of!(kvm_regs, fp_regs.fpsr); let mut bytes = [0_u8; 4]; self.fd .lock() @@ -1463,7 +1463,7 @@ impl cpu::Vcpu for KvmVcpu { state.fp_regs.fpsr = u32::from_le_bytes(bytes); // Floating-point Control Register - let off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, fpcr); + let off = offset_of!(kvm_regs, fp_regs.fpcr); let mut bytes = [0_u8; 4]; self.fd .lock() @@ -1644,7 +1644,7 @@ impl cpu::Vcpu for KvmVcpu { off += std::mem::size_of::(); } - let mut off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, vregs); + let mut off = offset_of!(kvm_regs, fp_regs.vregs); for i in 0..32 { self.fd .lock() @@ -1657,7 +1657,7 @@ impl cpu::Vcpu for KvmVcpu { off += mem::size_of::(); } - let off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, fpsr); + let off = offset_of!(kvm_regs, fp_regs.fpsr); self.fd .lock() .unwrap() @@ -1667,7 +1667,7 @@ impl cpu::Vcpu for KvmVcpu { ) .map_err(|e| cpu::HypervisorCpuError::SetAarchCoreRegister(e.into()))?; - let off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, fpcr); + let off = offset_of!(kvm_regs, fp_regs.fpcr); self.fd .lock() .unwrap() @@ -2281,10 +2281,8 @@ impl cpu::Vcpu for KvmVcpu { /// #[cfg(target_arch = "aarch64")] fn setup_regs(&self, cpu_id: u8, boot_ip: u64, fdt_start: u64) -> cpu::Result<()> { - let kreg_off = offset_of!(kvm_regs, regs); - // Get the register index of the PSTATE (Processor State) register. - let pstate = offset_of!(user_pt_regs, pstate) + kreg_off; + let pstate = offset_of!(kvm_regs, regs.pstate); self.fd .lock() .unwrap() @@ -2297,7 +2295,7 @@ impl cpu::Vcpu for KvmVcpu { // Other vCPUs are powered off initially awaiting PSCI wakeup. if cpu_id == 0 { // Setting the PC (Processor Counter) to the current program address (kernel address). - let pc = offset_of!(user_pt_regs, pc) + kreg_off; + let pc = offset_of!(kvm_regs, regs.pc); self.fd .lock() .unwrap() @@ -2311,7 +2309,7 @@ impl cpu::Vcpu for KvmVcpu { // "The device tree blob (dtb) must be placed on an 8-byte boundary and must // not exceed 2 megabytes in size." -> https://www.kernel.org/doc/Documentation/arm64/booting.txt. // We are choosing to place it the end of DRAM. See `get_fdt_addr`. - let regs0 = offset_of!(user_pt_regs, regs) + kreg_off; + let regs0 = offset_of!(kvm_regs, regs.regs); self.fd .lock() .unwrap() diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index e26946538e..d8278067a5 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -2963,19 +2963,19 @@ mod tests { #[cfg(test)] mod tests { #[cfg(feature = "kvm")] - use std::mem; + use std::{mem, mem::offset_of}; use arch::layout; use hypervisor::arch::aarch64::regs::MPIDR_EL1; #[cfg(feature = "kvm")] + use hypervisor::arm64_core_reg_id; + #[cfg(feature = "kvm")] use hypervisor::kvm::aarch64::is_system_register; #[cfg(feature = "kvm")] use hypervisor::kvm::kvm_bindings::{ user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_ARM_CORE, KVM_REG_SIZE_U64, }; use hypervisor::HypervisorCpuError; - #[cfg(feature = "kvm")] - use hypervisor::{arm64_core_reg_id, offset_of}; #[test] fn test_setup_regs() { From 96528f84f95cf9e6006b2192f6f693da04e5418c Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 10 Jul 2025 19:00:26 +0000 Subject: [PATCH 0016/1893] build: Bump gdbstub from 0.7.1 to 0.7.6 Bumps [gdbstub](https://github.com/daniel5151/gdbstub) from 0.7.1 to 0.7.6. - [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.1...0.7.6) --- updated-dependencies: - dependency-name: gdbstub dependency-version: 0.7.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Bo Chen --- Cargo.lock | 4 ++-- fuzz/Cargo.lock | 4 ++-- vmm/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1dfcd3afef..7acf24989b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -824,9 +824,9 @@ dependencies = [ [[package]] name = "gdbstub" -version = "0.7.1" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6341b3480afbb34eaefc7f92713bc92f2d83e338aaa1c44192f9c2956f4a4903" +checksum = "71d66e32caf5dd59f561be0143e413e01d651bd8498eb9aa0be8c482c81c8d31" dependencies = [ "bitflags 2.9.0", "cfg-if", diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 85596958ce..4ea15fff34 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -403,9 +403,9 @@ checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "gdbstub" -version = "0.7.2" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcc892208d6998fb57e7c3e05883def66f8130924bba066beb0cfe71566a9f6" +checksum = "71d66e32caf5dd59f561be0143e413e01d651bd8498eb9aa0be8c482c81c8d31" dependencies = [ "bitflags 2.9.0", "cfg-if", diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 342260c0da..813d74f351 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -40,7 +40,7 @@ epoll = "4.3.3" event_monitor = { path = "../event_monitor" } flume = "0.11.1" futures = { version = "0.3.31", optional = true } -gdbstub = { version = "0.7.1", optional = true } +gdbstub = { version = "0.7.6", optional = true } gdbstub_arch = { version = "0.3.0", optional = true } hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } From 0659eaeba18a03ca32244aa9eac585df2c682786 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 23:56:32 +0000 Subject: [PATCH 0017/1893] build: Bump async-signal from 0.2.10 to 0.2.11 Bumps [async-signal](https://github.com/smol-rs/async-signal) from 0.2.10 to 0.2.11. - [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.10...v0.2.11) --- updated-dependencies: - dependency-name: async-signal dependency-version: 0.2.11 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7acf24989b..2324d4e720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,9 +222,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" dependencies = [ "async-io", "async-lock", @@ -232,7 +232,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.44", + "rustix 1.0.7", "signal-hook-registry", "slab", "windows-sys 0.59.0", From b268e88ba31b172282f2c3556099a667622d7b24 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 11 Jul 2025 10:20:10 -0700 Subject: [PATCH 0018/1893] virtio-devices: remove unnecessary parentheses Cargo fuzz build report an warning: warning: unnecessary parentheses around closure body --> virtio-devices/src/iommu.rs:578:41 | 578 |.retain(|&x, _| (x < req.virt_start || x > req.virt_end)); | ^ | = note: `#[warn(unused_parens)]` on by default help: remove these parentheses | 578 -.retain(|&x, _| (x < req.virt_start || x > req.virt_end)); 578 +.retain(|&x, _| x < req.virt_start || x > req.virt_end); | warning: `virtio-devices` (lib) generated 1 warning (run `cargo fix --lib -p virtio-devices` to apply 1 suggestion) Signed-off-by: Muminul Islam --- virtio-devices/src/iommu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index 6a0ccd6be3..cdf9e59087 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -575,7 +575,7 @@ impl Request { .get_mut(&domain_id) .unwrap() .mappings - .retain(|&x, _| (x < req.virt_start || x > req.virt_end)); + .retain(|&x, _| x < req.virt_start || x > req.virt_end); } VIRTIO_IOMMU_T_PROBE => { if desc_size_left != size_of::() { From a5cd1b4fbefe95042e3715dba54e24a88fd1d3ca Mon Sep 17 00:00:00 2001 From: Nuno Das Neves Date: Fri, 11 Jul 2025 14:00:22 -0700 Subject: [PATCH 0019/1893] build: Bump mshv-ioctls and mshv-bindings to v0.5.2 Also update the version in the fuzz crate. Signed-off-by: Nuno Das Neves --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- fuzz/Cargo.lock | 4 ++-- fuzz/Cargo.toml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2324d4e720..2da292459b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1225,9 +1225,9 @@ checksum = "9bec4598fddb13cc7b528819e697852653252b760f1228b7642679bf2ff2cd07" [[package]] name = "mshv-bindings" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909de5fd4a5a3347a6c62872f6816e6279efd8615a753f10a3bc4daaef8a72ef" +checksum = "07f94f542c738f19317363222a7f415588c04cda964882479af41948ac3c3647" dependencies = [ "libc", "num_enum", @@ -1239,9 +1239,9 @@ dependencies = [ [[package]] name = "mshv-ioctls" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7d94972588d562bd349b916de6a43f2ee268e6e9c91cfb5b30549ed4ea2751" +checksum = "8a6df0848f14eb69505a28673f94acdd830cf248fb57022b21f24e242b702e66" dependencies = [ "libc", "mshv-bindings", diff --git a/Cargo.toml b/Cargo.toml index a913cb6002..f46fd602e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,8 +107,8 @@ acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" kvm-bindings = "0.12.0" kvm-ioctls = "0.22.0" linux-loader = "0.13.0" -mshv-bindings = "0.5.1" -mshv-ioctls = "0.5.1" +mshv-bindings = "0.5.2" +mshv-ioctls = "0.5.2" seccompiler = "0.5.0" vfio-bindings = { version = "0.5.0", default-features = false } vfio-ioctls = { version = "0.5.0", default-features = false } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 4ea15fff34..bb325964f1 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "mshv-bindings" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909de5fd4a5a3347a6c62872f6816e6279efd8615a753f10a3bc4daaef8a72ef" +checksum = "07f94f542c738f19317363222a7f415588c04cda964882479af41948ac3c3647" dependencies = [ "libc", "num_enum", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 27bfc87edb..5d6ac0a29e 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -24,7 +24,7 @@ libc = "0.2.155" libfuzzer-sys = "0.4.7" linux-loader = { version = "0.13.0", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } -mshv-bindings = "0.5.0" +mshv-bindings = "0.5.2" net_util = { path = "../net_util" } seccompiler = "0.5.0" virtio-devices = { path = "../virtio-devices" } From 01aed9733cdc9fc248ce1acb2abca545a325490e Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Mon, 14 Jul 2025 17:17:07 +0200 Subject: [PATCH 0020/1893] build: add missing dependency features This makes it possible to run cargo test just for the virtio-devices crate (as long as either KVM or MSHV is specified). Signed-off-by: Alyssa Ross --- pci/Cargo.toml | 4 ++-- virtio-devices/Cargo.toml | 2 ++ vmm/Cargo.toml | 9 ++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pci/Cargo.toml b/pci/Cargo.toml index 9273340879..0e29051815 100644 --- a/pci/Cargo.toml +++ b/pci/Cargo.toml @@ -6,8 +6,8 @@ version = "0.1.0" [features] default = [] -kvm = ["vfio-ioctls/kvm"] -mshv = ["vfio-ioctls/mshv"] +kvm = ["hypervisor/kvm", "vfio-ioctls/kvm"] +mshv = ["hypervisor/mshv", "vfio-ioctls/mshv"] [dependencies] anyhow = "1.0.94" diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index 5056fd1acf..73eaec803c 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -6,6 +6,8 @@ version = "0.1.0" [features] default = [] +kvm = ["pci/kvm"] +mshv = ["pci/mshv"] sev_snp = ["mshv-ioctls"] [dependencies] diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 813d74f351..968341eedb 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -16,9 +16,16 @@ kvm = [ "hypervisor/kvm", "pci/kvm", "vfio-ioctls/kvm", + "virtio-devices/kvm", "vm-device/kvm", ] -mshv = ["hypervisor/mshv", "pci/mshv", "vfio-ioctls/mshv", "vm-device/mshv"] +mshv = [ + "hypervisor/mshv", + "pci/mshv", + "vfio-ioctls/mshv", + "virtio-devices/mshv", + "vm-device/mshv", +] pvmemcontrol = ["devices/pvmemcontrol"] sev_snp = ["arch/sev_snp", "hypervisor/sev_snp", "virtio-devices/sev_snp"] tdx = ["arch/tdx", "hypervisor/tdx"] From ec8fceb4a6a537c4d838287d47edc2c156379b4e Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Mon, 14 Jul 2025 18:19:42 +0200 Subject: [PATCH 0021/1893] virtio-devices: stop corrupting vsock commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The read_exact() call was introduced in 82ac114b8 ("virtio-devices: vsock: handle short read in muxer") to solve a crash when a connection disconnected without sending any data, but it introduced a problem of its own: because the socket is non-blocking, read_exact() may read some data, then return ErrorKind::WouldBlock. In that case, the data it read will be discarded. So for example if it read "CONNECT ", and then nothing else was available to read yet, "CONNECT " would be discarded, and so the next time this function was called, when epoll triggered again for the socket, only the following data would end up in command.buf, causing an error due to just a port number being an invalid command. Contrary to that commit message, this code was actually designed to handle short reads just fine — in the case of a short read, it stores the data it has read in command, and returns Error::UnixRead(ErrorKind::WouldBlock), which is ignored by the caller, and the function gets called again when there is more data to read, building up command potentially over the course of several reads. The only thing it didn't handle correctly, as far as I can tell, was a 0-byte read, which happens when a client disconnects from the socket without writing anything. All that's needed to fix this is to avoid an invalid subtraction in that case, so this change reverts 82ac114b8, fixing the issue with partial commands being discarded, and instead handles the 0-byte read by using slice::get, and treating an empty command as an incomplete command, which of course it is. Fixes: 82ac114b8 ("virtio-devices: vsock: handle short read in muxer") Signed-off-by: Alyssa Ross --- virtio-devices/src/vsock/unix/muxer.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index 55e819d4b7..ebe0dc723b 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -493,15 +493,18 @@ impl VsockMuxer { const MIN_COMMAND_LEN: usize = 10; // Bring in the minimum number of bytes that we should be able to read. - stream - .read_exact(&mut command.buf[command.len..MIN_COMMAND_LEN]) - .map_err(Error::UnixRead)?; - command.len = MIN_COMMAND_LEN; + if command.len < MIN_COMMAND_LEN { + command.len += stream + .read(&mut command.buf[command.len..MIN_COMMAND_LEN]) + .map_err(Error::UnixRead)?; + } // Now, finish reading the destination port number, by bringing in one byte at a time, // until we reach an EOL terminator (or our buffer space runs out). Yeah, not // particularly proud of this approach, but it will have to do for now. - while command.buf[command.len - 1] != b'\n' && command.len < command.buf.len() { + while command.len.checked_sub(1).map(|n| command.buf[n]) != Some(b'\n') + && command.len < command.buf.len() + { command.len += stream .read(&mut command.buf[command.len..=command.len]) .map_err(Error::UnixRead)?; From e32fa593e5516aeab8412db2cabc5dc758d94fc4 Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Fri, 11 Jul 2025 11:43:30 +0800 Subject: [PATCH 0022/1893] build: clean up unused dependencies Signed-off-by: Songqian Li --- Cargo.lock | 13 ------------- arch/Cargo.toml | 1 - performance-metrics/Cargo.toml | 1 - test_infra/Cargo.toml | 1 - tpm/Cargo.toml | 1 - vhost_user_block/Cargo.toml | 1 - virtio-devices/Cargo.toml | 3 --- vm-device/Cargo.toml | 2 -- vm-virtio/Cargo.toml | 1 - vmm/Cargo.toml | 2 -- 10 files changed, 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2da292459b..7e239b3092 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,7 +119,6 @@ dependencies = [ "uuid", "vm-fdt", "vm-memory", - "vm-migration", "vmm-sys-util", ] @@ -1507,7 +1506,6 @@ dependencies = [ "serde_json", "test_infra", "thiserror 2.0.12", - "wait-timeout", ] [[package]] @@ -2065,7 +2063,6 @@ dependencies = [ "dirs", "epoll", "libc", - "serde", "serde_json", "ssh2", "thiserror 2.0.12", @@ -2141,7 +2138,6 @@ name = "tpm" version = "0.1.0" dependencies = [ "anyhow", - "byteorder", "libc", "log", "net_gen", @@ -2312,7 +2308,6 @@ dependencies = [ "block", "clap", "env_logger", - "epoll", "libc", "log", "option_parser", @@ -2355,7 +2350,6 @@ name = "virtio-devices" version = "0.1.0" dependencies = [ "anyhow", - "arc-swap", "block", "byteorder", "epoll", @@ -2363,13 +2357,11 @@ dependencies = [ "libc", "log", "mshv-ioctls", - "net_gen", "net_util", "pci", "rate_limiter", "seccompiler", "serde", - "serde_json", "serde_with", "serial_buffer", "thiserror 2.0.12", @@ -2409,12 +2401,10 @@ dependencies = [ name = "vm-device" version = "0.1.0" dependencies = [ - "anyhow", "hypervisor", "serde", "thiserror 2.0.12", "vfio-ioctls", - "vm-memory", "vmm-sys-util", ] @@ -2450,7 +2440,6 @@ dependencies = [ name = "vm-virtio" version = "0.1.0" dependencies = [ - "log", "virtio-queue", "vm-memory", ] @@ -2461,7 +2450,6 @@ version = "0.1.0" dependencies = [ "acpi_tables", "anyhow", - "arc-swap", "arch", "bitflags 2.9.0", "block", @@ -2503,7 +2491,6 @@ dependencies = [ "vfio_user", "virtio-bindings", "virtio-devices", - "virtio-queue", "vm-allocator", "vm-device", "vm-memory", diff --git a/arch/Cargo.toml b/arch/Cargo.toml index 4c068d131f..03d2ad4af5 100644 --- a/arch/Cargo.toml +++ b/arch/Cargo.toml @@ -21,7 +21,6 @@ serde = { version = "1.0.208", features = ["derive", "rc"] } thiserror = { workspace = true } uuid = { workspace = true } vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] } -vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true, features = ["with-serde"] } [target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies] diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index 87dce7862e..8572bf5160 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -12,4 +12,3 @@ serde = { version = "1.0.208", features = ["derive", "rc"] } serde_json = { workspace = true } test_infra = { path = "../test_infra" } thiserror = { workspace = true } -wait-timeout = "0.2.0" diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index 0374bb0fad..5aeae23c58 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -8,7 +8,6 @@ version = "0.1.0" dirs = "6.0.0" epoll = "4.3.3" libc = "0.2.167" -serde = { version = "1.0.208", features = ["derive", "rc"] } serde_json = { workspace = true } ssh2 = { version = "0.9.4", features = ["vendored-openssl"] } thiserror = { workspace = true } diff --git a/tpm/Cargo.toml b/tpm/Cargo.toml index 076be121ef..ab257ec189 100644 --- a/tpm/Cargo.toml +++ b/tpm/Cargo.toml @@ -7,7 +7,6 @@ version = "0.1.0" [dependencies] anyhow = "1.0.81" -byteorder = "1.5.0" libc = "0.2.153" log = "0.4.21" net_gen = { path = "../net_gen" } diff --git a/vhost_user_block/Cargo.toml b/vhost_user_block/Cargo.toml index d9aa7e105d..0648d13be5 100644 --- a/vhost_user_block/Cargo.toml +++ b/vhost_user_block/Cargo.toml @@ -9,7 +9,6 @@ version = "0.1.0" block = { path = "../block" } clap = { version = "4.5.13", features = ["cargo", "wrap_help"] } env_logger = { workspace = true } -epoll = "4.3.3" libc = "0.2.167" log = "0.4.22" option_parser = { path = "../option_parser" } diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index 73eaec803c..a4c70d111f 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -12,7 +12,6 @@ sev_snp = ["mshv-ioctls"] [dependencies] anyhow = "1.0.94" -arc-swap = "1.7.1" block = { path = "../block" } byteorder = "1.5.0" epoll = "4.3.3" @@ -20,13 +19,11 @@ event_monitor = { path = "../event_monitor" } libc = "0.2.167" log = "0.4.22" mshv-ioctls = { workspace = true, optional = true } -net_gen = { path = "../net_gen" } net_util = { path = "../net_util" } pci = { path = "../pci" } rate_limiter = { path = "../rate_limiter" } seccompiler = { workspace = true } serde = { version = "1.0.208", features = ["derive"] } -serde_json = { workspace = true } serde_with = { version = "3.9.0", default-features = false, features = [ "macros", ] } diff --git a/vm-device/Cargo.toml b/vm-device/Cargo.toml index cc24dc4764..8262f84b00 100644 --- a/vm-device/Cargo.toml +++ b/vm-device/Cargo.toml @@ -10,10 +10,8 @@ kvm = ["vfio-ioctls/kvm"] mshv = ["vfio-ioctls/mshv"] [dependencies] -anyhow = "1.0.94" hypervisor = { path = "../hypervisor" } serde = { version = "1.0.208", features = ["derive", "rc"] } thiserror = { workspace = true } vfio-ioctls = { workspace = true, default-features = false } -vm-memory = { workspace = true, features = ["backend-mmap"] } vmm-sys-util = { workspace = true } diff --git a/vm-virtio/Cargo.toml b/vm-virtio/Cargo.toml index 7a5492430e..b22a2f5551 100644 --- a/vm-virtio/Cargo.toml +++ b/vm-virtio/Cargo.toml @@ -8,7 +8,6 @@ version = "0.1.0" default = [] [dependencies] -log = "0.4.22" virtio-queue = { workspace = true } vm-memory = { workspace = true, features = [ "backend-atomic", diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 968341eedb..b28946f320 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -34,7 +34,6 @@ tracing = ["tracer/tracing"] [dependencies] acpi_tables = { workspace = true } anyhow = "1.0.94" -arc-swap = "1.7.1" arch = { path = "../arch" } bitflags = "2.9.0" block = { path = "../block" } @@ -79,7 +78,6 @@ vfio-ioctls = { workspace = true, default-features = false } vfio_user = { workspace = true } virtio-bindings = { workspace = true } virtio-devices = { path = "../virtio-devices" } -virtio-queue = { workspace = true } vm-allocator = { path = "../vm-allocator" } vm-device = { path = "../vm-device" } vm-memory = { workspace = true, features = [ From cea708deb92c5c277f42df75a05840191cfeb67c Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Wed, 16 Jul 2025 17:17:09 +0000 Subject: [PATCH 0023/1893] performance-metrics: Fix the names of the kernels In 2b0575371693, the names of the reference kernels are changed. Signed-off-by: Wei Liu --- performance-metrics/src/performance_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 7bbecf8898..a2d7004900 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -68,9 +68,9 @@ fn direct_kernel_boot_path() -> PathBuf { let mut kernel_path = workload_path; #[cfg(target_arch = "x86_64")] - kernel_path.push("vmlinux"); + kernel_path.push("vmlinux-x86_64"); #[cfg(target_arch = "aarch64")] - kernel_path.push("Image"); + kernel_path.push("Image-arm64"); kernel_path } From 987ad11c90d9dcc6189d61f7e2517d84eb70668a Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Wed, 16 Jul 2025 22:27:05 +0000 Subject: [PATCH 0024/1893] main: Report errors with 'error!()' This was missed from #7183, likely because `eprint!` is used instead of `eprintln!`. Signed-off-by: Bo Chen --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 355f0a9cfe..b8065083de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,12 +19,12 @@ pub fn cli_print_error_chain<'a>( &'a (dyn Error + 'static), ) -> Option, ) { - eprint!("Error: {component} exited with the following "); + let msg = format!("Error: {component} exited with the following"); if top_error.source().is_none() { - error!("error:"); + error!("{msg} error:"); error!(" {top_error}"); } else { - error!("chain of errors:"); + error!("{msg} chain of errors:"); std::iter::successors(Some(top_error), |sub_error| { // Dereference necessary to mitigate rustc compiler bug. // See From 4528e2f1eaaabb60aa027b5619072676b21e571d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Brucker Date: Tue, 15 Jul 2025 17:36:57 +0100 Subject: [PATCH 0025/1893] devices: rtc_pl031: Disable broken interrupt The PL031 RTC provides two features: a real-time counter and an alarm interrupt. To use the alarm, the driver normally writes a time value into the match register RTCMR, and when the counter reaches that value the device triggers the interrupt. At the moment the implementation ignores programming of the alarm, as the feature seems rarely used in VMs. However the interrupt is still triggered arbitrarily when the guest writes to registers, and the line is never cleared. This really confuses the Linux driver, which loops in the interrupt handler until Linux realizes that no one is dealing with the interrupt (200000 unanswered calls) and disables the handler. One way to fix this would be implementing the alarm function properly, which isn't too difficult but requires adding some async timer logic which probably won't ever get used. In addition the device's interrupt is level-triggered and we don't support level interrupts at the moment, though we could probably get away with changing this interrupt to edge. The simplest fix, though, is to just disable the interrupt logic entirely, so that the alarm function still doesn't work but the guest doesn't see spurious interrupts. Add a default() implementation to satisfy clippy's new_without_default check, since Rtc::new() doesn't take a parameter after this change. Signed-off-by: Jean-Philippe Brucker --- devices/src/legacy/rtc_pl031.rs | 102 +++++++------------------------- vmm/src/device_manager.rs | 8 +-- 2 files changed, 24 insertions(+), 86 deletions(-) diff --git a/devices/src/legacy/rtc_pl031.rs b/devices/src/legacy/rtc_pl031.rs index 39c7911eed..98bca77af2 100644 --- a/devices/src/legacy/rtc_pl031.rs +++ b/devices/src/legacy/rtc_pl031.rs @@ -4,16 +4,18 @@ //! ARM PL031 Real Time Clock //! -//! This module implements a PL031 Real Time Clock (RTC) that provides to provides long time base counter. -//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of -//! a real-time clock input. +//! This module implements part of a PL031 Real Time Clock (RTC): +//! * provide a clock value via RTCDR +//! * no alarm is implemented through the match register +//! * no interrupt is generated +//! * RTC cannot be disabled via RTCCR +//! * no test registers //! +use std::result; use std::sync::{Arc, Barrier}; use std::time::Instant; -use std::{io, result}; use thiserror::Error; -use vm_device::interrupt::InterruptSourceGroup; use vm_device::BusDevice; use crate::{read_le_u32, write_le_u32}; @@ -45,8 +47,6 @@ pub const NANOS_PER_SECOND: u64 = 1_000_000_000; pub enum Error { #[error("Bad Write Offset: {0}")] BadWriteOffset(u64), - #[error("Failed to trigger interrupt")] - InterruptFailure(#[source] io::Error), } type Result = result::Result; @@ -107,31 +107,20 @@ pub struct Rtc { match_value: u32, // Writes to this register load an update value into the RTC. load: u32, - imsc: u32, - ris: u32, - interrupt: Arc, } impl Rtc { /// Constructs an AMBA PL031 RTC device. - pub fn new(interrupt: Arc) -> Self { + pub fn new() -> Self { Self { // This is used only for duration measuring purposes. previous_now: Instant::now(), tick_offset: get_time(ClockType::Real) as i64, match_value: 0, load: 0, - imsc: 0, - ris: 0, - interrupt, } } - fn trigger_interrupt(&mut self) -> Result<()> { - self.interrupt.trigger(0).map_err(Error::InterruptFailure)?; - Ok(()) - } - fn get_time(&self) -> u32 { let ts = (self.tick_offset as i128) + (Instant::now().duration_since(self.previous_now).as_nanos() as i128); @@ -155,16 +144,8 @@ impl Rtc { // we want to terminate the execution of the process. self.tick_offset = seconds_to_nanoseconds(i64::from(val)).unwrap(); } - RTCIMSC => { - self.imsc = val & 1; - self.trigger_interrupt()?; - } - RTCICR => { - // As per above mentioned doc, the interrupt is cleared by writing any data value to - // the Interrupt Clear Register. - self.ris = 0; - self.trigger_interrupt()?; - } + RTCIMSC => (), + RTCICR => (), RTCCR => (), // ignore attempts to turn off the timer. o => { return Err(Error::BadWriteOffset(o)); @@ -174,6 +155,12 @@ impl Rtc { } } +impl Default for Rtc { + fn default() -> Self { + Self::new() + } +} + impl BusDevice for Rtc { fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { let mut read_ok = true; @@ -189,10 +176,10 @@ impl BusDevice for Rtc { self.match_value } RTCLR => self.load, - RTCCR => 1, // RTC is always enabled. - RTCIMSC => self.imsc, - RTCRIS => self.ris, - RTCMIS => self.ris & self.imsc, + RTCCR => 1, // RTC is always enabled. + RTCIMSC => 0, // Interrupt is always disabled. + RTCRIS => 0, + RTCMIS => 0, _ => { read_ok = false; 0 @@ -230,9 +217,6 @@ impl BusDevice for Rtc { #[cfg(test)] mod tests { - use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig}; - use vmm_sys_util::eventfd::EventFd; - use super::*; use crate::{ read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16, @@ -366,45 +350,9 @@ mod tests { assert!(seconds_to_nanoseconds(9_223_372_037).is_none()); } - struct TestInterrupt { - event_fd: EventFd, - } - - impl InterruptSourceGroup for TestInterrupt { - fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> { - self.event_fd.write(1) - } - - fn update( - &self, - _index: InterruptIndex, - _config: InterruptSourceConfig, - _masked: bool, - _set_gsi: bool, - ) -> result::Result<(), std::io::Error> { - Ok(()) - } - - fn set_gsi(&self) -> result::Result<(), std::io::Error> { - Ok(()) - } - - fn notifier(&self, _index: InterruptIndex) -> Option { - Some(self.event_fd.try_clone().unwrap()) - } - } - - impl TestInterrupt { - fn new(event_fd: EventFd) -> Self { - TestInterrupt { event_fd } - } - } - #[test] fn test_rtc_read_write_and_event() { - let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); - - let mut rtc = Rtc::new(Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))); + let mut rtc = Rtc::new(); let mut data = [0; 4]; // Read and write to the MR register. @@ -427,15 +375,13 @@ mod tests { assert_eq!((v / NANOS_PER_SECOND) as u32, v_read); // Read and write to IMSC register. - // Test with non zero value. + // Test with non zero value. Our device ignores the write. let non_zero = 1; write_le_u32(&mut data, non_zero); rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &data); - // The interrupt line should be on. - assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() == 1); rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data); let v = read_le_u32(&data); - assert_eq!(non_zero & 1, v); + assert_eq!(0, v); // Now test with 0. write_le_u32(&mut data, 0); @@ -447,8 +393,6 @@ mod tests { // Read and write to the ICR register. write_le_u32(&mut data, 1); rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCICR, &data); - // The interrupt line should be on. - assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() > 1); let v_before = read_le_u32(&data); rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index fccdd75bfa..2df6f1d585 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1931,13 +1931,7 @@ impl DeviceManager { .allocate_irq() .unwrap(); - let interrupt_group = interrupt_manager - .create_group(LegacyIrqGroupConfig { - irq: rtc_irq as InterruptIndex, - }) - .map_err(DeviceManagerError::CreateInterruptGroup)?; - - let rtc_device = Arc::new(Mutex::new(devices::legacy::Rtc::new(interrupt_group))); + let rtc_device = Arc::new(Mutex::new(devices::legacy::Rtc::new())); self.bus_devices .push(Arc::clone(&rtc_device) as Arc); From 4ea40b4beac71180588a37de5f4674ebcca9f31e Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 17 Jul 2025 16:59:06 +0000 Subject: [PATCH 0026/1893] rate_limiter: Use Mutex::get_mut() in update_buckets There is no need to lock. That function already holds a mutable reference to self. Signed-off-by: Wei Liu --- rate_limiter/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rate_limiter/src/lib.rs b/rate_limiter/src/lib.rs index 4202bfd1a1..9d67993386 100644 --- a/rate_limiter/src/lib.rs +++ b/rate_limiter/src/lib.rs @@ -486,7 +486,7 @@ impl RateLimiter { /// Updates the parameters of the token buckets associated with this RateLimiter. // TODO: Please note that, right now, the buckets become full after being updated. pub fn update_buckets(&mut self, bytes: BucketUpdate, ops: BucketUpdate) { - let mut guard = self.inner.lock().unwrap(); + let guard = self.inner.get_mut().unwrap(); match bytes { BucketUpdate::Disabled => guard.bandwidth = None, BucketUpdate::Update(tb) => guard.bandwidth = Some(tb), From 5716af09a5abaefdce4b7e973cd9faa37099a7b9 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 17 Jul 2025 17:03:01 +0000 Subject: [PATCH 0027/1893] vhost_user_block: Use Mutex::get_mut() where possible Signed-off-by: Wei Liu --- vhost_user_block/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 040eb18353..5e668c0c5a 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -334,8 +334,8 @@ impl VhostUserBackendMut for VhostUserBlkBackend { } fn set_event_idx(&mut self, enabled: bool) { - for thread in self.threads.iter() { - thread.lock().unwrap().event_idx = enabled; + for thread in self.threads.iter_mut() { + thread.get_mut().unwrap().event_idx = enabled; } } @@ -352,7 +352,7 @@ impl VhostUserBackendMut for VhostUserBlkBackend { debug!("event received: {:?}", device_event); - let mut thread = self.threads[thread_id].lock().unwrap(); + let thread = self.threads[thread_id].get_mut().unwrap(); match device_event { 0 => { let mut vring = vrings[0].get_mut(); From 4be2ca4c10ba388a3798c08d100bf172fe6e4e6d Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 17 Jul 2025 17:06:21 +0000 Subject: [PATCH 0028/1893] vhost_user_net: Use Mutex::get_mut() where possible Signed-off-by: Wei Liu --- vhost_user_net/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vhost_user_net/src/lib.rs b/vhost_user_net/src/lib.rs index 56b54ad6d8..de2a416aa0 100644 --- a/vhost_user_net/src/lib.rs +++ b/vhost_user_net/src/lib.rs @@ -205,7 +205,7 @@ impl VhostUserBackendMut for VhostUserNetBackend { vrings: &[VringRwLock>], thread_id: usize, ) -> VhostUserBackendResult<()> { - let mut thread = self.threads[thread_id].lock().unwrap(); + let thread = self.threads[thread_id].get_mut().unwrap(); match device_event { 0 => { if !thread.net.rx_tap_listening { From 62001b65e9d165e489b60d1e5a17e3db22142f30 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Wed, 16 Jul 2025 23:10:21 +0000 Subject: [PATCH 0029/1893] build: Release v47.0 Signed-off-by: Bo Chen --- Cargo.lock | 2 +- Cargo.toml | 2 +- release-notes.md | 237 ++++++++++++++++++++++++++++++----------------- 3 files changed, 154 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e239b3092..305e31cee0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,7 +401,7 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cloud-hypervisor" -version = "46.0.0" +version = "47.0.0" dependencies = [ "anyhow", "api_client", diff --git a/Cargo.toml b/Cargo.toml index f46fd602e0..f05de398ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" license = "Apache-2.0 AND BSD-3-Clause" name = "cloud-hypervisor" -version = "46.0.0" +version = "47.0.0" # Minimum buildable version: # Keep in sync with version in .github/workflows/build.yaml # Policy on MSRV (see #4318): diff --git a/release-notes.md b/release-notes.md index 559066feb4..0eb024c966 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,50 +1,57 @@ +- [v47.0](#v470) + - [Block Device Error Reporting to the Guest](#block-device-error-reporting-to-the-guest) + - [Nice Error Messages on Exit](#nice-error-messages-on-exit) + - [Alphabetically Sorted CLI Options for ch-remote](#alphabetically-sorted-cli-options-for-ch-remote) + - [Notable Bug Fixes](#notable-bug-fixes) + - [Deprecations](#deprecations) + - [Contributors](#contributors) - [v46.0](#v460) - [File-level Locking Support with `--disk`](#file-level-locking-support-with---disk) - [Improved Error Reporting with VM Resizing](#improved-error-reporting-with-vm-resizing) - [IPv6 Address Support with `--net`](#ipv6-address-support-with---net) - [Experimental AArch64 Support with the MSHV Hypervisor](#experimental-aarch64-support-with-the-mshv-hypervisor) - [Deprecated SGX Support](#deprecated-sgx-support) - - [Notable Bug Fixes](#notable-bug-fixes) - - [Contributors](#contributors) + - [Notable Bug Fixes](#notable-bug-fixes-1) + - [Contributors](#contributors-1) - [v45.0](#v450) - [Experimental `riscv64` Architecture Support](#experimental-riscv64-architecture-support) - [Alphabetically Sorted CLI Options](#alphabetically-sorted-cli-options) - [Improved Downtime of VM Live Migration](#improved-downtime-of-vm-live-migration) - - [Notable Bug Fixes](#notable-bug-fixes-1) - - [Contributors](#contributors-1) + - [Notable Bug Fixes](#notable-bug-fixes-2) + - [Contributors](#contributors-2) - [v44.0](#v440) - [Configurable `virtio-iommu` Address Width](#configurable-virtio-iommu-address-width) - [Notable Performance Improvements](#notable-performance-improvements) - [New Fuzzers](#new-fuzzers) - - [Notable Bug Fixes](#notable-bug-fixes-2) - - [Contributors](#contributors-2) + - [Notable Bug Fixes](#notable-bug-fixes-3) + - [Contributors](#contributors-3) - [v43.0](#v430) - [Live Migration over TCP Connections](#live-migration-over-tcp-connections) - [Notable Performance Improvements](#notable-performance-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-3) - - [Contributors](#contributors-3) + - [Notable Bug Fixes](#notable-bug-fixes-4) + - [Contributors](#contributors-4) - [v42.0](#v420) - [SVE/SVE2 Support on AArch64](#svesve2-support-on-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-4) + - [Notable Bug Fixes](#notable-bug-fixes-5) - [Sponsorships](#sponsorships) - - [Contributors](#contributors-4) + - [Contributors](#contributors-5) - [v41.0](#v410) - [Experimental "Pvmemcontrol" Support](#experimental-pvmemcontrol-support) - [Sandboxing With Landlock Support](#sandboxing-with-landlock-support) - [Notable Performance Improvements](#notable-performance-improvements-2) - - [Notable Bug Fixes](#notable-bug-fixes-5) - - [Contributors](#contributors-5) -- [v40.0](#v400) - - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) - [Notable Bug Fixes](#notable-bug-fixes-6) - [Contributors](#contributors-6) +- [v40.0](#v400) + - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) + - [Notable Bug Fixes](#notable-bug-fixes-7) + - [Contributors](#contributors-7) - [v39.0](#v390) - [Variable Sizing of PCI Apertures for Segments](#variable-sizing-of-pci-apertures-for-segments) - [Direct Booting with bzImages](#direct-booting-with-bzimages) - [Support for NVIDIA GPUDirect P2P Support](#support-for-nvidia-gpudirect-p2p-support) - [Guest NMI Injection Support](#guest-nmi-injection-support) - - [Notable Bug Fixes](#notable-bug-fixes-7) - - [Contributors](#contributors-7) + - [Notable Bug Fixes](#notable-bug-fixes-8) + - [Contributors](#contributors-8) - [v38.0](#v380) - [Group Rate Limiter on Block Devices](#group-rate-limiter-on-block-devices) - [CPU Pinning Support for Block Device Worker Thread](#cpu-pinning-support-for-block-device-worker-thread) @@ -52,16 +59,16 @@ - [New 'debug-console' Device](#new-debug-console-device) - [Improved VFIO Device Support](#improved-vfio-device-support) - [Extended CPU Affinity Support](#extended-cpu-affinity-support) - - [Notable Bug Fixes](#notable-bug-fixes-8) - - [Contributors](#contributors-8) + - [Notable Bug Fixes](#notable-bug-fixes-9) + - [Contributors](#contributors-9) - [v37.0](#v370) - [Long Term Support (LTS) Release](#long-term-support-lts-release) - [Multiple PCI segments Support for 32-bit VFIO devices](#multiple-pci-segments-support-for-32-bit-vfio-devices) - [Configurable Named TAP Devices](#configurable-named-tap-devices) - [TTY Output from Both Serial Device and Virtio Console](#tty-output-from-both-serial-device-and-virtio-console) - [Faster VM Restoration from Snapshots](#faster-vm-restoration-from-snapshots) - - [Notable Bug Fixes](#notable-bug-fixes-9) - - [Contributors](#contributors-9) + - [Notable Bug Fixes](#notable-bug-fixes-10) + - [Contributors](#contributors-10) - [v36.0](#v360) - [Command Line Changes](#command-line-changes) - [Enabled Features Reported via API Endpoint and CLI](#enabled-features-reported-via-api-endpoint-and-cli) @@ -70,31 +77,31 @@ - [Unix Socket Backend for Serial Port](#unix-socket-backend-for-serial-port) - [AIO Backend for Block Devices](#aio-backend-for-block-devices) - [Documentation Improvements](#documentation-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-10) - - [Contributors](#contributors-10) + - [Notable Bug Fixes](#notable-bug-fixes-11) + - [Contributors](#contributors-11) - [v35.0](#v350) - [`virtio-vsock` Support for Linux Guest Kernel v6.3+](#virtio-vsock-support-for-linux-guest-kernel-v63) - [User Specified Serial Number for `virtio-block`](#user-specified-serial-number-for-virtio-block) - [vCPU TSC Frequency Included in Migration State](#vcpu-tsc-frequency-included-in-migration-state) - - [Notable Bug Fixes](#notable-bug-fixes-11) - - [Contributors](#contributors-11) + - [Notable Bug Fixes](#notable-bug-fixes-12) + - [Contributors](#contributors-12) - [v34.0](#v340) - [Paravirtualised Panic Device Support](#paravirtualised-panic-device-support) - [Improvements to VM Core Dump](#improvements-to-vm-core-dump) - [QCOW2 Support for Backing Files](#qcow2-support-for-backing-files) - [Minimum Host Kernel Bump](#minimum-host-kernel-bump) - - [Notable Bug Fixes](#notable-bug-fixes-12) - - [Contributors](#contributors-12) + - [Notable Bug Fixes](#notable-bug-fixes-13) + - [Contributors](#contributors-13) - [v33.0](#v330) - [D-Bus based API](#d-bus-based-api) - [Expose Host CPU Cache Details for AArch64](#expose-host-cpu-cache-details-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-13) - - [Contributors](#contributors-13) + - [Notable Bug Fixes](#notable-bug-fixes-14) + - [Contributors](#contributors-14) - [v32.0](#v320) - [Increased PCI Segment Limit](#increased-pci-segment-limit) - [API Changes](#api-changes) - - [Notable Bug Fixes](#notable-bug-fixes-14) - - [Contributors](#contributors-14) + - [Notable Bug Fixes](#notable-bug-fixes-15) + - [Contributors](#contributors-15) - [v31.1](#v311) - [v31.0](#v310) - [Update to Latest `acpi_tables`](#update-to-latest-acpi_tables) @@ -102,15 +109,15 @@ - [Improvements on Console `SIGWINCH` Handler](#improvements-on-console-sigwinch-handler) - [Remove Directory Support from `MemoryZoneConfig::file`](#remove-directory-support-from-memoryzoneconfigfile) - [Documentation Improvements](#documentation-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-15) - - [Contributors](#contributors-15) + - [Notable Bug Fixes](#notable-bug-fixes-16) + - [Contributors](#contributors-16) - [v30.0](#v300) - [Command Line Changes for Reduced Binary Size](#command-line-changes-for-reduced-binary-size) - [Basic vfio-user Server Support](#basic-vfio-user-server-support) - [Heap Profiling Support](#heap-profiling-support) - [Documentation Improvements](#documentation-improvements-2) - - [Notable Bug Fixes](#notable-bug-fixes-16) - - [Contributors](#contributors-16) + - [Notable Bug Fixes](#notable-bug-fixes-17) + - [Contributors](#contributors-17) - [v28.2](#v282) - [v29.0](#v290) - [Release Binary Supports Both MSHV and KVM](#release-binary-supports-both-mshv-and-kvm) @@ -120,10 +127,10 @@ - [`AArch64` Documentation Integration](#aarch64-documentation-integration) - [`virtio-block` Counters Enhancement](#virtio-block-counters-enhancement) - [TCP Offload Control](#tcp-offload-control) - - [Notable Bug Fixes](#notable-bug-fixes-17) + - [Notable Bug Fixes](#notable-bug-fixes-18) - [Removals](#removals) - - [Deprecations](#deprecations) - - [Contributors](#contributors-17) + - [Deprecations](#deprecations-1) + - [Contributors](#contributors-18) - [v28.1](#v281) - [v28.0](#v280) - [Community Engagement (Reminder)](#community-engagement-reminder) @@ -131,9 +138,9 @@ - [Virtualised TPM Support](#virtualised-tpm-support) - [Transparent Huge Page Support](#transparent-huge-page-support) - [README Quick Start Improved](#readme-quick-start-improved) - - [Notable Bug Fixes](#notable-bug-fixes-18) + - [Notable Bug Fixes](#notable-bug-fixes-19) - [Removals](#removals-1) - - [Contributors](#contributors-18) + - [Contributors](#contributors-19) - [v27.0](#v270) - [Community Engagement](#community-engagement) - [Prebuilt Packages](#prebuilt-packages) @@ -142,41 +149,41 @@ - [Simplified Build Feature Flags](#simplified-build-feature-flags) - [Asynchronous Kernel Loading](#asynchronous-kernel-loading) - [GDB Support for AArch64](#gdb-support-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-19) - - [Deprecations](#deprecations-1) - - [Contributors](#contributors-19) + - [Notable Bug Fixes](#notable-bug-fixes-20) + - [Deprecations](#deprecations-2) + - [Contributors](#contributors-20) - [v26.0](#v260) - [SMBIOS Improvements via `--platform`](#smbios-improvements-via---platform) - [Unified Binary MSHV and KVM Support](#unified-binary-mshv-and-kvm-support) - - [Notable Bug Fixes](#notable-bug-fixes-20) - - [Deprecations](#deprecations-2) + - [Notable Bug Fixes](#notable-bug-fixes-21) + - [Deprecations](#deprecations-3) - [Removals](#removals-2) - - [Contributors](#contributors-20) + - [Contributors](#contributors-21) - [v25.0](#v250) - [`ch-remote` Improvements](#ch-remote-improvements-1) - [VM "Coredump" Support](#vm-coredump-support) - - [Notable Bug Fixes](#notable-bug-fixes-21) + - [Notable Bug Fixes](#notable-bug-fixes-22) - [Removals](#removals-3) - - [Contributors](#contributors-21) + - [Contributors](#contributors-22) - [v24.0](#v240) - [Bypass Mode for `virtio-iommu`](#bypass-mode-for-virtio-iommu) - [Ensure Identifiers Uniqueness](#ensure-identifiers-uniqueness) - [Sparse Mmap support](#sparse-mmap-support) - [Expose Platform Serial Number](#expose-platform-serial-number) - - [Notable Bug Fixes](#notable-bug-fixes-22) + - [Notable Bug Fixes](#notable-bug-fixes-23) - [Notable Improvements](#notable-improvements) - - [Deprecations](#deprecations-3) + - [Deprecations](#deprecations-4) - [New on the Website](#new-on-the-website) - - [Contributors](#contributors-22) + - [Contributors](#contributors-23) - [v23.1](#v231) - [v23.0](#v230) - [vDPA Support](#vdpa-support) - [Updated OS Support list](#updated-os-support-list) - [`AArch64` Memory Map Improvements](#aarch64-memory-map-improvements) - [`AMX` Support](#amx-support) - - [Notable Bug Fixes](#notable-bug-fixes-23) - - [Deprecations](#deprecations-4) - - [Contributors](#contributors-23) + - [Notable Bug Fixes](#notable-bug-fixes-24) + - [Deprecations](#deprecations-5) + - [Contributors](#contributors-24) - [v22.1](#v221) - [v22.0](#v220) - [GDB Debug Stub Support](#gdb-debug-stub-support) @@ -187,13 +194,13 @@ - [PMU Support for AArch64](#pmu-support-for-aarch64) - [Documentation Under CC-BY-4.0 License](#documentation-under-cc-by-40-license) - [Deprecation of "Classic" `virtiofsd`](#deprecation-of-classic-virtiofsd) - - [Notable Bug Fixes](#notable-bug-fixes-24) - - [Contributors](#contributors-24) + - [Notable Bug Fixes](#notable-bug-fixes-25) + - [Contributors](#contributors-25) - [v21.0](#v210) - [Efficient Local Live Migration (for Live Upgrade)](#efficient-local-live-migration-for-live-upgrade) - [Recommended Kernel is Now 5.15](#recommended-kernel-is-now-515) - - [Notable Bug fixes](#notable-bug-fixes-25) - - [Contributors](#contributors-25) + - [Notable Bug fixes](#notable-bug-fixes-26) + - [Contributors](#contributors-26) - [v20.2](#v202) - [v20.1](#v201) - [v20.0](#v200) @@ -202,8 +209,8 @@ - [Improved VFIO support](#improved-vfio-support) - [Safer code](#safer-code) - [Extended documentation](#extended-documentation) - - [Notable bug fixes](#notable-bug-fixes-26) - - [Contributors](#contributors-26) + - [Notable bug fixes](#notable-bug-fixes-27) + - [Contributors](#contributors-27) - [v19.0](#v190) - [Improved PTY handling for serial and `virtio-console`](#improved-pty-handling-for-serial-and-virtio-console) - [PCI boot time optimisations](#pci-boot-time-optimisations) @@ -211,8 +218,8 @@ - [Live migration enhancements](#live-migration-enhancements) - [`virtio-mem` support with `vfio-user`](#virtio-mem-support-with-vfio-user) - [AArch64 for `virtio-iommu`](#aarch64-for-virtio-iommu) - - [Notable bug fixes](#notable-bug-fixes-27) - - [Contributors](#contributors-27) + - [Notable bug fixes](#notable-bug-fixes-28) + - [Contributors](#contributors-28) - [v18.0](#v180) - [Experimental User Device (`vfio-user`) support](#experimental-user-device-vfio-user-support) - [Migration support for `vhost-user` devices](#migration-support-for-vhost-user-devices) @@ -222,31 +229,31 @@ - [Live migration on MSHV hypervisor](#live-migration-on-mshv-hypervisor) - [AArch64 CPU topology support](#aarch64-cpu-topology-support) - [Power button support on AArch64](#power-button-support-on-aarch64) - - [Notable bug fixes](#notable-bug-fixes-28) - - [Contributors](#contributors-28) + - [Notable bug fixes](#notable-bug-fixes-29) + - [Contributors](#contributors-29) - [v17.0](#v170) - [ARM64 NUMA support using ACPI](#arm64-numa-support-using-acpi) - [`Seccomp` support for MSHV backend](#seccomp-support-for-mshv-backend) - [Hotplug of `macvtap` devices](#hotplug-of-macvtap-devices) - [Improved SGX support](#improved-sgx-support) - [Inflight tracking for `vhost-user` devices](#inflight-tracking-for-vhost-user-devices) - - [Notable bug fixes](#notable-bug-fixes-29) - - [Contributors](#contributors-29) + - [Notable bug fixes](#notable-bug-fixes-30) + - [Contributors](#contributors-30) - [v16.0](#v160) - [Improved live migration support](#improved-live-migration-support) - [Improved `vhost-user` support](#improved-vhost-user-support) - [ARM64 ACPI and UEFI support](#arm64-acpi-and-uefi-support) - - [Notable bug fixes](#notable-bug-fixes-30) + - [Notable bug fixes](#notable-bug-fixes-31) - [Removed functionality](#removed-functionality) - - [Contributors](#contributors-30) + - [Contributors](#contributors-31) - [v15.0](#v150) - [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees) - [Network device rate limiting](#network-device-rate-limiting) - [Support for runtime control of `virtio-net` guest offload](#support-for-runtime-control-of-virtio-net-guest-offload) - [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter) - [Bug fixes](#bug-fixes) - - [Deprecations](#deprecations-5) - - [Contributors](#contributors-31) + - [Deprecations](#deprecations-6) + - [Contributors](#contributors-32) - [v0.14.1](#v0141) - [v0.14.0](#v0140) - [Structured event monitoring](#structured-event-monitoring) @@ -255,8 +262,8 @@ - [Updated hotplug documentation](#updated-hotplug-documentation) - [PTY control for serial and `virtio-console`](#pty-control-for-serial-and-virtio-console) - [Block device rate limiting](#block-device-rate-limiting) - - [Deprecations](#deprecations-6) - - [Contributors](#contributors-32) + - [Deprecations](#deprecations-7) + - [Contributors](#contributors-33) - [v0.13.0](#v0130) - [Wider VFIO device support](#wider-vfio-device-support) - [Improved huge page support](#improved-huge-page-support) @@ -264,13 +271,13 @@ - [VHD disk image support](#vhd-disk-image-support) - [Improved Virtio device threading](#improved-virtio-device-threading) - [Clean shutdown support via synthetic power button](#clean-shutdown-support-via-synthetic-power-button) - - [Contributors](#contributors-33) + - [Contributors](#contributors-34) - [v0.12.0](#v0120) - [ARM64 enhancements](#arm64-enhancements) - [Removal of `vhost-user-net` and `vhost-user-block` self spawning](#removal-of-vhost-user-net-and-vhost-user-block-self-spawning) - [Migration of `vhost-user-fs` backend](#migration-of-vhost-user-fs-backend) - [Enhanced "info" API](#enhanced-info-api) - - [Contributors](#contributors-34) + - [Contributors](#contributors-35) - [v0.11.0](#v0110) - [`io_uring` support by default for `virtio-block`](#io_uring-support-by-default-for-virtio-block) - [Windows Guest Support](#windows-guest-support) @@ -282,15 +289,15 @@ - [Default Log Level Changed](#default-log-level-changed) - [New `--balloon` Parameter Added](#new---balloon-parameter-added) - [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support) - - [Notable Bug Fixes](#notable-bug-fixes-31) - - [Contributors](#contributors-35) + - [Notable Bug Fixes](#notable-bug-fixes-32) + - [Contributors](#contributors-36) - [v0.10.0](#v0100) - [`virtio-block` Support for Multiple Descriptors](#virtio-block-support-for-multiple-descriptors) - [Memory Zones](#memory-zones) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements) - [Preliminary KVM HyperV Emulation Control](#preliminary-kvm-hyperv-emulation-control) - - [Notable Bug Fixes](#notable-bug-fixes-32) - - [Contributors](#contributors-36) + - [Notable Bug Fixes](#notable-bug-fixes-33) + - [Contributors](#contributors-37) - [v0.9.0](#v090) - [`io_uring` Based Block Device Support](#io_uring-based-block-device-support) - [Block and Network Device Statistics](#block-and-network-device-statistics) @@ -303,17 +310,17 @@ - [Enhancements to ARM64 Support](#enhancements-to-arm64-support) - [Intel SGX Support](#intel-sgx-support) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-33) - - [Contributors](#contributors-37) + - [Notable Bug Fixes](#notable-bug-fixes-34) + - [Contributors](#contributors-38) - [v0.8.0](#v080) - [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support) - [Experimental ARM64 Support](#experimental-arm64-support) - [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests) - [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices) - [`vhost_user_fs` Improvements](#vhost_user_fs-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-34) + - [Notable Bug Fixes](#notable-bug-fixes-35) - [Command Line and API Changes](#command-line-and-api-changes) - - [Contributors](#contributors-38) + - [Contributors](#contributors-39) - [v0.7.0](#v070) - [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug) - [Alternative `libc` Support](#alternative-libc-support) @@ -323,14 +330,14 @@ - [`Seccomp` Sandboxing](#seccomp-sandboxing) - [Updated Distribution Support](#updated-distribution-support) - [Command Line and API Changes](#command-line-and-api-changes-1) - - [Contributors](#contributors-39) + - [Contributors](#contributors-40) - [v0.6.0](#v060) - [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug) - [Shared Filesystem Improvements](#shared-filesystem-improvements) - [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading) - [Command Line Interface](#command-line-interface) - [PVH Boot](#pvh-boot) - - [Contributors](#contributors-40) + - [Contributors](#contributors-41) - [v0.5.1](#v051) - [v0.5.0](#v050) - [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing) @@ -338,7 +345,7 @@ - [New Interrupt Management Framework](#new-interrupt-management-framework) - [Development Tools](#development-tools) - [Kata Containers Integration](#kata-containers-integration) - - [Contributors](#contributors-41) + - [Contributors](#contributors-42) - [v0.4.0](#v040) - [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition) - [Programmatic firmware tables generation](#programmatic-firmware-tables-generation) @@ -347,7 +354,7 @@ - [Userspace IOAPIC by default](#userspace-ioapic-by-default) - [PCI BAR reprogramming](#pci-bar-reprogramming) - [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization) - - [Contributors](#contributors-42) + - [Contributors](#contributors-43) - [v0.3.0](#v030) - [Block device offloading](#block-device-offloading) - [Network device backend](#network-device-backend) @@ -374,6 +381,66 @@ - [Unit testing](#unit-testing) - [Integration tests parallelization](#integration-tests-parallelization) +# v47.0 + +This release has been tracked in [v47.0 +group](https://github.com/orgs/cloud-hypervisor/projects/6/views/4?filterQuery=release%3A%22Release+47%22) +of our [roadmap project](https://github.com/orgs/cloud-hypervisor/projects/6/). + +### Block Device Error Reporting to the Guest + +Instead of exiting on I/O errors, the `virtio-block` device now reports +errors to the guest using `VIRTIO_BLK_S_IOERR`. It improves the user +experience particularly when the guest rootfs is not backed by the +affected block device. (#7107) + +### Nice Error Messages on Exit + +We now have the chain of errors being reported and printed nicely, when +Cloud Hypervisor or ch-remote exits on errors. (#7066) + +### Alphabetically Sorted CLI Options for ch-remote + +To improve readability, ch-remote now prints help information in +alphabetical order. (#7130) + +### Notable Bug Fixes + +* Error out early when block device serial is too long (#7124) +* Fix partial commands being discarded for `virtio-vsock` (#7195) +* Disable the broken interrupt support for the `rtc_pl031` device to + prevent spurious guest interrupts (#7199) + +### Deprecations + +* A default IP (`192.168.249.1`) and mask (`255.255.255.0`) are + currently assigned to the `virtio-net` device if no value is specified + by users. Such behavior is now deprecated. Users of this behavior will + receive a warning message and should make adjustments. The behavior + will be removed in two release cycles (v49.0). + +### Contributors + +Many thanks to everyone who has contributed to our release: + +* Alyssa Ross +* Bo Chen +* Demi Marie Obenour +* Gauthier Jolly +* Hengqi Chen +* Jinank Jain +* Jinrong Liang +* Jean-Philippe Brucker +* Maximilian Güntner +* Muminul Islam +* Nuno Das Neves +* Philipp Schuster +* Ruoqing He +* Songqian Li +* Wei Liu +* Yi Wang +* ninollei + # v46.0 This release has been tracked in [v46.0 From 03b22a510d90d74d02cec54404545eb1110c5b9d Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Tue, 22 Jul 2025 19:08:07 +0000 Subject: [PATCH 0030/1893] tests: Disable 'test_virtio_block_dynamic_vhdx_expand' This issue is tracked via #7209. Signed-off-by: Bo Chen --- tests/integration.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration.rs b/tests/integration.rs index 0536864fd2..49eb6f0038 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3381,6 +3381,7 @@ mod common_parallel { } #[test] + #[ignore = "See #7209"] fn test_virtio_block_dynamic_vhdx_expand() { const VIRTUAL_DISK_SIZE: u64 = 100 << 20; const EMPTY_VHDX_FILE_SIZE: u64 = 8 << 20; From 930a91186268f6e8ec60d628d0b4998bedc6172f Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 17 Jul 2025 23:15:50 +0000 Subject: [PATCH 0031/1893] block: Simplify AsyncAdaptor It shouldn't be necessary to lock the file for the adaptor. This removes two layers of indirection for QcowDiskSync and VhdxDiskSync. Signed-off-by: Wei Liu --- block/src/lib.rs | 40 +++++++++++++++++++--------------------- block/src/qcow/mod.rs | 2 +- block/src/qcow_sync.rs | 24 +++++++++--------------- block/src/vhdx_sync.rs | 20 +++++++------------- 4 files changed, 36 insertions(+), 50 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index b081c87734..aed9c0ab3c 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -39,7 +39,7 @@ use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; use std::os::linux::fs::MetadataExt; use std::os::unix::io::AsRawFd; use std::path::Path; -use std::sync::{Arc, MutexGuard}; +use std::sync::Arc; use std::time::Instant; use std::{cmp, result}; @@ -646,10 +646,7 @@ pub fn block_io_uring_is_supported() -> bool { } } -pub trait AsyncAdaptor -where - F: Read + Write + Seek, -{ +pub trait AsyncAdaptor { fn read_vectored_sync( &mut self, offset: libc::off_t, @@ -657,7 +654,10 @@ where user_data: u64, eventfd: &EventFd, completion_list: &mut VecDeque<(u64, i32)>, - ) -> AsyncIoResult<()> { + ) -> AsyncIoResult<()> + where + Self: Read + Seek, + { // Convert libc::iovec into IoSliceMut let mut slices: SmallVec<[IoSliceMut; DEFAULT_DESCRIPTOR_VEC_SIZE]> = SmallVec::with_capacity(iovecs.len()); @@ -669,15 +669,13 @@ where } let result = { - let mut file = self.file(); - // Move the cursor to the right offset - file.seek(SeekFrom::Start(offset as u64)) + self.seek(SeekFrom::Start(offset as u64)) .map_err(AsyncIoError::ReadVectored)?; let mut r = 0; for b in slices.iter_mut() { - r += file.read(b).map_err(AsyncIoError::ReadVectored)?; + r += self.read(b).map_err(AsyncIoError::ReadVectored)?; } r }; @@ -695,7 +693,10 @@ where user_data: u64, eventfd: &EventFd, completion_list: &mut VecDeque<(u64, i32)>, - ) -> AsyncIoResult<()> { + ) -> AsyncIoResult<()> + where + Self: Write + Seek, + { // Convert libc::iovec into IoSlice let mut slices: SmallVec<[IoSlice; DEFAULT_DESCRIPTOR_VEC_SIZE]> = SmallVec::with_capacity(iovecs.len()); @@ -707,15 +708,13 @@ where } let result = { - let mut file = self.file(); - // Move the cursor to the right offset - file.seek(SeekFrom::Start(offset as u64)) + self.seek(SeekFrom::Start(offset as u64)) .map_err(AsyncIoError::WriteVectored)?; let mut r = 0; for b in slices.iter() { - r += file.write(b).map_err(AsyncIoError::WriteVectored)?; + r += self.write(b).map_err(AsyncIoError::WriteVectored)?; } r }; @@ -731,12 +730,13 @@ where user_data: Option, eventfd: &EventFd, completion_list: &mut VecDeque<(u64, i32)>, - ) -> AsyncIoResult<()> { + ) -> AsyncIoResult<()> + where + Self: Write, + { let result: i32 = { - let mut file = self.file(); - // Flush - file.flush().map_err(AsyncIoError::Fsync)?; + self.flush().map_err(AsyncIoError::Fsync)?; 0 }; @@ -748,8 +748,6 @@ where Ok(()) } - - fn file(&mut self) -> MutexGuard<'_, F>; } pub enum ImageType { diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 6d74232ddf..7733ff0ceb 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -425,7 +425,7 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u /// # Ok(()) /// # } /// ``` -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct QcowFile { raw_file: QcowRawFile, header: QcowHeader, diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index f07e245e01..332db24868 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -6,7 +6,6 @@ use std::collections::VecDeque; use std::fs::File; use std::io::{Seek, SeekFrom}; use std::os::fd::AsRawFd; -use std::sync::{Arc, Mutex, MutexGuard}; use vmm_sys_util::eventfd::EventFd; @@ -17,22 +16,22 @@ use crate::qcow::{QcowFile, RawFile, Result as QcowResult}; use crate::AsyncAdaptor; pub struct QcowDiskSync { - qcow_file: Arc>, + qcow_file: QcowFile, } impl QcowDiskSync { pub fn new(file: File, direct_io: bool) -> QcowResult { Ok(QcowDiskSync { - qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)), + qcow_file: QcowFile::from(RawFile::new(file, direct_io))?, }) } } impl DiskFile for QcowDiskSync { fn size(&mut self) -> DiskFileResult { - let mut file = self.qcow_file.lock().unwrap(); - - file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size) + self.qcow_file + .seek(SeekFrom::End(0)) + .map_err(DiskFileError::Size) } fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { @@ -40,19 +39,18 @@ impl DiskFile for QcowDiskSync { } fn fd(&mut self) -> BorrowedDiskFd<'_> { - let lock = self.qcow_file.lock().unwrap(); - BorrowedDiskFd::new(lock.as_raw_fd()) + BorrowedDiskFd::new(self.qcow_file.as_raw_fd()) } } pub struct QcowSync { - qcow_file: Arc>, + qcow_file: QcowFile, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, } impl QcowSync { - pub fn new(qcow_file: Arc>) -> Self { + pub fn new(qcow_file: QcowFile) -> Self { QcowSync { qcow_file, eventfd: EventFd::new(libc::EFD_NONBLOCK) @@ -62,11 +60,7 @@ impl QcowSync { } } -impl AsyncAdaptor for Arc> { - fn file(&mut self) -> MutexGuard<'_, QcowFile> { - self.lock().unwrap() - } -} +impl AsyncAdaptor for QcowFile {} impl AsyncIo for QcowSync { fn notifier(&self) -> &EventFd { diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index d832f5e3cc..fd5888e638 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -5,7 +5,6 @@ use std::collections::VecDeque; use std::fs::File; use std::os::fd::AsRawFd; -use std::sync::{Arc, Mutex, MutexGuard}; use vmm_sys_util::eventfd::EventFd; @@ -16,20 +15,20 @@ use crate::vhdx::{Result as VhdxResult, Vhdx}; use crate::AsyncAdaptor; pub struct VhdxDiskSync { - vhdx_file: Arc>, + vhdx_file: Vhdx, } impl VhdxDiskSync { pub fn new(f: File) -> VhdxResult { Ok(VhdxDiskSync { - vhdx_file: Arc::new(Mutex::new(Vhdx::new(f)?)), + vhdx_file: Vhdx::new(f)?, }) } } impl DiskFile for VhdxDiskSync { fn size(&mut self) -> DiskFileResult { - Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) + Ok(self.vhdx_file.virtual_disk_size()) } fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { @@ -40,19 +39,18 @@ impl DiskFile for VhdxDiskSync { } fn fd(&mut self) -> BorrowedDiskFd<'_> { - let lock = self.vhdx_file.lock().unwrap(); - BorrowedDiskFd::new(lock.as_raw_fd()) + BorrowedDiskFd::new(self.vhdx_file.as_raw_fd()) } } pub struct VhdxSync { - vhdx_file: Arc>, + vhdx_file: Vhdx, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, } impl VhdxSync { - pub fn new(vhdx_file: Arc>) -> std::io::Result { + pub fn new(vhdx_file: Vhdx) -> std::io::Result { Ok(VhdxSync { vhdx_file, eventfd: EventFd::new(libc::EFD_NONBLOCK)?, @@ -61,11 +59,7 @@ impl VhdxSync { } } -impl AsyncAdaptor for Arc> { - fn file(&mut self) -> MutexGuard<'_, Vhdx> { - self.lock().unwrap() - } -} +impl AsyncAdaptor for Vhdx {} impl AsyncIo for VhdxSync { fn notifier(&self) -> &EventFd { From 6d70c570f35ce54cfabf51206f2870a8987217b3 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Tue, 22 Jul 2025 10:01:55 +0200 Subject: [PATCH 0032/1893] tpm: Use the same anyhow version as other workspace members The other workspace members in the Cloud-hypervisor workspace currently declare anyhow version 1.0.94, but the tpm crate has an older version. This inconsistency is addressed by this PR which opens the door for declaring anyhow as a workspace dependency. Signed-off-by: Oliver Anderson On-behalf-of: SAP oliver.anderson@sap.com --- tpm/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tpm/Cargo.toml b/tpm/Cargo.toml index ab257ec189..0b50049943 100644 --- a/tpm/Cargo.toml +++ b/tpm/Cargo.toml @@ -6,7 +6,7 @@ name = "tpm" version = "0.1.0" [dependencies] -anyhow = "1.0.81" +anyhow = "1.0.94" libc = "0.2.153" log = "0.4.21" net_gen = { path = "../net_gen" } From 2ae5b2567b40a782d3b18c33ca3f0da7139b4829 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 23 Jul 2025 21:01:07 +0200 Subject: [PATCH 0033/1893] tpm: Use the same libc crate version as other workspace members The other workspace members in the Cloud-hypervisor workspace currently declare libc crate version 0.2.167, but the tpm crate has an older version. This inconsistency is addressed by this PR which opens the door for declaring libc as a workspace dependency. Signed-off-by: Oliver Anderson On-behalf-of: SAP oliver.anderson@sap.com --- tpm/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tpm/Cargo.toml b/tpm/Cargo.toml index 0b50049943..21cb222d5a 100644 --- a/tpm/Cargo.toml +++ b/tpm/Cargo.toml @@ -7,7 +7,7 @@ version = "0.1.0" [dependencies] anyhow = "1.0.94" -libc = "0.2.153" +libc = "0.2.167" log = "0.4.21" net_gen = { path = "../net_gen" } thiserror = { workspace = true } From f6568042ce530d3a46ba267cd00ba2d7e7220075 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Wed, 23 Jul 2025 18:51:22 +0000 Subject: [PATCH 0034/1893] tests: Fix an error message in VHDX expansion test The VHDX image is generated directly, not converted from a RAW image. Signed-off-by: Wei Liu --- tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration.rs b/tests/integration.rs index 49eb6f0038..da2d5029c9 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3402,7 +3402,7 @@ mod common_parallel { .arg(vhdx_path) .arg(VIRTUAL_DISK_SIZE.to_string()) .output() - .expect("Expect generating dynamic VHDx image from RAW image"); + .expect("Expect generating dynamic VHDX image"); // Check if the size matches with empty VHDx file size assert_eq!(vhdx_image_size(vhdx_path), EMPTY_VHDX_FILE_SIZE); From 4cae96f0706508d8abc074f903658be220295243 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Wed, 23 Jul 2025 18:58:26 +0000 Subject: [PATCH 0035/1893] tests: Avoid races in the VHDX expansion test Generate the data disk under a temporary directory so that multiple instances of the test suites can run at the same time. Signed-off-by: Wei Liu --- tests/integration.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index da2d5029c9..62cbea9dea 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3388,12 +3388,12 @@ mod common_parallel { const FULL_VHDX_FILE_SIZE: u64 = 112 << 20; const DYNAMIC_VHDX_NAME: &str = "dynamic.vhdx"; - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); + let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(focal)); + let kernel_path = direct_kernel_boot_path(); - let mut vhdx_file_path = workload_path; - vhdx_file_path.push(DYNAMIC_VHDX_NAME); - let vhdx_path = vhdx_file_path.to_str().unwrap(); + let vhdx_pathbuf = guest.tmp_dir.as_path().join(DYNAMIC_VHDX_NAME); + let vhdx_path = vhdx_pathbuf.to_str().unwrap(); // Generate a 100 MiB dynamic VHDX file std::process::Command::new("qemu-img") @@ -3407,10 +3407,6 @@ mod common_parallel { // Check if the size matches with empty VHDx file size assert_eq!(vhdx_image_size(vhdx_path), EMPTY_VHDX_FILE_SIZE); - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); - let kernel_path = direct_kernel_boot_path(); - let mut cloud_child = GuestCommand::new(&guest) .args(["--cpus", "boot=1"]) .args(["--memory", "size=512M"]) From 2d9fc3beb63dc4fe72f478ea65bcc2cf4fe73e2e Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Wed, 23 Jul 2025 19:29:58 +0000 Subject: [PATCH 0036/1893] tests: Reenable test_virtio_block_dynamic_vhdx_expand Signed-off-by: Wei Liu --- tests/integration.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration.rs b/tests/integration.rs index 62cbea9dea..d754ccff8a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3381,7 +3381,6 @@ mod common_parallel { } #[test] - #[ignore = "See #7209"] fn test_virtio_block_dynamic_vhdx_expand() { const VIRTUAL_DISK_SIZE: u64 = 100 << 20; const EMPTY_VHDX_FILE_SIZE: u64 = 8 << 20; From 5f2392c0958f719b895b23cbb9ca4975953d0fb0 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Wed, 23 Jul 2025 22:37:19 +0000 Subject: [PATCH 0037/1893] tests: Avoid repeatedly downloading files from GitHub Running one or two tests in a tight loop can cause the download functions to quickly hit GitHub's API rate limit. That causes the test script to fail for no apparent reason. Signed-off-by: Wei Liu --- scripts/run_integration_tests_x86_64.sh | 8 ++++++-- scripts/sha1sums-x86_64 | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 11f5d6664a..4fec7d04c9 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -20,9 +20,13 @@ fi cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR" -download_hypervisor_fw +if [ ! -f "$WORKLOADS_DIR/hypervisor-fw" ]; then + download_hypervisor_fw +fi -download_ovmf +if [ ! -f "$WORKLOADS_DIR/CLOUDHV.fd" ]; then + download_ovmf +fi FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2" FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME" diff --git a/scripts/sha1sums-x86_64 b/scripts/sha1sums-x86_64 index 7fd5c56d87..e198816035 100644 --- a/scripts/sha1sums-x86_64 +++ b/scripts/sha1sums-x86_64 @@ -3,3 +3,5 @@ f1eccdc5e1b515dbad294426ab081b47ebfb97c0 focal-server-cloudimg-amd64-custom-2021 7f5a8358243a96adf61f5c20139b29f308f2c0e3 focal-server-cloudimg-amd64-custom-20210609-0.raw 5f10738920efb74f0bf854cadcd1b1fd544e49c8 jammy-server-cloudimg-amd64-custom-20241017-0.qcow2 c1dfbe7abde400e675844568dbe9d3914222f6de jammy-server-cloudimg-amd64-custom-20241017-0.raw +540ac358429305d7aa94e15363665d1c9d845982 hypervisor-fw +4e96fd0914a44005d40707b2b0c7e829e4086bd5 CLOUDHV.fd From 423280fb96662d7866cc8f36f3b90c59bc5fc193 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 23:18:29 +0000 Subject: [PATCH 0038/1893] build: Bump enumflags2 from 0.7.10 to 0.7.12 Bumps [enumflags2](https://github.com/meithecatte/enumflags2) from 0.7.10 to 0.7.12. - [Release notes](https://github.com/meithecatte/enumflags2/releases) - [Commits](https://github.com/meithecatte/enumflags2/compare/v0.7.10...v0.7.12) --- updated-dependencies: - dependency-name: enumflags2 dependency-version: 0.7.12 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 305e31cee0..c67d21cb2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -590,9 +590,9 @@ checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" [[package]] name = "enumflags2" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ "enumflags2_derive", "serde", @@ -600,9 +600,9 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", From 76d8d47f6acd22c364f9dd8a9ffd3c9ad645401c Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 17 Jul 2025 00:47:04 +0000 Subject: [PATCH 0039/1893] performance-metrics: Initialize tests after setting overrides No functional change. Signed-off-by: Wei Liu --- performance-metrics/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index e029b10d8d..8c1b669bbf 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -712,8 +712,6 @@ fn main() { // Run performance tests sequentially and report results (in both readable/json format) let mut metrics_report: MetricsReport = Default::default(); - init_tests(); - let overrides = Arc::new(PerformanceTestOverrides { test_iterations: cmd_arguments .get_one::("iterations") @@ -727,6 +725,8 @@ fn main() { .unwrap_or_default(), }); + init_tests(); + for test in test_list.iter() { if test_filter.is_empty() || test_filter.iter().any(|&s| test.name.contains(s)) { match run_test_with_timeout(test, &overrides) { From 8a26380657fae54f5ce0ead68fb61737414151e2 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 17 Jul 2025 00:20:24 +0000 Subject: [PATCH 0040/1893] performance-metrics: Add all supported formats to block tests Signed-off-by: Wei Liu --- performance-metrics/src/main.rs | 55 +++++++++++++++++++- performance-metrics/src/performance_tests.rs | 32 ++++++++---- 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 8c1b669bbf..c348155817 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -106,10 +106,45 @@ impl Default for MetricsReport { } } +#[derive(Clone, Copy, Default)] +pub enum ImageFormat { + #[default] + Raw, + Qcow2, + Vhd, + Vhdx, +} + +impl std::str::FromStr for ImageFormat { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "raw" => Ok(ImageFormat::Raw), + "qcow2" => Ok(ImageFormat::Qcow2), + "vhd" => Ok(ImageFormat::Vhd), + "vhdx" => Ok(ImageFormat::Vhdx), + _ => Err(()), + } + } +} + +impl fmt::Display for ImageFormat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ImageFormat::Raw => write!(f, "raw"), + ImageFormat::Qcow2 => write!(f, "qcow2"), + ImageFormat::Vhd => write!(f, "vhd"), + ImageFormat::Vhdx => write!(f, "vhdx"), + } + } +} + #[derive(Default)] pub struct PerformanceTestOverrides { test_iterations: Option, test_timeout: Option, + test_image_format: Option, } impl fmt::Display for PerformanceTestOverrides { @@ -121,6 +156,10 @@ impl fmt::Display for PerformanceTestOverrides { write!(f, "test_timeout = {test_timeout}")?; } + if let Some(test_image_format) = self.test_image_format { + write!(f, "test_image_format = {test_image_format}")?; + } + Ok(()) } } @@ -686,6 +725,15 @@ fn main() { .help("Override test timeout, Ex. --timeout 5") .num_args(1), ) + .arg( + Arg::new("image-format") + .long("image-format") + .help( + "Override the image format used for block tests, supported values: qcow2, raw, vhd, vhdx. \ + Default is 'raw'.", + ) + .num_args(1), + ) .get_matches(); // It seems that the tool (ethr) used for testing the virtio-net latency @@ -723,9 +771,14 @@ fn main() { .map(|s| s.parse()) .transpose() .unwrap_or_default(), + test_image_format: cmd_arguments + .get_one::("image-format") + .map(|s| s.parse()) + .transpose() + .unwrap_or_default(), }); - init_tests(); + init_tests(&overrides); for test in test_list.iter() { if test_filter.is_empty() || test_filter.iter().any(|&s| test.name.contains(s)) { diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index a2d7004900..46eb090fcc 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -12,7 +12,7 @@ use std::{fs, thread}; use test_infra::{Error as InfraError, *}; use thiserror::Error; -use crate::{mean, PerformanceTestControl}; +use crate::{mean, ImageFormat, PerformanceTestControl, PerformanceTestOverrides}; #[cfg(target_arch = "x86_64")] pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw"; @@ -30,16 +30,30 @@ enum Error { RestoreTimeParse, } +// The test image cannot be created on tmpfs (e.g. /tmp) filesystem, +// as tmpfs does not support O_DIRECT const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img"; -pub fn init_tests() { - // The test image cannot be created on tmpfs (e.g. /tmp) filesystem, - // as tmpfs does not support O_DIRECT - assert!(exec_host_command_output(&format!( - "dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096" - )) - .status - .success()); +pub fn init_tests(overrides: &PerformanceTestOverrides) { + let mut cmd = format!("dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096"); + + if let Some(o) = overrides.test_image_format { + match o { + ImageFormat::Raw => { /* Nothing to do */ } + ImageFormat::Qcow2 => { + cmd = + format!("qemu-img create -f qcow2 -o preallocation=full {BLK_IO_TEST_IMG} 4G"); + } + ImageFormat::Vhd => { + cmd = format!("qemu-img create -f vpc -o subformat=fixed {BLK_IO_TEST_IMG} 4G"); + } + ImageFormat::Vhdx => { + cmd = format!("qemu-img create -f vhdx -o subformat=fixed {BLK_IO_TEST_IMG} 4G"); + } + } + } + + assert!(exec_host_command_output(&cmd).status.success()); } pub fn cleanup_tests() { From 511a100842664ffcfba34bf215de3dec80f3d216 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 23 Jul 2025 20:56:19 +0200 Subject: [PATCH 0041/1893] tpm: Use the same log version as other workspace members The other workspace members in the Cloud-hypervisor workspace currently declare log version 0.4.22, but the tpm crate has an older version. This inconsistency is addressed by this PR which opens the door for declaring log as a workspace dependency. Signed-off-by: Oliver Anderson On-behalf-of: SAP oliver.anderson@sap.com --- tpm/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tpm/Cargo.toml b/tpm/Cargo.toml index 21cb222d5a..fa3461245d 100644 --- a/tpm/Cargo.toml +++ b/tpm/Cargo.toml @@ -8,7 +8,7 @@ version = "0.1.0" [dependencies] anyhow = "1.0.94" libc = "0.2.167" -log = "0.4.21" +log = "0.4.22" net_gen = { path = "../net_gen" } thiserror = { workspace = true } vmm-sys-util = { workspace = true } From 985dcfbf3e82a699d45ba1b93519d40faa7f4b69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 23:53:17 +0000 Subject: [PATCH 0042/1893] build: Bump async-signal from 0.2.11 to 0.2.12 Bumps [async-signal](https://github.com/smol-rs/async-signal) from 0.2.11 to 0.2.12. - [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.11...v0.2.12) --- updated-dependencies: - dependency-name: async-signal dependency-version: 0.2.12 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c67d21cb2f..b58a6c4563 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,9 +221,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" dependencies = [ "async-io", "async-lock", @@ -234,7 +234,7 @@ dependencies = [ "rustix 1.0.7", "signal-hook-registry", "slab", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2644,6 +2644,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2668,13 +2677,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2687,6 +2712,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2699,6 +2730,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2711,12 +2748,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2729,6 +2778,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2741,6 +2796,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2753,6 +2814,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2765,6 +2832,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.7.2" From 2c3e6cd93d66782ece23b307e52ca604ca7805eb Mon Sep 17 00:00:00 2001 From: Lucas Grosche Date: Wed, 16 Jul 2025 16:24:10 +0200 Subject: [PATCH 0043/1893] docs: Add Windows 11 support information The current documentation only mentions Windows server support. Since only minimal adjustments are needed for Windows 11 support, these were added where applicable. Also contains whitespace fixes, adds missing `\` in example code and adds more cross references. Signed-off-by: Lucas Grosche --- docs/windows.md | 134 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 28 deletions(-) diff --git a/docs/windows.md b/docs/windows.md index e4945d939b..ebb902fbfa 100644 --- a/docs/windows.md +++ b/docs/windows.md @@ -4,10 +4,10 @@ Starting with the release version [0.10.0](https://github.com/cloud-hypervisor/c __Requirements__ -- Host with KVM enabled +- Host with KVM enabled - [UEFI](uefi.md) capable Windows guest image with Virtio drivers integrated -Any modern Windows Server version is compatible. Cloud Hypervisor has been successfully tested with Windows Server 2019 and Windows Server Core 2004. +Any modern Windows Server version is compatible, as well as Windows 11. Cloud Hypervisor has been successfully tested with Windows Server 2019, Windows Server Core 2004 and Windows 11 IoT Enterprise LTSC 2024. At the current stage, only UEFI capable Windows images are supported. This implies the presence of the OVMF firmware during the Windows installation and in any subsequent usage. BIOS boot is not supported. @@ -20,10 +20,15 @@ The subsequent sections will tell, in detail, how to prepare an appropriate Wind __Prerequisites__ - QEMU, version >=5.0.0 is recommended. -- Windows installation ISO. Obtained through MSDN, Visual Studio subscription, evaluation center, etc. +- Windows installation ISO. Obtained through MSDN, Visual Studio subscription, evaluation center, etc. - [VirtIO driver ISO](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/) + - Please use the [VirtIO Windows 11 attestation file](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/upstream-virtio/virtio-win11-attestation-0.1-258.zip) + for Windows 11 - Suitable firmware for Cloud Hypervisor (`CLOUDHV.fd`) and for QEMU (`OVMF.fd`) -- With the suggested image size of 30G, there should be enough free disk space to hold the installation ISO and any other necessary files +- With the suggested image size of 30G for Windows Server, there should be enough free disk space to hold the installation ISO and any other necessary files + - For Windows 11, increasing this image size to 64GB is recommended (see [minimal requirements](https://support.microsoft.com/en-us/windows/windows-11-system-requirements-86c11283-ea52-4782-9efd-7674389a7ba3)) +- Windows 11 only: TPM 2.0 support +- Windows 11 only: 2 or more cores This step currently requires QEMU to install Windows onto the guest. QEMU is only used at the preparation stage, the resulting image is then fully functional with Cloud Hypervisor. @@ -37,11 +42,13 @@ OVMF_DIR=./FV ``` Create an empty image file, `raw` is supported. + ```shell qemu-img create -f raw $IMG_FILE 30G ``` -Begin the Windows installation process under QEMU +Begin the Windows installation process under QEMU for Windows Server: + ```shell qemu-system-x86_64 \ -machine q35,accel=kvm \ @@ -57,13 +64,56 @@ qemu-system-x86_64 \ -vga std ``` -Before the installation can proceed, point the Windows installation program to the VirtIO disk and install the necessary storage controller drivers. After that, the attached hard drive will become visible and the actual installation can commence. +For Windows 11 you can use `swtpm` to fulfill the TPM 2.0 requirement: + +```shell +# Create directory to store state +mkdir -p /tmp/mytpm1 +# Start swtpm daemon for TPM 2.0 support +swtpm socket \ + --tpm2 \ + --ctrl type=unixio,path=/tmp/swtpm-sock \ + --tpmstate dir=/tmp/mytpm1 \ + --flags startup-clear \ + --log level=20 \ + --log file=/tmp/swtpm.log \ + --daemon +``` + +Begin the Windows 11 installation process under QEMU like this: + +```shell +qemu-system-x86_64 \ + -machine q35,accel=kvm \ + -cpu host \ + -m 4G \ + -bios ./$OVMF_DIR/OVMF.fd \ + -cdrom ./$WIN_ISO_FILE \ + -drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom \ + -drive if=none,id=root,file=./$IMG_FILE \ + -device virtio-blk-pci,drive=root,disable-legacy=on \ + -device virtio-net-pci,netdev=mynet0,disable-legacy=on \ + -netdev user,id=mynet0 \ + -vga std \ + -smp 4 \ + -chardev socket,id=chrtpm,path=/tmp/swtpm-sock \ + -tpmdev emulator,id=tpm0,chardev=chrtpm \ + -device tpm-tis,tpmdev=tpm0 +``` + +This command needs at least `-smp 2` (2 cores), as well as the last three lines (TPM 2.0), to support Windows 11 minimal requirements. Additionally, using `OVMF_CODE.fd` leads to the following error: `qemu: could not load PC BIOS '././FV/OVMF_CODE.fd'`. Switching to `OVMF.fd` is therefore necessary. + +For more details about TPM specifically, please continue with the [TPM documentation](./tpm.md). + +Before the installation can proceed, point the Windows installation program to the VirtIO disk and install the necessary storage controller drivers. For Windows 11 with the attestation drivers, you need to navigate to the `viostor` directory to be able to see and install it. After that, the attached hard drive will become visible and the actual installation can commence. -After the installation has completed, proceed further to the configuration section. QEMU will be needed at least once more to enable the Windows Special Administration Console (SAC) and to possibly install extra device drivers. +Do not install network drivers for Windows 11 just yet, if you don't want to be forced to log-in to/create a Microsoft account. Simply select `I don't have internet` for now. + +After the installation has completed, proceed further to the [configuration section](#image-configuration). QEMU will be needed at least once more to enable/install the Windows Special Administration Console (SAC) and to possibly install extra device drivers. ## Image Usage -The basic command to boot a Windows image. The configuration section should be checked before executing it for the first time. +The basic command to boot a Windows image is shown in the next code snippet. The [configuration section](#image-configuration), as well as the [Getting Started section](../README.md#2-getting-started) should be checked before executing it for the first time. Please especially read the documentation for giving the cloud-hypervisor binary the correct capabilities for it to set TAP interfaces up on the host, otherwise the command below will fail: ```shell cloud-hypervisor \ @@ -85,19 +135,25 @@ In cases where the host processor supports address space > 39 bits, it might be To daemonize the Cloud Hypervisor process, `nohup` can be used. Some STDIO redirections might need to be done. In a simple case it is sufficient to just redirect all the output to `/dev/null`. +Be aware, currently, running the Windows 11 VM on Cloud Hypervisor with TPM 2.0 was not proven successful: `thread 'vcpu0' panicked`. Running the VM without TPM is a valid option though. Therefore the command as shown above is also valid for a Windows 11 VM. + ## Image Configuration ### Device Drivers After the Windows installation has finished under QEMU, there might be still devices with no drivers installed. This might happen for example, when a device was not used during the installation. In particular it is important to ensure that the VirtIO network device is setup correctly because further steps for the configuration and the usage require network in most case. -Boot once more under QEMU and use the [Device Manager](https://support.microsoft.com/en-in/help/4028443/windows-10-update-drivers), to ensure all the device drivers, and especially the network card, are installed correctly. Also, as Cloud Hypervisor can introduce new devices, it is advisable to repeat the procedure while booted under Cloud Hypervisor, when the RDP access to the image is functional. +Boot once more under QEMU and use the [Device Manager](https://support.microsoft.com/en-in/help/4028443/windows-10-update-drivers), to ensure all the device drivers, and especially the network card, are installed correctly. If not, right click on the unknown network device, choose `Update driver` and browse to the `NetKvm` directory on the CD. + +Also, as Cloud Hypervisor can introduce new devices, it is advisable to repeat the procedure while booted under Cloud Hypervisor, when the [RDP](#remote-desktop-protocol-rdp-enablement) access to the image is functional. ### Windows Special Administration Console (SAC) enablement SAC provides a text based console access to the Windows guest. As Cloud Hypervisor doesn't implement a VGA adaptor, SAC is an important instrument for the Windows guest management. -Boot the Windows image under QEMU and execute the below commands to permanently enable SAC +Boot the Windows image under QEMU. For all non-server Windows versions, the SAC needs to be downloaded and enabled first in the `Optional features` menu of Windows. + +Execute the below commands to permanently enable SAC. You might need admin privileges. ```cmd bcdedit /emssettings emsport:1 emsbaudrate:115200 @@ -105,15 +161,14 @@ bcdedit /ems on bcdedit /bootems on ``` -Once SAC is enabled, the image can be booted under Cloud Hypervisor. The SAC prompt will show up +Once SAC is enabled, the image can be booted under Cloud Hypervisor. The SAC prompt will show up
-Computer is booting, SAC started and initialized.                               
-                                                                                
-Use the "ch -?" command for information about using channels.                   
-Use the "?" command for general help.                                           
-                                                                                
-                                                                                
+Computer is booting, SAC started and initialized.
+
+Use the "ch -?" command for information about using channels.
+Use the "?" command for general help.
+
 SAC>
 
@@ -139,7 +194,7 @@ As the simplest option, using `--net tap=` in the Cloud Hypervisor command line
 SAC>i 10 192.168.249.2 255.255.255.0 192.168.249.1
-
+ Where `10` is the device index as shown by the `i` command. @@ -149,26 +204,38 @@ Additional steps are necessary to provide the guest with internet access. - On the guest, add the DNS server either by using `netsh` or by opening `Network and Connectivity Center` and editing the adapter properties. - On the host, configure the traffic forwarding. Replace the `NET_DEV` with the name of your network device. + ```shell NET_DEV=wlp3s0 sysctl -w net.ipv4.ip_forward=1 iptables -t nat -A POSTROUTING -o $NET_DEV -j MASQUERADE ``` +If needed, you can also allow ICMP from host to guest via the following command executed on the guest: + +```shell +netsh advfirewall firewall add rule name="Allow ICMPv4" protocol=icmpv4:8,any dir=in action=allow +``` + +This will enable simple `ping` requests from your host to the guest. + ### Remote Desktop Protocol (RDP) enablement #### Using QEMU - - Execute `SystemPropertiesRemote` - - In the properties window, choose "Allow remote connections to this computer" - - Click "Select Users" and add some user to the allow list + +- Execute `SystemPropertiesRemote` +- In the properties window, choose "Allow remote connections to this computer" +- Click "Select Users" and add some user to the allow list + #### Using powershell + ```powershell Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\" -Name "fDenyTSConnections" -Value 0 Enable-NetFirewallRule -DisplayGroup "Remote Desktop" Add-LocalGroupMember -Group "Remote Desktop Users" -Member someuser ``` - -Administrators can always RDP, non administrator users have to be explicitly enabled. + +Administrators can always RDP, non administrator users have to be explicitly enabled. Once the configuration is set, RDP clients can connect to `192.168.249.2`. @@ -182,7 +249,15 @@ Start-Service sshd Set-Service -Name sshd -StartupType ‘Automatic’ ``` -This allows for SSH login from a remote machine, for example through the `administrator` user: `ssh administrator@192.168.249.2`. For a more detailed OpenSSH guide, please follow the MSDN article from the [links](#links) section. +This allows for SSH login from a remote machine, for example through the `administrator` user: `ssh administrator@192.168.249.2`. + +On Windows 11, opening the firewall was needed as well: + +```powershell +New-NetFirewallRule -Name sshd -DisplayName "OpenSSH Server" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 +``` + +For a more detailed OpenSSH guide, please follow the MSDN article from the [links](#links) section. ## Hotplug capability @@ -196,6 +271,8 @@ Disk hotplug and hot-remove are supported. After the device has been hotplugged, ## Debugging +Disclaimer: This chapter was not verified on Windows 11 yet. Proceed with care. + The Windows guest debugging process relies heavily on QEMU and [socat](http://www.dest-unreach.org/socat/). The procedure requires two Windows VMs: - A debugger VM running under QEMU. @@ -203,7 +280,7 @@ The Windows guest debugging process relies heavily on QEMU and [socat](http://ww The connection between both guests happens over TCP, whereby on the guest side it is automatically translated to a COM port. Because the VMs are connected through TCP, the debugging infrastructure can be distributed over the network. The serial port, while slowly transferring data, is common enough to support a wide range of cases and tools. -In this exercise, [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) is used. Any other debugger of choice with the ability to use serial connection can be used instead. +In this exercise, [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) is used. Any other debugger of choice with the ability to use serial connection can be used instead. ### Debugger and Debuggee @@ -220,7 +297,7 @@ qemu-system-x86_64 \ -smp 1 \ -m 4G \ -cdrom ./$WIN_ISO_FILE \ - -drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom + -drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom \ -drive if=none,id=root,file=./windbg-disk.raw \ -device virtio-blk-pci,drive=root,disable-legacy=on \ -device virtio-net-pci,netdev=mynet0,disable-legacy=on \ @@ -256,7 +333,7 @@ bcdedit /debug on bcdedit /bootdebug on ``` -##### Turn on boot manager debug +##### Turn on boot manager debug ```cmd bcdedit /set {bootmgr} bootdebug on @@ -308,6 +385,7 @@ Once started, WinDbg will wait for an incoming connection which is going to be i ##### Under QEMU Essentially it would be the command like depicted in the guest preparation sections, with a few modifications: + ```shell qemu-system-x86_64 \ -machine q35,accel=kvm \ @@ -315,7 +393,7 @@ qemu-system-x86_64 \ -m 4G \ -bios ./$OVMF_DIR/OVMF_CODE.fd \ -cdrom ./$WIN_ISO_FILE \ - -drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom + -drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom \ -drive if=none,id=root,file=./$IMG_FILE \ -device virtio-blk-pci,drive=root,disable-legacy=on \ -device virtio-net-pci,netdev=mynet0,disable-legacy=on \ From b0bf889d586bfbf1f3ab9fe30fc0c25a08067fbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 23:19:14 +0000 Subject: [PATCH 0044/1893] build: Bump serde_with from 3.9.0 to 3.14.0 Bumps [serde_with](https://github.com/jonasbb/serde_with) from 3.9.0 to 3.14.0. - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.9.0...v3.14.0) --- updated-dependencies: - dependency-name: serde_with dependency-version: 3.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 10 +++++----- hypervisor/Cargo.toml | 2 +- virtio-devices/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b58a6c4563..7e07c7551b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -579,7 +579,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1924,9 +1924,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "serde", "serde_derive", @@ -1935,9 +1935,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 60f53c7d4d..9a254dd940 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -33,7 +33,7 @@ mshv-ioctls = { workspace = true, optional = true } open-enum = "0.5.2" serde = { version = "1.0.208", features = ["derive", "rc"] } serde_json = { workspace = true } -serde_with = { version = "3.9.0", default-features = false, features = [ +serde_with = { version = "3.14.0", default-features = false, features = [ "macros", ] } thiserror = { workspace = true } diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index a4c70d111f..9bf2efeca5 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -24,7 +24,7 @@ pci = { path = "../pci" } rate_limiter = { path = "../rate_limiter" } seccompiler = { workspace = true } serde = { version = "1.0.208", features = ["derive"] } -serde_with = { version = "3.9.0", default-features = false, features = [ +serde_with = { version = "3.14.0", default-features = false, features = [ "macros", ] } serial_buffer = { path = "../serial_buffer" } From 8c136041cbdf2879584f3ca3eb5d8371e9435680 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 25 Jul 2025 10:27:22 +0200 Subject: [PATCH 0045/1893] build: Use workspace dependencies Many of the workspace members in the Cloud-hypervisor workspace share common dependencies. Making these workspace dependencies reduces duplication and improves maintainability. Signed-off-by: Oliver Anderson On-behalf-of: SAP oliver.anderson@sap.com --- Cargo.toml | 33 ++++++++++++++++++++++++--------- arch/Cargo.toml | 10 +++++----- block/Cargo.toml | 8 ++++---- devices/Cargo.toml | 12 ++++++------ event_monitor/Cargo.toml | 6 +++--- hypervisor/Cargo.toml | 14 +++++++------- net_util/Cargo.toml | 8 ++++---- pci/Cargo.toml | 10 +++++----- performance-metrics/Cargo.toml | 6 +++--- rate_limiter/Cargo.toml | 6 +++--- test_infra/Cargo.toml | 8 ++++---- tpm/Cargo.toml | 6 +++--- tracer/Cargo.toml | 6 +++--- vhost_user_block/Cargo.toml | 6 +++--- vhost_user_net/Cargo.toml | 8 ++++---- virtio-devices/Cargo.toml | 14 +++++++------- vm-allocator/Cargo.toml | 2 +- vm-device/Cargo.toml | 2 +- vm-migration/Cargo.toml | 4 ++-- vmm/Cargo.toml | 22 +++++++++++----------- 20 files changed, 103 insertions(+), 88 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f05de398ee..b11b0b728d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,20 +29,20 @@ inherits = "release" strip = false [dependencies] -anyhow = "1.0.94" +anyhow = { workspace = true } api_client = { path = "api_client" } -clap = { version = "4.5.13", features = ["string"] } -dhat = { version = "0.3.3", optional = true } +clap = { workspace = true, features = ["string"] } +dhat = { workspace = true, optional = true } env_logger = { workspace = true } -epoll = "4.3.3" +epoll = { workspace = true } event_monitor = { path = "event_monitor" } hypervisor = { path = "hypervisor" } -libc = "0.2.167" -log = { version = "0.4.22", features = ["std"] } +libc = { workspace = true } +log = { workspace = true, features = ["std"] } option_parser = { path = "option_parser" } seccompiler = { workspace = true } serde_json = { workspace = true } -signal-hook = "0.3.18" +signal-hook = { workspace = true } thiserror = { workspace = true } tpm = { path = "tpm" } tracer = { path = "tracer" } @@ -52,11 +52,11 @@ vmm-sys-util = { workspace = true } zbus = { version = "5.7.1", optional = true } [dev-dependencies] -dirs = "6.0.0" +dirs = { workspace = true } net_util = { path = "net_util" } serde_json = { workspace = true } test_infra = { path = "test_infra" } -wait-timeout = "0.2.0" +wait-timeout = { workspace = true } # Please adjust `vmm::feature_list()` accordingly when changing the # feature list below @@ -127,10 +127,25 @@ igvm = { git = "https://github.com/microsoft/igvm", branch = "main" } igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates +serde = "1.0.208" serde_json = "1.0.120" +serde_with = { version = "3.14.0", default-features = false } # other crates +anyhow = "1.0.94" +bitflags = "2.9.0" +byteorder = "1.5.0" +cfg-if = "1.0.0" +clap = "4.5.13" +dhat = "0.3.3" +dirs = "6.0.0" env_logger = "0.11.8" +epoll = "4.3.3" +flume = "0.11.1" +libc = "0.2.167" +log = "0.4.22" +signal-hook = "0.3.18" thiserror = "2.0.12" uuid = { version = "1.17.0" } +wait-timeout = "0.2.0" zerocopy = { version = "0.8.26", default-features = false } diff --git a/arch/Cargo.toml b/arch/Cargo.toml index 03d2ad4af5..3e2202f025 100644 --- a/arch/Cargo.toml +++ b/arch/Cargo.toml @@ -11,13 +11,13 @@ sev_snp = [] tdx = [] [dependencies] -anyhow = "1.0.94" -byteorder = "1.5.0" +anyhow = { workspace = true } +byteorder = { workspace = true } hypervisor = { path = "../hypervisor" } -libc = "0.2.167" +libc = { workspace = true } linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] } -log = "0.4.22" -serde = { version = "1.0.208", features = ["derive", "rc"] } +log = { workspace = true } +serde = { workspace = true, features = ["derive", "rc"] } thiserror = { workspace = true } uuid = { workspace = true } vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] } diff --git a/block/Cargo.toml b/block/Cargo.toml index 48551251a5..f1e102e922 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -9,13 +9,13 @@ default = [] io_uring = ["dep:io-uring"] [dependencies] -byteorder = "1.5.0" +byteorder = { workspace = true } crc-any = "2.5.0" io-uring = { version = "0.6.4", optional = true } -libc = "0.2.167" -log = "0.4.22" +libc = { workspace = true } +log = { workspace = true } remain = "0.2.15" -serde = { version = "1.0.208", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } smallvec = "1.13.2" thiserror = { workspace = true } uuid = { workspace = true, features = ["v4"] } diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 334ec0e310..0c32e468e7 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -6,17 +6,17 @@ version = "0.1.0" [dependencies] acpi_tables = { workspace = true } -anyhow = "1.0.94" +anyhow = { workspace = true } arch = { path = "../arch" } -bitflags = "2.9.0" -byteorder = "1.5.0" +bitflags = { workspace = true } +byteorder = { workspace = true } event_monitor = { path = "../event_monitor" } hypervisor = { path = "../hypervisor" } -libc = "0.2.167" -log = "0.4.22" +libc = { workspace = true } +log = { workspace = true } num_enum = "0.7.2" pci = { path = "../pci" } -serde = { version = "1.0.208", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } tpm = { path = "../tpm" } vm-allocator = { path = "../vm-allocator" } diff --git a/event_monitor/Cargo.toml b/event_monitor/Cargo.toml index 764cc6218e..af63335046 100644 --- a/event_monitor/Cargo.toml +++ b/event_monitor/Cargo.toml @@ -5,7 +5,7 @@ name = "event_monitor" version = "0.1.0" [dependencies] -flume = "0.11.1" -libc = "0.2.167" -serde = { version = "1.0.208", features = ["derive", "rc"] } +flume = { workspace = true } +libc = { workspace = true } +serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 9a254dd940..bdfefdeefd 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -13,27 +13,27 @@ sev_snp = ["igvm", "igvm_defs"] tdx = [] [dependencies] -anyhow = "1.0.94" +anyhow = { workspace = true } arc-swap = "1.7.1" bitfield-struct = "0.10.1" -byteorder = "1.5.0" -cfg-if = "1.0.0" +byteorder = { workspace = true } +cfg-if = { workspace = true } concat-idents = "1.1.5" igvm = { workspace = true, optional = true } igvm_defs = { workspace = true, optional = true } kvm-bindings = { workspace = true, optional = true, features = ["serde"] } kvm-ioctls = { workspace = true, optional = true } -libc = "0.2.167" -log = "0.4.22" +libc = { workspace = true } +log = { workspace = true } mshv-bindings = { workspace = true, features = [ "fam-wrappers", "with-serde", ], optional = true } mshv-ioctls = { workspace = true, optional = true } open-enum = "0.5.2" -serde = { version = "1.0.208", features = ["derive", "rc"] } +serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } -serde_with = { version = "3.14.0", default-features = false, features = [ +serde_with = { workspace = true, default-features = false, features = [ "macros", ] } thiserror = { workspace = true } diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml index 981b5b3d9e..74ad342085 100644 --- a/net_util/Cargo.toml +++ b/net_util/Cargo.toml @@ -5,13 +5,13 @@ name = "net_util" version = "0.1.0" [dependencies] -epoll = "4.3.3" +epoll = { workspace = true } getrandom = "0.3.3" -libc = "0.2.167" -log = "0.4.22" +libc = { workspace = true } +log = { workspace = true } net_gen = { path = "../net_gen" } rate_limiter = { path = "../rate_limiter" } -serde = { version = "1.0.208", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } virtio-bindings = { workspace = true } virtio-queue = { workspace = true } diff --git a/pci/Cargo.toml b/pci/Cargo.toml index 0e29051815..49618b194b 100644 --- a/pci/Cargo.toml +++ b/pci/Cargo.toml @@ -10,12 +10,12 @@ kvm = ["hypervisor/kvm", "vfio-ioctls/kvm"] mshv = ["hypervisor/mshv", "vfio-ioctls/mshv"] [dependencies] -anyhow = "1.0.94" -byteorder = "1.5.0" +anyhow = { workspace = true } +byteorder = { workspace = true } hypervisor = { path = "../hypervisor" } -libc = "0.2.167" -log = "0.4.22" -serde = { version = "1.0.208", features = ["derive"] } +libc = { workspace = true } +log = { workspace = true } +serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } vfio-bindings = { workspace = true, features = ["fam-wrappers"] } vfio-ioctls = { workspace = true, default-features = false } diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index 8572bf5160..2403913767 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -6,9 +6,9 @@ name = "performance-metrics" version = "0.1.0" [dependencies] -clap = { version = "4.5.13", features = ["wrap_help"] } -dirs = "6.0.0" -serde = { version = "1.0.208", features = ["derive", "rc"] } +clap = { workspace = true, features = ["wrap_help"] } +dirs = { workspace = true } +serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } test_infra = { path = "../test_infra" } thiserror = { workspace = true } diff --git a/rate_limiter/Cargo.toml b/rate_limiter/Cargo.toml index a286b9e20a..6c6cdd650e 100644 --- a/rate_limiter/Cargo.toml +++ b/rate_limiter/Cargo.toml @@ -4,8 +4,8 @@ name = "rate_limiter" version = "0.1.0" [dependencies] -epoll = "4.3.3" -libc = "0.2.167" -log = "0.4.22" +epoll = { workspace = true } +libc = { workspace = true } +log = { workspace = true } thiserror = { workspace = true } vmm-sys-util = { workspace = true } diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index 5aeae23c58..37c6f38454 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -5,11 +5,11 @@ name = "test_infra" version = "0.1.0" [dependencies] -dirs = "6.0.0" -epoll = "4.3.3" -libc = "0.2.167" +dirs = { workspace = true } +epoll = { workspace = true } +libc = { workspace = true } serde_json = { workspace = true } ssh2 = { version = "0.9.4", features = ["vendored-openssl"] } thiserror = { workspace = true } vmm-sys-util = { workspace = true } -wait-timeout = "0.2.0" +wait-timeout = { workspace = true } diff --git a/tpm/Cargo.toml b/tpm/Cargo.toml index fa3461245d..cf03968cde 100644 --- a/tpm/Cargo.toml +++ b/tpm/Cargo.toml @@ -6,9 +6,9 @@ name = "tpm" version = "0.1.0" [dependencies] -anyhow = "1.0.94" -libc = "0.2.167" -log = "0.4.22" +anyhow = { workspace = true } +libc = { workspace = true } +log = { workspace = true } net_gen = { path = "../net_gen" } thiserror = { workspace = true } vmm-sys-util = { workspace = true } diff --git a/tracer/Cargo.toml b/tracer/Cargo.toml index 5d4cb678c9..368520870f 100644 --- a/tracer/Cargo.toml +++ b/tracer/Cargo.toml @@ -5,9 +5,9 @@ name = "tracer" version = "0.1.0" [dependencies] -libc = "0.2.167" -log = "0.4.22" -serde = { version = "1.0.208", features = ["derive", "rc"] } +libc = { workspace = true } +log = { workspace = true } +serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } [features] diff --git a/vhost_user_block/Cargo.toml b/vhost_user_block/Cargo.toml index 0648d13be5..ea1acf12a3 100644 --- a/vhost_user_block/Cargo.toml +++ b/vhost_user_block/Cargo.toml @@ -7,10 +7,10 @@ version = "0.1.0" [dependencies] block = { path = "../block" } -clap = { version = "4.5.13", features = ["cargo", "wrap_help"] } +clap = { workspace = true, features = ["cargo", "wrap_help"] } env_logger = { workspace = true } -libc = "0.2.167" -log = "0.4.22" +libc = { workspace = true } +log = { workspace = true } option_parser = { path = "../option_parser" } thiserror = { workspace = true } vhost = { workspace = true, features = ["vhost-user-backend"] } diff --git a/vhost_user_net/Cargo.toml b/vhost_user_net/Cargo.toml index 515287e2cc..f84eae8d18 100644 --- a/vhost_user_net/Cargo.toml +++ b/vhost_user_net/Cargo.toml @@ -6,11 +6,11 @@ name = "vhost_user_net" version = "0.1.0" [dependencies] -clap = { version = "4.5.13", features = ["cargo", "wrap_help"] } +clap = { workspace = true, features = ["cargo", "wrap_help"] } env_logger = { workspace = true } -epoll = "4.3.3" -libc = "0.2.167" -log = "0.4.22" +epoll = { workspace = true } +libc = { workspace = true } +log = { workspace = true } net_util = { path = "../net_util" } option_parser = { path = "../option_parser" } thiserror = { workspace = true } diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index 9bf2efeca5..e13e98f759 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -11,20 +11,20 @@ mshv = ["pci/mshv"] sev_snp = ["mshv-ioctls"] [dependencies] -anyhow = "1.0.94" +anyhow = { workspace = true } block = { path = "../block" } -byteorder = "1.5.0" -epoll = "4.3.3" +byteorder = { workspace = true } +epoll = { workspace = true } event_monitor = { path = "../event_monitor" } -libc = "0.2.167" -log = "0.4.22" +libc = { workspace = true } +log = { workspace = true } mshv-ioctls = { workspace = true, optional = true } net_util = { path = "../net_util" } pci = { path = "../pci" } rate_limiter = { path = "../rate_limiter" } seccompiler = { workspace = true } -serde = { version = "1.0.208", features = ["derive"] } -serde_with = { version = "3.14.0", default-features = false, features = [ +serde = { workspace = true, features = ["derive"] } +serde_with = { workspace = true, default-features = false, features = [ "macros", ] } serial_buffer = { path = "../serial_buffer" } diff --git a/vm-allocator/Cargo.toml b/vm-allocator/Cargo.toml index 1cc0ae9e1f..4f546058e1 100644 --- a/vm-allocator/Cargo.toml +++ b/vm-allocator/Cargo.toml @@ -9,7 +9,7 @@ default = [] kvm = ["arch/kvm"] [dependencies] -libc = "0.2.167" +libc = { workspace = true } vm-memory = { workspace = true } [target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies] diff --git a/vm-device/Cargo.toml b/vm-device/Cargo.toml index 8262f84b00..9df6af3a0a 100644 --- a/vm-device/Cargo.toml +++ b/vm-device/Cargo.toml @@ -11,7 +11,7 @@ mshv = ["vfio-ioctls/mshv"] [dependencies] hypervisor = { path = "../hypervisor" } -serde = { version = "1.0.208", features = ["derive", "rc"] } +serde = { workspace = true, features = ["derive", "rc"] } thiserror = { workspace = true } vfio-ioctls = { workspace = true, default-features = false } vmm-sys-util = { workspace = true } diff --git a/vm-migration/Cargo.toml b/vm-migration/Cargo.toml index 5a992c6070..de10ebdb33 100644 --- a/vm-migration/Cargo.toml +++ b/vm-migration/Cargo.toml @@ -5,8 +5,8 @@ name = "vm-migration" version = "0.1.0" [dependencies] -anyhow = "1.0.94" -serde = { version = "1.0.208", features = ["derive", "rc"] } +anyhow = { workspace = true } +serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } thiserror = { workspace = true } vm-memory = { workspace = true, features = ["backend-atomic", "backend-mmap"] } diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index b28946f320..2ca7cd2dc4 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -33,18 +33,18 @@ tracing = ["tracer/tracing"] [dependencies] acpi_tables = { workspace = true } -anyhow = "1.0.94" +anyhow = { workspace = true } arch = { path = "../arch" } -bitflags = "2.9.0" +bitflags = { workspace = true } block = { path = "../block" } blocking = { version = "1.6.1", optional = true } -cfg-if = "1.0.0" -clap = "4.5.13" +cfg-if = { workspace = true } +clap = { workspace = true } devices = { path = "../devices" } -dhat = { version = "0.3.3", optional = true } -epoll = "4.3.3" +dhat = { workspace = true, optional = true } +epoll = { workspace = true } event_monitor = { path = "../event_monitor" } -flume = "0.11.1" +flume = { workspace = true } futures = { version = "0.3.31", optional = true } gdbstub = { version = "0.7.6", optional = true } gdbstub_arch = { version = "0.3.0", optional = true } @@ -53,9 +53,9 @@ hypervisor = { path = "../hypervisor" } igvm = { workspace = true, optional = true } igvm_defs = { workspace = true, optional = true } landlock = "0.4.0" -libc = "0.2.167" +libc = { workspace = true } linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] } -log = "0.4.22" +log = { workspace = true } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } mshv-bindings = { workspace = true, features = [ "fam-wrappers", @@ -67,10 +67,10 @@ pci = { path = "../pci" } range_map_vec = { version = "0.2.0", optional = true } rate_limiter = { path = "../rate_limiter" } seccompiler = { workspace = true } -serde = { version = "1.0.208", features = ["derive", "rc"] } +serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } serial_buffer = { path = "../serial_buffer" } -signal-hook = "0.3.18" +signal-hook = { workspace = true } thiserror = { workspace = true } tracer = { path = "../tracer" } uuid = { workspace = true } From ba962a30d5cac4fe4f7a4e99dd98d9b5193414f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 03:05:45 +0000 Subject: [PATCH 0046/1893] build: Bump r-efi from 5.2.0 to 5.3.0 Bumps [r-efi](https://github.com/r-efi/r-efi) from 5.2.0 to 5.3.0. - [Release notes](https://github.com/r-efi/r-efi/releases) - [Changelog](https://github.com/r-efi/r-efi/blob/main/NEWS.md) - [Commits](https://github.com/r-efi/r-efi/compare/v5.2.0...v5.3.0) --- updated-dependencies: - dependency-name: r-efi dependency-version: 5.3.0 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e07c7551b..4f2af0eb4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1696,9 +1696,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" From 7c631b2d1b16a193c5c7ba0a3e8165d949275f39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 04:46:43 +0000 Subject: [PATCH 0047/1893] build: Bump toml_datetime from 0.6.8 to 0.6.11 Bumps [toml_datetime](https://github.com/toml-rs/toml) from 0.6.8 to 0.6.11. - [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.6.8...toml_datetime-v0.6.11) --- updated-dependencies: - dependency-name: toml_datetime dependency-version: 0.6.11 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f2af0eb4a..ffc1ef8e0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2118,9 +2118,9 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" From 8e010f1aa3e3d09fc40e721aa9e90de36898befa Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Mon, 28 Jul 2025 12:09:28 -0700 Subject: [PATCH 0048/1893] vmm: don't configure system if rsdp is not available In case of CVM guest rsdp is set to none. Unwrapping it make the vmm crashed. Don't call configure system if the rsdb address is none. Signed-off-by: Muminul Islam --- vmm/src/vm.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index d7bba25cc0..012ebef05e 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2332,15 +2332,16 @@ impl Vm { let rsdp_addr = self.create_acpi_tables(); #[cfg(not(target_arch = "riscv64"))] - // Configure shared state based on loaded kernel - entry_point - .map(|entry_point| { - // Safe to unwrap rsdp_addr as we know it can't be None when - // the entry_point is Some. - self.configure_system(rsdp_addr.unwrap(), entry_point) - }) - .transpose()?; - + { + #[cfg(not(feature = "sev_snp"))] + assert!(rsdp_addr.is_some()); + // Configure shared state based on loaded kernel + if let Some(rsdp_adr) = rsdp_addr { + entry_point + .map(|entry_point| self.configure_system(rsdp_adr, entry_point)) + .transpose()?; + } + } #[cfg(target_arch = "riscv64")] self.configure_system().unwrap(); From 003e89e8cd5d8cafe241212fa3d6f100d4ff97ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 23:37:12 +0000 Subject: [PATCH 0049/1893] build: Bump anstream from 0.6.15 to 0.6.19 Bumps [anstream](https://github.com/rust-cli/anstyle) from 0.6.15 to 0.6.19. - [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.15...anstream-v0.6.19) --- updated-dependencies: - dependency-name: anstream dependency-version: 0.6.19 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffc1ef8e0b..db394f861e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,9 +36,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -655,7 +655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1837,7 +1837,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1850,7 +1850,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] From 8a37e154db90ca0db200cc28752f0a19581e7500 Mon Sep 17 00:00:00 2001 From: Shubham Chakrawar Date: Tue, 29 Jul 2025 16:51:32 -0700 Subject: [PATCH 0050/1893] ci: Prevent link checker rate limits Optimize lychee workflow to check only changed files in pull requests, avoiding excessive API calls to prevent rate limits. Fixes #7056 Signed-off-by: Shubham Chakrawar --- .github/workflows/lychee.yaml | 39 ++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lychee.yaml b/.github/workflows/lychee.yaml index dd3a372dc8..620648bf9b 100644 --- a/.github/workflows/lychee.yaml +++ b/.github/workflows/lychee.yaml @@ -1,7 +1,5 @@ name: Link Check (lychee) -on: - pull_request - +on: pull_request jobs: link_check: name: Link Check @@ -9,8 +7,39 @@ jobs: steps: - name: Code checkout uses: actions/checkout@v4 + 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@v40 # 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 }} - - name: Link Availability Check + # 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: - args: --verbose --config .lychee.toml . + # 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 \ No newline at end of file From 16b0e0848248d044b13dc62c65335f4ae1fa9e7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 23:18:02 +0000 Subject: [PATCH 0051/1893] build: Bump tj-actions/changed-files from 40 to 46 in /.github/workflows Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 40 to 46. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v40...v46) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-version: '46' dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/lychee.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lychee.yaml b/.github/workflows/lychee.yaml index 620648bf9b..68271c509b 100644 --- a/.github/workflows/lychee.yaml +++ b/.github/workflows/lychee.yaml @@ -12,7 +12,7 @@ jobs: fetch-depth: 0 - name: Get changed files in PR id: changed-files - uses: tj-actions/changed-files@v40 # Using a dedicated action for robustness + uses: tj-actions/changed-files@v46 # 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 }} From 254db85a33052c13f2e00cc4728c92f1ebb0ee28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 23:50:15 +0000 Subject: [PATCH 0052/1893] build: Bump anyhow from 1.0.94 to 1.0.98 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.94 to 1.0.98. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.94...1.0.98) --- updated-dependencies: - dependency-name: anyhow dependency-version: 1.0.98 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db394f861e..b549891b12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,9 +85,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "api_client" @@ -655,7 +655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1837,7 +1837,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1850,7 +1850,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b11b0b728d..4e1848010f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,7 +132,7 @@ serde_json = "1.0.120" serde_with = { version = "3.14.0", default-features = false } # other crates -anyhow = "1.0.94" +anyhow = "1.0.98" bitflags = "2.9.0" byteorder = "1.5.0" cfg-if = "1.0.0" From a7645ae9d6ea30acf2fe017385a6cff5df36a658 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 23:18:29 +0000 Subject: [PATCH 0053/1893] build: Bump async-process from 2.3.0 to 2.4.0 Bumps [async-process](https://github.com/smol-rs/async-process) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/smol-rs/async-process/releases) - [Changelog](https://github.com/smol-rs/async-process/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/async-process/compare/v2.3.0...v2.4.0) --- updated-dependencies: - dependency-name: async-process dependency-version: 2.4.0 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b549891b12..01a9ef0eda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,9 +191,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" dependencies = [ "async-channel", "async-io", @@ -204,8 +204,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 0.38.44", - "tracing", + "rustix 1.0.7", ] [[package]] From b630b22ed91f694ff5f4c4cf5fa365fee32f55bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 23:31:21 +0000 Subject: [PATCH 0054/1893] build: Bump landlock from 0.4.0 to 0.4.2 Bumps [landlock](https://github.com/landlock-lsm/rust-landlock) from 0.4.0 to 0.4.2. - [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.0...v0.4.2) --- updated-dependencies: - dependency-name: landlock dependency-version: 0.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 6 +++--- vmm/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01a9ef0eda..46fe34a97a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1082,13 +1082,13 @@ dependencies = [ [[package]] name = "landlock" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dafb8a4afee64f167eb2b52d32f0eea002e41a7a6450e68c799c8ec3a81a634c" +checksum = "b3d2ef408b88e913bfc6594f5e693d57676f6463ded7d8bf994175364320c706" dependencies = [ "enumflags2", "libc", - "thiserror 1.0.62", + "thiserror 2.0.12", ] [[package]] diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 2ca7cd2dc4..bceba9bdd2 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -52,7 +52,7 @@ hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } igvm = { workspace = true, optional = true } igvm_defs = { workspace = true, optional = true } -landlock = "0.4.0" +landlock = "0.4.2" libc = { workspace = true } linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] } log = { workspace = true } From eb147cb3cccf343ae0fb8c15a5d2b929a6426ef5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 06:17:08 +0000 Subject: [PATCH 0055/1893] build: Bump rustc-demangle from 0.1.24 to 0.1.26 Bumps [rustc-demangle](https://github.com/rust-lang/rustc-demangle) from 0.1.24 to 0.1.26. - [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/0.1.24...rustc-demangle-v0.1.26) --- updated-dependencies: - dependency-name: rustc-demangle dependency-version: 0.1.26 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46fe34a97a..d464823498 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1816,9 +1816,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" From 9ee3b29a1173cafff2e80a6ede1adbd6a4842410 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 07:28:23 +0000 Subject: [PATCH 0056/1893] build: Bump crate-ci/typos from 1.34.0 to 1.35.1 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.34.0 to 1.35.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.34.0...v1.35.1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.35.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 0ece7fa690..81ad38b626 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -152,4 +152,4 @@ jobs: steps: - uses: actions/checkout@v4 # Executes "typos ." - - uses: crate-ci/typos@v1.34.0 + - uses: crate-ci/typos@v1.35.1 From e2d2b2f2f05d9a6b1da32abcd05a58d9cd5c3975 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 11:26:54 +0000 Subject: [PATCH 0057/1893] build: Bump rand from 0.9.1 to 0.9.2 Bumps [rand](https://github.com/rust-random/rand) from 0.9.1 to 0.9.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/rand_core-0.9.1...rand_core-0.9.2) --- updated-dependencies: - dependency-name: rand dependency-version: 0.9.2 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d464823498..1a0499d850 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1701,9 +1701,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", From cda1ea53a56b30f77d5982c42ea170afd3e56f06 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Tue, 22 Jul 2025 01:20:31 +0000 Subject: [PATCH 0058/1893] vmm: acpi: Make 'create_acpi_tables' more flexible Now 'create_acpi_tables_internal()' can generate ACPI tables with different dsdt offset, so that it can be reused for generating ACPI tables for `fw_cfg`. Signed-off-by: Bo Chen Signed-off-by: Alex Orozco --- vmm/src/acpi.rs | 107 ++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index e59b63f58b..27d52abeb4 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -626,41 +626,32 @@ fn create_viot_table(iommu_bdf: &PciBdf, devices_bdf: &[PciBdf]) -> Sdt { viot } -pub fn create_acpi_tables( - guest_mem: &GuestMemoryMmap, +fn create_acpi_tables_internal( + dsdt_offset: GuestAddress, device_manager: &Arc>, cpu_manager: &Arc>, memory_manager: &Arc>, numa_nodes: &NumaNodes, tpm_enabled: bool, -) -> GuestAddress { - trace_scoped!("create_acpi_tables"); - - let start_time = Instant::now(); - let rsdp_offset = arch::layout::RSDP_POINTER; +) -> (Rsdp, Vec, Vec) { + // Generated bytes for ACPI tables + let mut tables_bytes: Vec = Vec::new(); let mut tables: Vec = Vec::new(); // DSDT let dsdt = create_dsdt_table(device_manager, cpu_manager, memory_manager); - let dsdt_offset = rsdp_offset.checked_add(Rsdp::len() as u64).unwrap(); - guest_mem - .write_slice(dsdt.as_slice(), dsdt_offset) - .expect("Error writing DSDT table"); + tables_bytes.extend_from_slice(dsdt.as_slice()); // FACP aka FADT let facp = create_facp_table(dsdt_offset, device_manager); let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap(); - guest_mem - .write_slice(facp.as_slice(), facp_offset) - .expect("Error writing FACP table"); + tables_bytes.extend_from_slice(facp.as_slice()); tables.push(facp_offset.0); // MADT let madt = cpu_manager.lock().unwrap().create_madt(); let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap(); - guest_mem - .write_slice(madt.as_slice(), madt_offset) - .expect("Error writing MADT table"); + tables_bytes.extend_from_slice(madt.as_slice()); tables.push(madt_offset.0); let mut prev_tbl_len = madt.len() as u64; let mut prev_tbl_off = madt_offset; @@ -670,9 +661,7 @@ pub fn create_acpi_tables( { let pptt = cpu_manager.lock().unwrap().create_pptt(); let pptt_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(pptt.as_slice(), pptt_offset) - .expect("Error writing PPTT table"); + tables_bytes.extend_from_slice(pptt.as_slice()); tables.push(pptt_offset.0); prev_tbl_len = pptt.len() as u64; prev_tbl_off = pptt_offset; @@ -683,9 +672,7 @@ pub fn create_acpi_tables( { let gtdt = create_gtdt_table(); let gtdt_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(gtdt.as_slice(), gtdt_offset) - .expect("Error writing GTDT table"); + tables_bytes.extend_from_slice(gtdt.as_slice()); tables.push(gtdt_offset.0); prev_tbl_len = gtdt.len() as u64; prev_tbl_off = gtdt_offset; @@ -694,9 +681,7 @@ pub fn create_acpi_tables( // MCFG let mcfg = create_mcfg_table(device_manager.lock().unwrap().pci_segments()); let mcfg_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(mcfg.as_slice(), mcfg_offset) - .expect("Error writing MCFG table"); + tables_bytes.extend_from_slice(mcfg.as_slice()); tables.push(mcfg_offset.0); prev_tbl_len = mcfg.len() as u64; prev_tbl_off = mcfg_offset; @@ -728,9 +713,7 @@ pub fn create_acpi_tables( // SPCR let spcr = create_spcr_table(serial_device_addr, serial_device_irq); let spcr_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(spcr.as_slice(), spcr_offset) - .expect("Error writing SPCR table"); + tables_bytes.extend_from_slice(spcr.as_slice()); tables.push(spcr_offset.0); prev_tbl_len = spcr.len() as u64; prev_tbl_off = spcr_offset; @@ -738,9 +721,7 @@ pub fn create_acpi_tables( // DBG2 let dbg2 = create_dbg2_table(serial_device_addr); let dbg2_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(dbg2.as_slice(), dbg2_offset) - .expect("Error writing DBG2 table"); + tables_bytes.extend_from_slice(dbg2.as_slice()); tables.push(dbg2_offset.0); prev_tbl_len = dbg2.len() as u64; prev_tbl_off = dbg2_offset; @@ -750,9 +731,7 @@ pub fn create_acpi_tables( // TPM2 Table let tpm2 = create_tpm2_table(); let tpm2_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(tpm2.as_slice(), tpm2_offset) - .expect("Error writing TPM2 table"); + tables_bytes.extend_from_slice(tpm2.as_slice()); tables.push(tpm2_offset.0); prev_tbl_len = tpm2.len() as u64; @@ -770,17 +749,13 @@ pub fn create_acpi_tables( topology, ); let srat_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(srat.as_slice(), srat_offset) - .expect("Error writing SRAT table"); + tables_bytes.extend_from_slice(srat.as_slice()); tables.push(srat_offset.0); // SLIT let slit = create_slit_table(numa_nodes); let slit_offset = srat_offset.checked_add(srat.len() as u64).unwrap(); - guest_mem - .write_slice(slit.as_slice(), slit_offset) - .expect("Error writing SLIT table"); + tables_bytes.extend_from_slice(slit.as_slice()); tables.push(slit_offset.0); prev_tbl_len = slit.len() as u64; @@ -791,9 +766,7 @@ pub fn create_acpi_tables( { let iort = create_iort_table(device_manager.lock().unwrap().pci_segments()); let iort_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(iort.as_slice(), iort_offset) - .expect("Error writing IORT table"); + tables_bytes.extend_from_slice(iort.as_slice()); tables.push(iort_offset.0); prev_tbl_len = iort.len() as u64; prev_tbl_off = iort_offset; @@ -805,9 +778,7 @@ pub fn create_acpi_tables( let viot = create_viot_table(iommu_bdf, devices_bdf); let viot_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(viot.as_slice(), viot_offset) - .expect("Error writing VIOT table"); + tables_bytes.extend_from_slice(viot.as_slice()); tables.push(viot_offset.0); prev_tbl_len = viot.len() as u64; prev_tbl_off = viot_offset; @@ -815,26 +786,56 @@ pub fn create_acpi_tables( // XSDT let mut xsdt = Sdt::new(*b"XSDT", 36, 1, *b"CLOUDH", *b"CHXSDT ", 1); - for table in tables { - xsdt.append(table); + for table in &tables { + xsdt.append(*table); } xsdt.update_checksum(); let xsdt_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); - guest_mem - .write_slice(xsdt.as_slice(), xsdt_offset) - .expect("Error writing XSDT table"); + tables_bytes.extend_from_slice(xsdt.as_slice()); // RSDP let rsdp = Rsdp::new(*b"CLOUDH", xsdt_offset.0); + + (rsdp, tables_bytes, tables) +} + +pub fn create_acpi_tables( + guest_mem: &GuestMemoryMmap, + device_manager: &Arc>, + cpu_manager: &Arc>, + memory_manager: &Arc>, + numa_nodes: &NumaNodes, + tpm_enabled: bool, +) -> GuestAddress { + trace_scoped!("create_acpi_tables"); + + let start_time = Instant::now(); + let rsdp_offset = arch::layout::RSDP_POINTER; + let dsdt_offset = rsdp_offset.checked_add(Rsdp::len() as u64).unwrap(); + + let (rsdp, tables_bytes, _tables_offset) = create_acpi_tables_internal( + dsdt_offset, + device_manager, + cpu_manager, + memory_manager, + numa_nodes, + tpm_enabled, + ); + guest_mem .write_slice(rsdp.as_bytes(), rsdp_offset) .expect("Error writing RSDP"); + guest_mem + .write_slice(tables_bytes.as_slice(), dsdt_offset) + .expect("Error writing ACPI tables"); + info!( "Generated ACPI tables: took {}µs size = {}", Instant::now().duration_since(start_time).as_micros(), - xsdt_offset.0 + xsdt.len() as u64 - rsdp_offset.0 + Rsdp::len() + tables_bytes.len(), ); + rsdp_offset } From abcec231eb5732dc2f9c398cb4175e501135af6b Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Mon, 28 Jul 2025 23:14:11 +0000 Subject: [PATCH 0059/1893] vmm: acpi: Clarify variable naming in 'create_acpi_tables_internal' When generating ACPI tables, we are not using "offset", instead we are using (guest physical) addresses. Signed-off-by: Bo Chen --- vmm/src/acpi.rs | 90 ++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 27d52abeb4..9d7106b8f7 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -627,7 +627,7 @@ fn create_viot_table(iommu_bdf: &PciBdf, devices_bdf: &[PciBdf]) -> Sdt { } fn create_acpi_tables_internal( - dsdt_offset: GuestAddress, + dsdt_addr: GuestAddress, device_manager: &Arc>, cpu_manager: &Arc>, memory_manager: &Arc>, @@ -643,48 +643,48 @@ fn create_acpi_tables_internal( tables_bytes.extend_from_slice(dsdt.as_slice()); // FACP aka FADT - let facp = create_facp_table(dsdt_offset, device_manager); - let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap(); + let facp = create_facp_table(dsdt_addr, device_manager); + let facp_addr = dsdt_addr.checked_add(dsdt.len() as u64).unwrap(); tables_bytes.extend_from_slice(facp.as_slice()); - tables.push(facp_offset.0); + tables.push(facp_addr.0); // MADT let madt = cpu_manager.lock().unwrap().create_madt(); - let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap(); + let madt_addr = facp_addr.checked_add(facp.len() as u64).unwrap(); tables_bytes.extend_from_slice(madt.as_slice()); - tables.push(madt_offset.0); + tables.push(madt_addr.0); let mut prev_tbl_len = madt.len() as u64; - let mut prev_tbl_off = madt_offset; + let mut prev_tbl_addr = madt_addr; // PPTT #[cfg(target_arch = "aarch64")] { let pptt = cpu_manager.lock().unwrap().create_pptt(); - let pptt_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let pptt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(pptt.as_slice()); - tables.push(pptt_offset.0); + tables.push(pptt_addr.0); prev_tbl_len = pptt.len() as u64; - prev_tbl_off = pptt_offset; + prev_tbl_addr = pptt_addr; } // GTDT #[cfg(target_arch = "aarch64")] { let gtdt = create_gtdt_table(); - let gtdt_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let gtdt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(gtdt.as_slice()); - tables.push(gtdt_offset.0); + tables.push(gtdt_addr.0); prev_tbl_len = gtdt.len() as u64; - prev_tbl_off = gtdt_offset; + prev_tbl_addr = gtdt_addr; } // MCFG let mcfg = create_mcfg_table(device_manager.lock().unwrap().pci_segments()); - let mcfg_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let mcfg_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(mcfg.as_slice()); - tables.push(mcfg_offset.0); + tables.push(mcfg_addr.0); prev_tbl_len = mcfg.len() as u64; - prev_tbl_off = mcfg_offset; + prev_tbl_addr = mcfg_addr; // SPCR and DBG2 #[cfg(target_arch = "aarch64")] @@ -712,30 +712,30 @@ fn create_acpi_tables_internal( // SPCR let spcr = create_spcr_table(serial_device_addr, serial_device_irq); - let spcr_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let spcr_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(spcr.as_slice()); - tables.push(spcr_offset.0); + tables.push(spcr_addr.0); prev_tbl_len = spcr.len() as u64; - prev_tbl_off = spcr_offset; + prev_tbl_addr = spcr_addr; // DBG2 let dbg2 = create_dbg2_table(serial_device_addr); - let dbg2_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let dbg2_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(dbg2.as_slice()); - tables.push(dbg2_offset.0); + tables.push(dbg2_addr.0); prev_tbl_len = dbg2.len() as u64; - prev_tbl_off = dbg2_offset; + prev_tbl_addr = dbg2_addr; } if tpm_enabled { // TPM2 Table let tpm2 = create_tpm2_table(); - let tpm2_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let tpm2_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(tpm2.as_slice()); - tables.push(tpm2_offset.0); + tables.push(tpm2_addr.0); prev_tbl_len = tpm2.len() as u64; - prev_tbl_off = tpm2_offset; + prev_tbl_addr = tpm2_addr; } // SRAT and SLIT // Only created if the NUMA nodes list is not empty. @@ -748,28 +748,28 @@ fn create_acpi_tables_internal( #[cfg(target_arch = "x86_64")] topology, ); - let srat_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let srat_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(srat.as_slice()); - tables.push(srat_offset.0); + tables.push(srat_addr.0); // SLIT let slit = create_slit_table(numa_nodes); - let slit_offset = srat_offset.checked_add(srat.len() as u64).unwrap(); + let slit_addr = srat_addr.checked_add(srat.len() as u64).unwrap(); tables_bytes.extend_from_slice(slit.as_slice()); - tables.push(slit_offset.0); + tables.push(slit_addr.0); prev_tbl_len = slit.len() as u64; - prev_tbl_off = slit_offset; + prev_tbl_addr = slit_addr; }; #[cfg(target_arch = "aarch64")] { let iort = create_iort_table(device_manager.lock().unwrap().pci_segments()); - let iort_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let iort_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(iort.as_slice()); - tables.push(iort_offset.0); + tables.push(iort_addr.0); prev_tbl_len = iort.len() as u64; - prev_tbl_off = iort_offset; + prev_tbl_addr = iort_addr; } // VIOT @@ -777,11 +777,11 @@ fn create_acpi_tables_internal( { let viot = create_viot_table(iommu_bdf, devices_bdf); - let viot_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let viot_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(viot.as_slice()); - tables.push(viot_offset.0); + tables.push(viot_addr.0); prev_tbl_len = viot.len() as u64; - prev_tbl_off = viot_offset; + prev_tbl_addr = viot_addr; } // XSDT @@ -790,11 +790,11 @@ fn create_acpi_tables_internal( xsdt.append(*table); } xsdt.update_checksum(); - let xsdt_offset = prev_tbl_off.checked_add(prev_tbl_len).unwrap(); + let xsdt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(xsdt.as_slice()); // RSDP - let rsdp = Rsdp::new(*b"CLOUDH", xsdt_offset.0); + let rsdp = Rsdp::new(*b"CLOUDH", xsdt_addr.0); (rsdp, tables_bytes, tables) } @@ -810,11 +810,11 @@ pub fn create_acpi_tables( trace_scoped!("create_acpi_tables"); let start_time = Instant::now(); - let rsdp_offset = arch::layout::RSDP_POINTER; - let dsdt_offset = rsdp_offset.checked_add(Rsdp::len() as u64).unwrap(); + let rsdp_addr = arch::layout::RSDP_POINTER; + let dsdt_addr = rsdp_addr.checked_add(Rsdp::len() as u64).unwrap(); - let (rsdp, tables_bytes, _tables_offset) = create_acpi_tables_internal( - dsdt_offset, + let (rsdp, tables_bytes, _tables_addr) = create_acpi_tables_internal( + dsdt_addr, device_manager, cpu_manager, memory_manager, @@ -823,11 +823,11 @@ pub fn create_acpi_tables( ); guest_mem - .write_slice(rsdp.as_bytes(), rsdp_offset) + .write_slice(rsdp.as_bytes(), rsdp_addr) .expect("Error writing RSDP"); guest_mem - .write_slice(tables_bytes.as_slice(), dsdt_offset) + .write_slice(tables_bytes.as_slice(), dsdt_addr) .expect("Error writing ACPI tables"); info!( @@ -836,7 +836,7 @@ pub fn create_acpi_tables( Rsdp::len() + tables_bytes.len(), ); - rsdp_offset + rsdp_addr } #[cfg(feature = "tdx")] From 394fd230b06d0ddd832677daae19f6e3f3fd8085 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Mon, 28 Jul 2025 23:18:31 +0000 Subject: [PATCH 0060/1893] vmm: acpi: Clarify the return of 'create_acpi_tables_internal' Signed-off-by: Bo Chen --- vmm/src/acpi.rs | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 9d7106b8f7..2185ee8b32 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -626,6 +626,13 @@ fn create_viot_table(iommu_bdf: &PciBdf, devices_bdf: &[PciBdf]) -> Sdt { viot } +// Generate ACPI tables based on the given DSDT address +// +// # Returns +// +// * `Rsdp` is the generated RSDP. +// * `Vec` contains the generated bytes for ACPI tables. +// * `Vec` contains a list of table pointers stored in XSDT. fn create_acpi_tables_internal( dsdt_addr: GuestAddress, device_manager: &Arc>, @@ -636,7 +643,8 @@ fn create_acpi_tables_internal( ) -> (Rsdp, Vec, Vec) { // Generated bytes for ACPI tables let mut tables_bytes: Vec = Vec::new(); - let mut tables: Vec = Vec::new(); + // List of table pointers stored in XSDT + let mut xsdt_table_pointers: Vec = Vec::new(); // DSDT let dsdt = create_dsdt_table(device_manager, cpu_manager, memory_manager); @@ -646,13 +654,13 @@ fn create_acpi_tables_internal( let facp = create_facp_table(dsdt_addr, device_manager); let facp_addr = dsdt_addr.checked_add(dsdt.len() as u64).unwrap(); tables_bytes.extend_from_slice(facp.as_slice()); - tables.push(facp_addr.0); + xsdt_table_pointers.push(facp_addr.0); // MADT let madt = cpu_manager.lock().unwrap().create_madt(); let madt_addr = facp_addr.checked_add(facp.len() as u64).unwrap(); tables_bytes.extend_from_slice(madt.as_slice()); - tables.push(madt_addr.0); + xsdt_table_pointers.push(madt_addr.0); let mut prev_tbl_len = madt.len() as u64; let mut prev_tbl_addr = madt_addr; @@ -662,7 +670,7 @@ fn create_acpi_tables_internal( let pptt = cpu_manager.lock().unwrap().create_pptt(); let pptt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(pptt.as_slice()); - tables.push(pptt_addr.0); + xsdt_table_pointers.push(pptt_addr.0); prev_tbl_len = pptt.len() as u64; prev_tbl_addr = pptt_addr; } @@ -673,7 +681,7 @@ fn create_acpi_tables_internal( let gtdt = create_gtdt_table(); let gtdt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(gtdt.as_slice()); - tables.push(gtdt_addr.0); + xsdt_table_pointers.push(gtdt_addr.0); prev_tbl_len = gtdt.len() as u64; prev_tbl_addr = gtdt_addr; } @@ -682,7 +690,7 @@ fn create_acpi_tables_internal( let mcfg = create_mcfg_table(device_manager.lock().unwrap().pci_segments()); let mcfg_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(mcfg.as_slice()); - tables.push(mcfg_addr.0); + xsdt_table_pointers.push(mcfg_addr.0); prev_tbl_len = mcfg.len() as u64; prev_tbl_addr = mcfg_addr; @@ -714,7 +722,7 @@ fn create_acpi_tables_internal( let spcr = create_spcr_table(serial_device_addr, serial_device_irq); let spcr_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(spcr.as_slice()); - tables.push(spcr_addr.0); + xsdt_table_pointers.push(spcr_addr.0); prev_tbl_len = spcr.len() as u64; prev_tbl_addr = spcr_addr; @@ -722,7 +730,7 @@ fn create_acpi_tables_internal( let dbg2 = create_dbg2_table(serial_device_addr); let dbg2_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(dbg2.as_slice()); - tables.push(dbg2_addr.0); + xsdt_table_pointers.push(dbg2_addr.0); prev_tbl_len = dbg2.len() as u64; prev_tbl_addr = dbg2_addr; } @@ -732,7 +740,7 @@ fn create_acpi_tables_internal( let tpm2 = create_tpm2_table(); let tpm2_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(tpm2.as_slice()); - tables.push(tpm2_addr.0); + xsdt_table_pointers.push(tpm2_addr.0); prev_tbl_len = tpm2.len() as u64; prev_tbl_addr = tpm2_addr; @@ -750,13 +758,13 @@ fn create_acpi_tables_internal( ); let srat_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(srat.as_slice()); - tables.push(srat_addr.0); + xsdt_table_pointers.push(srat_addr.0); // SLIT let slit = create_slit_table(numa_nodes); let slit_addr = srat_addr.checked_add(srat.len() as u64).unwrap(); tables_bytes.extend_from_slice(slit.as_slice()); - tables.push(slit_addr.0); + xsdt_table_pointers.push(slit_addr.0); prev_tbl_len = slit.len() as u64; prev_tbl_addr = slit_addr; @@ -767,7 +775,7 @@ fn create_acpi_tables_internal( let iort = create_iort_table(device_manager.lock().unwrap().pci_segments()); let iort_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(iort.as_slice()); - tables.push(iort_addr.0); + xsdt_table_pointers.push(iort_addr.0); prev_tbl_len = iort.len() as u64; prev_tbl_addr = iort_addr; } @@ -779,15 +787,15 @@ fn create_acpi_tables_internal( let viot_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(viot.as_slice()); - tables.push(viot_addr.0); + xsdt_table_pointers.push(viot_addr.0); prev_tbl_len = viot.len() as u64; prev_tbl_addr = viot_addr; } // XSDT let mut xsdt = Sdt::new(*b"XSDT", 36, 1, *b"CLOUDH", *b"CHXSDT ", 1); - for table in &tables { - xsdt.append(*table); + for table_pointer in &xsdt_table_pointers { + xsdt.append(*table_pointer); } xsdt.update_checksum(); let xsdt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); @@ -796,7 +804,7 @@ fn create_acpi_tables_internal( // RSDP let rsdp = Rsdp::new(*b"CLOUDH", xsdt_addr.0); - (rsdp, tables_bytes, tables) + (rsdp, tables_bytes, xsdt_table_pointers) } pub fn create_acpi_tables( @@ -813,7 +821,7 @@ pub fn create_acpi_tables( let rsdp_addr = arch::layout::RSDP_POINTER; let dsdt_addr = rsdp_addr.checked_add(Rsdp::len() as u64).unwrap(); - let (rsdp, tables_bytes, _tables_addr) = create_acpi_tables_internal( + let (rsdp, tables_bytes, _xsdt_table_pointers) = create_acpi_tables_internal( dsdt_addr, device_manager, cpu_manager, From cd2c43b4896670cc5bd7ef60a12732c81fd34bdd Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Wed, 6 Aug 2025 16:41:29 +0800 Subject: [PATCH 0061/1893] misc: Fix beta clippy errors Fix clippy error: "error: manual implementation of `.is_multiple_of() `" from rustc 1.90.0-beta.1 (788da80fc 2025-08-04). Signed-off-by: Songqian Li --- arch/src/riscv64/fdt.rs | 5 +---- block/src/lib.rs | 2 +- block/src/qcow/mod.rs | 4 ++-- block/src/qcow/raw_file.rs | 6 +++--- hypervisor/src/kvm/aarch64/gic/dist_regs.rs | 2 +- virtio-devices/src/balloon.rs | 2 +- virtio-devices/src/mem.rs | 14 +++++++------- virtio-devices/src/net.rs | 2 +- virtio-devices/src/vhost_user/net.rs | 2 +- vm-allocator/src/address.rs | 2 +- vm-migration/src/protocol.rs | 2 +- vmm/src/igvm/igvm_loader.rs | 6 +++--- vmm/src/memory_manager.rs | 2 +- 13 files changed, 24 insertions(+), 27 deletions(-) diff --git a/arch/src/riscv64/fdt.rs b/arch/src/riscv64/fdt.rs index 1a7e2e5f46..580aaa7d3f 100644 --- a/arch/src/riscv64/fdt.rs +++ b/arch/src/riscv64/fdt.rs @@ -448,10 +448,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) { // - At first, try to convert it to CStr and print, // - If failed, print it as u32 array. let value_result = match CStr::from_bytes_with_nul(value) { - Ok(value_cstr) => match value_cstr.to_str() { - Ok(value_str) => Some(value_str), - Err(_e) => None, - }, + Ok(value_cstr) => value_cstr.to_str().ok(), Err(_e) => None, }; diff --git a/block/src/lib.rs b/block/src/lib.rs index aed9c0ab3c..4e10771c4d 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -431,7 +431,7 @@ impl Request { // In case it's not properly aligned, an intermediate buffer is // created with the correct alignment, and a copy from/to the // origin buffer is performed, depending on the type of operation. - let iov_base = if (origin_ptr.as_ptr() as u64) % SECTOR_SIZE != 0 { + let iov_base = if !(origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) { let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap(); // SAFETY: layout has non-zero size let aligned_ptr = unsafe { alloc_zeroed(layout) }; diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 7733ff0ceb..4d84918e97 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1705,12 +1705,12 @@ fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> { // Ceiling of the division of `dividend`/`divisor`. fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 { - dividend / divisor + u64::from(dividend % divisor != 0) + dividend / divisor + u64::from(!dividend.is_multiple_of(divisor)) } // Ceiling of the division of `dividend`/`divisor`. fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 { - dividend / divisor + u32::from(dividend % divisor != 0) + dividend / divisor + u32::from(!dividend.is_multiple_of(divisor)) } fn convert_copy(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()> diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index cb96376015..67bc99fca6 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -89,9 +89,9 @@ impl RawFile { let align64: u64 = self.alignment.try_into().unwrap(); - (self.position % align64 == 0) - && ((buf.as_ptr() as usize) % self.alignment == 0) - && (buf.len() % self.alignment == 0) + self.position.is_multiple_of(align64) + && (buf.as_ptr() as usize).is_multiple_of(self.alignment) + && buf.len().is_multiple_of(self.alignment) } pub fn set_len(&self, size: u64) -> std::io::Result<()> { diff --git a/hypervisor/src/kvm/aarch64/gic/dist_regs.rs b/hypervisor/src/kvm/aarch64/gic/dist_regs.rs index 9135ad0031..af2d6022c4 100644 --- a/hypervisor/src/kvm/aarch64/gic/dist_regs.rs +++ b/hypervisor/src/kvm/aarch64/gic/dist_regs.rs @@ -156,7 +156,7 @@ fn compute_reg_len(gic: &DeviceFd, reg: &DistReg, base: u32) -> Result { // that the model has. It is also the type of register where // a register relates to multiple interrupts. end = base + (reg.bpi as u32 * (num_irq - LAYOUT_IRQ_BASE) / 8); - if reg.bpi as u32 * (num_irq - LAYOUT_IRQ_BASE) % 8 > 0 { + if !(reg.bpi as u32 * (num_irq - LAYOUT_IRQ_BASE)).is_multiple_of(8) { end += REG_SIZE as u32; } } diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index 890dfbd9fc..2a34b6688f 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -265,7 +265,7 @@ impl BalloonEpollHandler { error!("The head contains the request type is not right"); return Err(Error::UnexpectedWriteOnlyDescriptor); } - if desc.len() as usize % data_chunk_size != 0 { + if !(desc.len() as usize).is_multiple_of(data_chunk_size) { error!("the request size {} is not right", desc.len()); return Err(Error::InvalidRequest); } diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 5739a12fae..8d5830ac3c 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -193,35 +193,35 @@ unsafe impl ByteValued for VirtioMemConfig {} impl VirtioMemConfig { fn validate(&self) -> result::Result<(), Error> { - if self.addr % self.block_size != 0 { + if !self.addr.is_multiple_of(self.block_size) { return Err(Error::ValidateError(anyhow!( "addr 0x{:x} is not aligned on block_size 0x{:x}", self.addr, self.block_size ))); } - if self.region_size % self.block_size != 0 { + if !self.region_size.is_multiple_of(self.block_size) { return Err(Error::ValidateError(anyhow!( "region_size 0x{:x} is not aligned on block_size 0x{:x}", self.region_size, self.block_size ))); } - if self.usable_region_size % self.block_size != 0 { + if !self.usable_region_size.is_multiple_of(self.block_size) { return Err(Error::ValidateError(anyhow!( "usable_region_size 0x{:x} is not aligned on block_size 0x{:x}", self.usable_region_size, self.block_size ))); } - if self.plugged_size % self.block_size != 0 { + if !self.plugged_size.is_multiple_of(self.block_size) { return Err(Error::ValidateError(anyhow!( "plugged_size 0x{:x} is not aligned on block_size 0x{:x}", self.plugged_size, self.block_size ))); } - if self.requested_size % self.block_size != 0 { + if !self.requested_size.is_multiple_of(self.block_size) { return Err(Error::ValidateError(anyhow!( "requested_size 0x{:x} is not aligned on block_size 0x{:x}", self.requested_size, @@ -244,7 +244,7 @@ impl VirtioMemConfig { size, self.region_size ))); - } else if size % self.block_size != 0 { + } else if !size.is_multiple_of(self.block_size) { return Err(Error::ResizeError(anyhow!( "new size 0x{:x} is not aligned on block_size 0x{:x}", size, @@ -267,7 +267,7 @@ impl VirtioMemConfig { // Start address must be aligned on block_size, the size must be // greater than 0, and all blocks covered by the request must be // in the usable region. - if addr % self.block_size != 0 + if !addr.is_multiple_of(self.block_size) || size == 0 || (addr < self.addr || addr + size > self.addr + self.usable_region_size) { diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 7d2a4a4597..950cedb519 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -706,7 +706,7 @@ impl VirtioDevice for Net { let num_queues = queues.len(); let event_idx = self.common.feature_acked(VIRTIO_RING_F_EVENT_IDX.into()); - if self.common.feature_acked(VIRTIO_NET_F_CTRL_VQ.into()) && num_queues % 2 != 0 { + if self.common.feature_acked(VIRTIO_NET_F_CTRL_VQ.into()) && !num_queues.is_multiple_of(2) { let ctrl_queue_index = num_queues - 1; let (_, mut ctrl_queue, ctrl_queue_evt) = queues.remove(ctrl_queue_index); diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 930f557419..c52d5ca385 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -298,7 +298,7 @@ impl VirtioDevice for Net { let num_queues = queues.len(); let event_idx = self.common.feature_acked(VIRTIO_RING_F_EVENT_IDX.into()); - if self.common.feature_acked(VIRTIO_NET_F_CTRL_VQ.into()) && num_queues % 2 != 0 { + if self.common.feature_acked(VIRTIO_NET_F_CTRL_VQ.into()) && !num_queues.is_multiple_of(2) { let ctrl_queue_index = num_queues - 1; let (_, mut ctrl_queue, ctrl_queue_evt) = queues.remove(ctrl_queue_index); diff --git a/vm-allocator/src/address.rs b/vm-allocator/src/address.rs index a6e11dc63a..9a72afdf98 100644 --- a/vm-allocator/src/address.rs +++ b/vm-allocator/src/address.rs @@ -68,7 +68,7 @@ impl AddressAllocator { } fn align_address(&self, address: GuestAddress, alignment: GuestUsize) -> GuestAddress { - let align_adjust = if address.raw_value() % alignment != 0 { + let align_adjust = if !address.raw_value().is_multiple_of(alignment) { alignment - (address.raw_value() % alignment) } else { 0 diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index 274baf0397..094a8c1a30 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -264,7 +264,7 @@ impl MemoryRangeTable { } pub fn read_from(fd: &mut dyn Read, length: u64) -> Result { - assert!(length as usize % std::mem::size_of::() == 0); + assert!((length as usize).is_multiple_of(size_of::())); let mut data: Vec = Vec::new(); data.resize_with( diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 75eeb33582..805bb94b41 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -66,8 +66,8 @@ enum ParameterAreaState { #[cfg(feature = "sev_snp")] fn igvm_memmap_from_ram_range(ram_range: (u64, u64)) -> IGVM_VHS_MEMORY_MAP_ENTRY { - assert!(ram_range.0 % HV_PAGE_SIZE == 0); - assert!((ram_range.1 - ram_range.0) % HV_PAGE_SIZE == 0); + assert!(ram_range.0.is_multiple_of(HV_PAGE_SIZE)); + assert!((ram_range.1 - ram_range.0).is_multiple_of(HV_PAGE_SIZE)); IGVM_VHS_MEMORY_MAP_ENTRY { starting_gpa_page_number: ram_range.0 / HV_PAGE_SIZE, @@ -179,7 +179,7 @@ pub fn load_igvm( data_type, data, } => { - debug_assert!(data.len() as u64 % HV_PAGE_SIZE == 0); + debug_assert!((data.len() as u64).is_multiple_of(HV_PAGE_SIZE)); // TODO: only 4k or empty page data supported right now assert!(data.len() as u64 == HV_PAGE_SIZE || data.is_empty()); diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index a5ab297182..2aa8e51046 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -1700,7 +1700,7 @@ impl MemoryManager { } // "Inserted" DIMM must have a size that is a multiple of 128MiB - if size % (128 << 20) != 0 { + if !size.is_multiple_of(128 << 20) { return Err(Error::InvalidSize); } From 530719a57a3556dcfac069e1f5091ba7dfda5f6d Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Wed, 6 Aug 2025 17:29:56 +0800 Subject: [PATCH 0062/1893] build: Bump MSRV to 1.87.0 rustc 1.90.0-beta.1 (788da80fc 2025-08-04) suggests using library feature `unsigned_is_multiple_of`. It is stabled in Rust 1.87.0. Update image to 20250807-0 since MSRV in Dockerfile is updated. Signed-off-by: Songqian Li Signed-off-by: Bo Chen --- .github/workflows/build.yaml | 2 +- .github/workflows/docker-image.yaml | 2 +- .github/workflows/preview-riscv64.yaml | 2 +- .github/workflows/release.yaml | 2 +- Cargo.toml | 2 +- resources/Dockerfile | 2 +- scripts/dev_cli.sh | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 070650ba6e..297b2e153d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,7 +15,7 @@ jobs: - stable - beta - nightly - - "1.83.0" + - "1.87.0" target: - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 6891d60997..b5bbdf4284 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -41,7 +41,7 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # generate Docker tags based on the following events/attributes tags: | - type=raw,value=20250412-0 + type=raw,value=20250807-0 type=sha - name: Build and push diff --git a/.github/workflows/preview-riscv64.yaml b/.github/workflows/preview-riscv64.yaml index 84435402a8..6e4c5071e3 100644 --- a/.github/workflows/preview-riscv64.yaml +++ b/.github/workflows/preview-riscv64.yaml @@ -24,7 +24,7 @@ jobs: fetch-depth: 0 - name: Install Rust toolchain - run: /opt/scripts/exec-in-qemu.sh rustup default 1.83.0 + run: /opt/scripts/exec-in-qemu.sh rustup default 1.87.0 - name: Build ${{ matrix.module }} Module (kvm) run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 01d4d6d810..ef1eb3573c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -45,7 +45,7 @@ jobs: target: ${{ matrix.platform.target }} args: ${{ matrix.platform.args }} strip: true - toolchain: "1.83.0" + toolchain: "1.87.0" - name: Copy Release Binaries if: github.event_name == 'create' && github.event.ref_type == 'tag' shell: bash diff --git a/Cargo.toml b/Cargo.toml index 4e1848010f..c19c070769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ version = "47.0.0" # 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. -rust-version = "1.83.0" +rust-version = "1.87.0" [profile.release] codegen-units = 1 diff --git a/resources/Dockerfile b/resources/Dockerfile index ca527857f6..a650c48211 100644 --- a/resources/Dockerfile +++ b/resources/Dockerfile @@ -8,7 +8,7 @@ FROM ubuntu:24.04 AS dev ARG TARGETARCH -ARG RUST_TOOLCHAIN="1.83.0" +ARG RUST_TOOLCHAIN="1.87.0" ARG CLH_SRC_DIR="/cloud-hypervisor" ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build" ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry" diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index db560d529e..3349afa9d8 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -9,7 +9,7 @@ CLI_NAME="Cloud Hypervisor" CTR_IMAGE_TAG="ghcr.io/cloud-hypervisor/cloud-hypervisor" # Needs to match explicit version in docker-image.yaml workflow -CTR_IMAGE_VERSION="20250412-0" +CTR_IMAGE_VERSION="20250807-0" : "${CTR_IMAGE:=${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}}" DOCKER_RUNTIME="docker" From 0bfa1603602e393a7c7849544a7c8f86ca787c4b Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Thu, 7 Aug 2025 11:46:23 +0800 Subject: [PATCH 0063/1893] hypervisor: Replacing pdf link with new available link Replacing pdf link with new available link to fix the failed CI. Signed-off-by: Songqian Li --- hypervisor/src/kvm/aarch64/gic/dist_regs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypervisor/src/kvm/aarch64/gic/dist_regs.rs b/hypervisor/src/kvm/aarch64/gic/dist_regs.rs index af2d6022c4..0aa3da76de 100644 --- a/hypervisor/src/kvm/aarch64/gic/dist_regs.rs +++ b/hypervisor/src/kvm/aarch64/gic/dist_regs.rs @@ -11,7 +11,7 @@ use crate::kvm::kvm_bindings::{ /* Distributor registers as detailed at page 456 from - https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_architecture_specification.pdf. + https://developer.arm.com/documentation/ihi0069/c/?lang=en. Address offsets are relative to the Distributor base address defined by the system memory map. Unless otherwise stated in the register description, all GIC registers are 32-bits wide. From 4f4940b02ac55f1fb48d9ca255ea7c4a7643eceb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 23:35:01 +0000 Subject: [PATCH 0064/1893] build: Bump crate-ci/typos from 1.35.1 to 1.35.2 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.1 to 1.35.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.35.1...v1.35.2) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.35.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 81ad38b626..b02daf8124 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -152,4 +152,4 @@ jobs: steps: - uses: actions/checkout@v4 # Executes "typos ." - - uses: crate-ci/typos@v1.35.1 + - uses: crate-ci/typos@v1.35.2 From 0d9a941a2cc60ea397a19709e246cf8b21172d2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 22:04:48 +0000 Subject: [PATCH 0065/1893] build: Bump redox_users from 0.5.0 to 0.5.2 Bumps redox_users from 0.5.0 to 0.5.2. --- updated-dependencies: - dependency-name: redox_users dependency-version: 0.5.2 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a0499d850..5a372a29d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1765,9 +1765,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.15", "libredox", From f87772cd367ba2b9b5ebac5e4070878879cd948e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 23:31:16 +0000 Subject: [PATCH 0066/1893] build: Bump crate-ci/typos from 1.35.2 to 1.35.3 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.2 to 1.35.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.35.2...v1.35.3) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.35.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index b02daf8124..b4cddb3ce9 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -152,4 +152,4 @@ jobs: steps: - uses: actions/checkout@v4 # Executes "typos ." - - uses: crate-ci/typos@v1.35.2 + - uses: crate-ci/typos@v1.35.3 From e718e0ad36da8147144a225b774a2420ebf70329 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 23:37:02 +0000 Subject: [PATCH 0067/1893] build: Bump async-trait from 0.1.86 to 0.1.88 Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.86 to 0.1.88. - [Release notes](https://github.com/dtolnay/async-trait/releases) - [Commits](https://github.com/dtolnay/async-trait/compare/0.1.86...0.1.88) --- updated-dependencies: - dependency-name: async-trait dependency-version: 0.1.88 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a372a29d6..8173ec1fd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,9 +244,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", From 684fb1dfce04f877dd91efc0319f308ae158b5cb Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Fri, 8 Aug 2025 11:34:03 +0800 Subject: [PATCH 0068/1893] ci: Improve link checker conditions Link checker excludes checks on local links and links with variables. Signed-off-by: Songqian Li --- .lychee.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.lychee.toml b/.lychee.toml index 9eb8f9fdf7..875a861826 100644 --- a/.lychee.toml +++ b/.lychee.toml @@ -14,8 +14,16 @@ exclude = [ # 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://.*", ] +# Exclude loopback addresses +exclude_loopback = true + + max_retries = 3 retry_wait_time = 5 From a637940be73376cedd691a4c79d44d623ae85081 Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Fri, 8 Aug 2025 11:39:05 +0800 Subject: [PATCH 0069/1893] ci: Fix link check failed ### Errors in src/main.rs * [404] | Rejected status code (this depends on your "accept" configuration) : Not Found Signed-off-by: Songqian Li --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 101da1706b..56b48d59b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -553,10 +553,10 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { // handler safe functions (writing to stderr) and manipulating signals. unsafe { signal_hook::low_level::register(signal_hook::consts::SIGSYS, || { - eprint!( + eprintln!( "\n==== Possible seccomp violation ====\n\ Try running with `strace -ff` to identify the cause and open an issue: \ - https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new\n" + https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new" ); signal_hook::low_level::emulate_default_handler(SIGSYS).unwrap(); }) From 9825397350aa027f847f6d3003b72df34a370563 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 25 Jul 2025 21:56:02 -0400 Subject: [PATCH 0070/1893] misc: option_parser: allow quoting special characters The option parser did not allow quoting any special characters, which means that some strings could not be passed via command-line options. Fix this by allowing double quotes to quote all the special characters, such as '@', ',', '[', and ']'. '"' can be escaped by doubling it. The implementation uses some tricks to stay modular. Signed-off-by: Demi Marie Obenour --- option_parser/src/lib.rs | 188 +++++++++++++++++++++++++++++---------- 1 file changed, 140 insertions(+), 48 deletions(-) diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index f83524a20b..ff3d9ffffd 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -9,6 +9,23 @@ use std::str::FromStr; use thiserror::Error; +mod private_trait { + // Voldemort trait that dispatches to `FromStr::from_str` on externally-defined types + // and to custom parsing code for types in this module. + pub trait Parseable + where + Self: Sized, + { + type Err; + // Actually does the parsing, but panics if the input doesn't have + // balanced quotes. This is fine because split_commas checks that the + // input has balanced quotes, and option names cannot contain anything + // that split_commas treats as special. + fn from_str(input: &str) -> Result::Err>; + } +} +use private_trait::Parseable; + #[derive(Default)] pub struct OptionParser { options: HashMap, @@ -34,38 +51,34 @@ type OptionParserResult = std::result::Result; fn split_commas(s: &str) -> OptionParserResult> { let mut list: Vec = Vec::new(); - let mut opened_brackets = 0; + let mut opened_brackets = 0u64; let mut in_quotes = false; let mut current = String::new(); for c in s.trim().chars() { match c { - '[' => { - opened_brackets += 1; - current.push('['); - } + // In quotes, only '"' is special + '"' => in_quotes = !in_quotes, + _ if in_quotes => {} + '[' => opened_brackets += 1, ']' => { - opened_brackets -= 1; - if opened_brackets < 0 { + if opened_brackets < 1 { return Err(OptionParserError::InvalidSyntax(s.to_owned())); } - current.push(']'); + opened_brackets -= 1; } - '"' => in_quotes = !in_quotes, - ',' => { - if opened_brackets > 0 || in_quotes { - current.push(',') - } else { - list.push(current); - current = String::new(); - } + ',' if opened_brackets == 0 => { + list.push(current); + current = String::new(); + continue; } - c => current.push(c), - } + _ => {} + }; + current.push(c); } list.push(current); - if opened_brackets != 0 || in_quotes { + if in_quotes || opened_brackets != 0 { return Err(OptionParserError::InvalidSyntax(s.to_owned())); } @@ -86,7 +99,6 @@ impl OptionParser { for option in split_commas(input)?.iter() { let parts: Vec<&str> = option.splitn(2, '=').collect(); - match self.options.get_mut(parts[0]) { None => return Err(OptionParserError::UnknownOption(parts[0].to_owned())), Some(value) => { @@ -106,6 +118,12 @@ impl OptionParser { } pub fn add(&mut self, option: &str) -> &mut Self { + // Check that option=value has balanced + // quotes and brackets iff value does. + assert!( + !option.contains(['"', '[', ']', '=', ',']), + "forbidden character in option name" + ); self.options.insert( option.to_owned(), OptionParserValue { @@ -133,7 +151,13 @@ impl OptionParser { self.options .get(option) .and_then(|v| v.value.clone()) - .and_then(|s| if s.is_empty() { None } else { Some(s) }) + .and_then(|s| { + if s.is_empty() { + None + } else { + Some(dequote(&s)) + } + }) } pub fn is_set(&self, option: &str) -> bool { @@ -143,12 +167,18 @@ impl OptionParser { .is_some() } - pub fn convert(&self, option: &str) -> OptionParserResult> { - match self.get(option) { + pub fn convert(&self, option: &str) -> OptionParserResult> { + match self.options.get(option).and_then(|v| v.value.as_ref()) { None => Ok(None), - Some(v) => Ok(Some(v.parse().map_err(|_| { - OptionParserError::Conversion(option.to_owned(), v.to_owned()) - })?)), + Some(v) => { + Ok(if v.is_empty() { + None + } else { + Some(Parseable::from_str(v).map_err(|_| { + OptionParserError::Conversion(option.to_owned(), v.to_owned()) + })?) + }) + } } } } @@ -161,7 +191,7 @@ pub enum ToggleParseError { InvalidValue(String), } -impl FromStr for Toggle { +impl Parseable for Toggle { type Err = ToggleParseError; fn from_str(s: &str) -> std::result::Result { @@ -216,7 +246,7 @@ pub enum IntegerListParseError { InvalidValue(String), } -impl FromStr for IntegerList { +impl Parseable for IntegerList { type Err = IntegerListParseError; fn from_str(s: &str) -> std::result::Result { @@ -300,6 +330,7 @@ impl TupleValue for Vec { } } +#[derive(PartialEq, Eq, Debug)] pub struct Tuple(pub Vec<(S, T)>); #[derive(Error, Debug)] @@ -314,31 +345,39 @@ pub enum TupleError { InvalidInteger(#[source] ParseIntError), } -impl FromStr for Tuple { +impl Parseable for Tuple { type Err = TupleError; fn from_str(s: &str) -> std::result::Result { let mut list: Vec<(S, T)> = Vec::new(); - let body = s .trim() .strip_prefix('[') .and_then(|s| s.strip_suffix(']')) .ok_or_else(|| TupleError::InvalidValue(s.to_string()))?; - let tuples_list = split_commas(body).map_err(TupleError::SplitOutsideBrackets)?; for tuple in tuples_list.iter() { - let items: Vec<&str> = tuple.split('@').collect(); - - if items.len() != 2 { - return Err(TupleError::InvalidValue((*tuple).to_string())); + let mut in_quotes = false; + let mut last_idx = 0; + let mut first_val = None; + for (idx, c) in tuple.as_bytes().iter().enumerate() { + match c { + b'"' => in_quotes = !in_quotes, + b'@' if !in_quotes => { + if last_idx != 0 { + return Err(TupleError::InvalidValue((*tuple).to_string())); + } + first_val = Some(&tuple[last_idx..idx]); + last_idx = idx + 1; + } + _ => {} + } } - - let item1 = items[0] - .parse::() - .map_err(|_| TupleError::InvalidValue(items[0].to_owned()))?; - let item2 = TupleValue::parse_value(items[1])?; - + let item1 = ::from_str( + first_val.ok_or(TupleError::InvalidValue((*tuple).to_string()))?, + ) + .map_err(|_| TupleError::InvalidValue(first_val.unwrap().to_owned()))?; + let item2 = TupleValue::parse_value(&tuple[last_idx..])?; list.push((item1, item2)); } @@ -355,16 +394,48 @@ pub enum StringListParseError { InvalidValue(String), } -impl FromStr for StringList { +fn dequote(s: &str) -> String { + let mut prev_byte = b'\0'; + let mut in_quotes = false; + let mut out: Vec = vec![]; + for i in s.bytes() { + if i == b'"' { + if prev_byte == b'"' && !in_quotes { + out.push(b'"'); + } + in_quotes = !in_quotes; + } else { + out.push(i); + } + prev_byte = i + } + assert!(!in_quotes, "split_commas didn't reject unbalanced quotes"); + // SAFETY: the non-ASCII bytes in the output are the same + // and in the same order as those in the input, so if the + // input is valid UTF-8 the output will be as well. + unsafe { String::from_utf8_unchecked(out) } +} + +impl Parseable for T +where + T: FromStr + Sized, +{ + type Err = ::Err; + fn from_str(s: &str) -> std::result::Result { + dequote(s).parse() + } +} + +impl Parseable for StringList { type Err = StringListParseError; fn from_str(s: &str) -> std::result::Result { - let string_list: Vec = s - .trim() - .trim_matches(|c| c == '[' || c == ']') - .split(',') - .map(|e| e.to_owned()) - .collect(); + let string_list: Vec = + split_commas(s.trim().trim_matches(|c| c == '[' || c == ']')) + .map_err(|_| StringListParseError::InvalidValue(s.to_owned()))? + .iter() + .map(|e| e.to_owned()) + .collect(); Ok(StringList(string_list)) } @@ -385,6 +456,7 @@ mod tests { .add("topology") .add("cmdline"); + assert_eq!(split_commas("\"\"").unwrap(), vec!["\"\""]); parser.parse("size=128M,hanging_param").unwrap_err(); parser .parse("size=128M,too_many_equals=foo=bar") @@ -395,6 +467,8 @@ mod tests { assert_eq!(parser.get("size"), Some("128M".to_owned())); assert!(!parser.is_set("mergeable")); assert!(parser.is_set("size")); + parser.parse("size=").unwrap(); + assert!(parser.get("size").is_none()); parser.parse("size=128M,mergeable=on").unwrap(); assert_eq!(parser.get("size"), Some("128M".to_owned())); @@ -416,6 +490,14 @@ mod tests { parser.parse("topology=[").unwrap_err(); parser.parse("topology=[[[]]]]").unwrap_err(); + parser.parse("topology=[\"@\"\"b\"@[1,2]]").unwrap(); + assert_eq!( + parser + .convert::>>("topology") + .unwrap() + .unwrap(), + Tuple(vec![("@\"b".to_owned(), vec![1, 2])]) + ); parser.parse("cmdline=\"console=ttyS0,9600n8\"").unwrap(); assert_eq!( @@ -425,4 +507,14 @@ mod tests { parser.parse("cmdline=\"").unwrap_err(); parser.parse("cmdline=\"\"\"").unwrap_err(); } + + #[test] + fn parse_bytes() { + assert_eq!(::from_str("a=\"b\"").unwrap(), "a=b"); + } + + #[test] + fn check_dequote() { + assert_eq!(dequote("a\u{3b2}\"a\"\"\""), "a\u{3b2}a\"") + } } From 2524b015b8cdc4f95b459f3e3b78fc1e2508c97b Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Sun, 10 Aug 2025 23:54:37 +0000 Subject: [PATCH 0071/1893] arch: riscv: Introduce UEFI related constants Set UEFI_START and UEFI_SIZE for riscv64 layout. Signed-off-by: Ruoqing He --- arch/src/riscv64/layout.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arch/src/riscv64/layout.rs b/arch/src/riscv64/layout.rs index 40583301c1..3ef7eddf2c 100644 --- a/arch/src/riscv64/layout.rs +++ b/arch/src/riscv64/layout.rs @@ -44,16 +44,23 @@ // | | // | APLICs | // | | +// 4 MB +---------------------------------------------------------------+ +// | UEFI flash | // 0 GB +---------------------------------------------------------------+ // // use vm_memory::GuestAddress; +/// 0x0 ~ 0x40_0000 (4 MiB) is reserved to UEFI +/// UEFI binary size is required less than 3 MiB, reserving 4 MiB is enough. +pub const UEFI_START: GuestAddress = GuestAddress(0); +pub const UEFI_SIZE: u64 = 0x040_0000; + /// AIA related devices /// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h -/// 0x0 ~ 0x0400_0000 (64 MiB) resides APLICs -pub const APLIC_START: GuestAddress = GuestAddress(0); +/// 0x40_0000 ~ 0x0400_0000 (64 MiB) resides APLICs +pub const APLIC_START: GuestAddress = GuestAddress(0x40_0000); pub const APLIC_SIZE: u64 = 0x4000; /// 0x0400_0000 ~ 0x0800_0000 (64 MiB) resides IMSICs From ef2bbe5012b778f5e9fe4f56752f79075965c7c8 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Mon, 11 Aug 2025 00:01:03 +0000 Subject: [PATCH 0072/1893] arch: riscv: Introduce UEFI module Provide Error definitions and load_uefi to be referenced while loading firmware. Signed-off-by: Ruoqing He --- arch/src/riscv64/mod.rs | 2 ++ arch/src/riscv64/uefi.rs | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 arch/src/riscv64/uefi.rs diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index a04cf9471f..128698961d 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -7,6 +7,8 @@ pub mod fdt; /// Layout for this riscv64 system. pub mod layout; +/// Module for loading UEFI binary. +pub mod uefi; use std::collections::HashMap; use std::fmt::Debug; diff --git a/arch/src/riscv64/uefi.rs b/arch/src/riscv64/uefi.rs new file mode 100644 index 0000000000..bd40e36ff0 --- /dev/null +++ b/arch/src/riscv64/uefi.rs @@ -0,0 +1,50 @@ +// Copyright 2020 Arm Limited (or its affiliates). All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use std::io::{Read, Seek, SeekFrom}; +use std::os::fd::AsFd; +use std::result; + +use thiserror::Error; +use vm_memory::{GuestAddress, GuestMemory}; + +/// Errors thrown while loading UEFI binary +#[derive(Debug, Error)] +pub enum Error { + /// Unable to seek to UEFI image start. + #[error("Unable to seek to UEFI image start")] + SeekUefiStart, + /// Unable to seek to UEFI image end. + #[error("Unable to seek to UEFI image end")] + SeekUefiEnd, + /// UEFI image too big. + #[error("UEFI image too big")] + UefiTooBig, + /// Unable to read UEFI image + #[error("Unable to read UEFI image")] + ReadUefiImage, +} +type Result = result::Result; + +pub fn load_uefi( + guest_mem: &M, + guest_addr: GuestAddress, + uefi_image: &mut F, +) -> Result<()> +where + F: Read + Seek + AsFd, +{ + let uefi_size = uefi_image + .seek(SeekFrom::End(0)) + .map_err(|_| Error::SeekUefiEnd)? as usize; + + // edk2 image on virtual platform is smaller than 3M + if uefi_size > 0x300000 { + return Err(Error::UefiTooBig); + } + uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?; + guest_mem + .read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size) + .map_err(|_| Error::ReadUefiImage) +} From 2e0ec8095c78f0e15cd56fd7e8c935c1bb0f3753 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Mon, 11 Aug 2025 00:07:43 +0000 Subject: [PATCH 0073/1893] vmm: Enable uefi_flash field for riscv64 uefi_flash field in memory_manager is required for uefi loading and booting, enable it for riscv64 architecture. Signed-off-by: Ruoqing He --- vmm/src/memory_manager.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 2aa8e51046..cdc5810445 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -199,7 +199,7 @@ pub struct MemoryManager { guest_ram_mappings: Vec, pub acpi_address: Option, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] uefi_flash: Option>, } @@ -1274,7 +1274,7 @@ impl MemoryManager { arch_mem_regions, ram_allocator, dynamic, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] uefi_flash: None, thp: config.thp, }; @@ -2192,7 +2192,7 @@ impl MemoryManager { self.guest_ram_mappings.len() as u32 } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub fn uefi_flash(&self) -> GuestMemoryAtomic { self.uefi_flash.as_ref().unwrap().clone() } From 0df4b1ac4f73e1c64ffc3813904d60e6966fb4c0 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Mon, 11 Aug 2025 00:09:22 +0000 Subject: [PATCH 0074/1893] vmm: Define riscv64 UEFI Error Error::UefiLoad is required for load_firmware to propagate errors encountered, define it for riscv64. Signed-off-by: Ruoqing He --- vmm/src/vm.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 012ebef05e..60ad39bc2f 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -116,6 +116,10 @@ pub enum Error { #[error("Cannot load the UEFI binary in memory")] UefiLoad(#[source] arch::aarch64::uefi::Error), + #[cfg(target_arch = "riscv64")] + #[error("Cannot load the UEFI binary in memory")] + UefiLoad(#[source] arch::riscv64::uefi::Error), + #[error("Cannot load the initramfs into memory")] InitramfsLoad, From 17195e1a460e8978790f11eec80c5db3ed3f3d02 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Mon, 11 Aug 2025 00:23:41 +0000 Subject: [PATCH 0075/1893] vmm: Enable firmware boot for riscv64 Implement firmware boot (UEFI boot) for riscv64 architecture. Signed-off-by: Ruoqing He --- vmm/src/vm.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 60ad39bc2f..e8354c5cd6 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1085,6 +1085,15 @@ impl Vm { Ok(EntryPoint { entry_addr }) } + #[cfg(target_arch = "riscv64")] + fn load_firmware(mut firmware: &File, memory_manager: Arc>) -> Result<()> { + let uefi_flash = memory_manager.lock().as_ref().unwrap().uefi_flash(); + let mem = uefi_flash.memory(); + arch::riscv64::uefi::load_uefi(mem.deref(), arch::layout::UEFI_START, &mut firmware) + .map_err(Error::UefiLoad)?; + Ok(()) + } + #[cfg(target_arch = "riscv64")] fn load_kernel( firmware: Option, @@ -1108,17 +1117,17 @@ impl Vm { // If failed, retry to load it as UEFI binary. // As the UEFI binary is formatless, it must be the last option to try. Err(linux_loader::loader::Error::Pe(InvalidImageMagicNumber)) => { - // TODO: UEFI for riscv64 is scheduled to next stage. - unimplemented!() + Self::load_firmware(&kernel, memory_manager)?; + arch::layout::UEFI_START } Err(e) => { return Err(Error::KernelLoad(e)); } } } - (Some(_firmware), None) => { - // TODO: UEFI for riscv64 is scheduled to next stage. - unimplemented!() + (Some(firmware), None) => { + Self::load_firmware(&firmware, memory_manager)?; + arch::layout::UEFI_START } _ => return Err(Error::InvalidPayload), }; From 5226ceb9741d9328065f6cc47410283b93cc1144 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 11 Aug 2025 11:50:08 +0200 Subject: [PATCH 0076/1893] misc: switch error output from error! back to stderr This partially reverts ed8f347fe62edd33355ad771615296ff8edc8d33 from #7183 and 6277d7d5f20126945904fefdf5fb990bbcce5ae8 from #7201. # Output how it was merged for v47 (#7066) ``` Error: Cloud Hypervisor exited with the following chain of errors: 0: Error booting VM 1: The VM could not boot 2: Error manipulating firmware file 3: No such file or directory (os error 2) Debug Info: VmBoot(VmBoot(FirmwareFile(Os { code: 2, kind: NotFound, message: "No such file or directory" }))) ``` # Output after #7183 and #7201 ``` cloud-hypervisor: 31.385730ms:
ERROR:/home/pschuster/dev/cloud-hypervisor/src/lib.rs:27 -- Error: Cloud Hypervisor exited with the following chain of errors: cloud-hypervisor: 31.417961ms:
ERROR:/home/pschuster/dev/cloud-hypervisor/src/lib.rs:39 -- 0: Error booting VM cloud-hypervisor: 31.448078ms:
ERROR:/home/pschuster/dev/cloud-hypervisor/src/lib.rs:39 -- 1: The VM could not boot cloud-hypervisor: 31.486711ms:
ERROR:/home/pschuster/dev/cloud-hypervisor/src/lib.rs:39 -- 2: Error manipulating firmware file cloud-hypervisor: 31.513331ms:
ERROR:/home/pschuster/dev/cloud-hypervisor/src/lib.rs:39 -- 3: No such file or directory (os error 2) cloud-hypervisor: 31.548037ms:
ERROR:/home/pschuster/dev/cloud-hypervisor/src/lib.rs:44 -- cloud-hypervisor: 31.568045ms:
ERROR:/home/pschuster/dev/cloud-hypervisor/src/lib.rs:45 -- Debug Info: VmBoot(VmBoot(FirmwareFile(Os { code: 2, kind: NotFound, message: "No such file or directory" }))) ``` The "proper logger" has indeed the advantage that messages can be gracefully redirected to log files etc. However, this makes the error message hardly readable. Therefore, I propose to use error!() only for runtime errors messages but not a pretty-printed version of those. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b8065083de..836ed8e2ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,12 +19,15 @@ pub fn cli_print_error_chain<'a>( &'a (dyn Error + 'static), ) -> Option, ) { - let msg = format!("Error: {component} exited with the following"); + // Debug info. + error!("Fatal error: {top_error:?}"); + + eprint!("Error: {component} exited with the following "); if top_error.source().is_none() { - error!("{msg} error:"); - error!(" {top_error}"); + eprintln!("error:"); + eprintln!(" {top_error}"); } else { - error!("{msg} chain of errors:"); + eprintln!("chain of errors:"); std::iter::successors(Some(top_error), |sub_error| { // Dereference necessary to mitigate rustc compiler bug. // See @@ -34,13 +37,10 @@ pub fn cli_print_error_chain<'a>( .for_each(|(level, error)| { // Special case: handling of HTTP Server responses in ch-remote if let Some(message) = display_modifier(level, 2, error) { - error!("{message}"); + eprintln!("{message}"); } else { - error!(" {level}: {error}"); + eprintln!(" {level}: {error}"); } }); } - - error!(""); - error!("Debug Info: {top_error:?}"); } From 777b7ee11eab34c4ce219861e0b26c8f13f4b1cb Mon Sep 17 00:00:00 2001 From: Alex Orozco Date: Mon, 24 Mar 2025 19:02:50 +0000 Subject: [PATCH 0077/1893] devices: Add fw_cfg device Here we add the fw_cfg device as a legacy device to the device manager. It is guarded behind a fw_cfg flag in vmm at creation of the DeviceManager. In this cl we implement the fw_cfg device with one function (signature). Signed-off-by: Alex Orozco --- Cargo.lock | 1 + Cargo.toml | 1 + devices/Cargo.toml | 5 + devices/src/legacy/fw_cfg.rs | 309 +++++++++++++++++++++++++++++++++++ devices/src/legacy/mod.rs | 4 + docs/fw_cfg.md | 77 +++++++++ src/main.rs | 2 + vmm/Cargo.toml | 1 + vmm/src/device_manager.rs | 46 ++++++ vmm/src/lib.rs | 2 + vmm/src/vm.rs | 7 + 11 files changed, 455 insertions(+) create mode 100644 devices/src/legacy/fw_cfg.rs create mode 100644 docs/fw_cfg.md diff --git a/Cargo.lock b/Cargo.lock index 8173ec1fd1..f26735d3cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -542,6 +542,7 @@ dependencies = [ "vm-memory", "vm-migration", "vmm-sys-util", + "zerocopy 0.8.26", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c19c070769..3fdfef690d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ wait-timeout = { workspace = true } dbus_api = ["vmm/dbus_api", "zbus"] default = ["io_uring", "kvm"] dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling +fw_cfg = ["vmm/fw_cfg"] guest_debug = ["vmm/guest_debug"] igvm = ["mshv", "vmm/igvm"] io_uring = ["vmm/io_uring"] diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 0c32e468e7..dc025948cc 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -28,11 +28,16 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } +zerocopy = { version = "0.8.26", features = [ + "alloc", + "derive", +], optional = true } [target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies] arch = { path = "../arch" } [features] default = [] +fw_cfg = ["zerocopy"] kvm = ["arch/kvm"] pvmemcontrol = [] diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs new file mode 100644 index 0000000000..8810c14353 --- /dev/null +++ b/devices/src/legacy/fw_cfg.rs @@ -0,0 +1,309 @@ +// Copyright 2025 Google LLC. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Cloud Hypervisor implementation of Qemu's fw_cfg spec +/// https://www.qemu.org/docs/master/specs/fw_cfg.html +/// Linux kernel fw_cfg driver header +/// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h +/// Uploading files to the guest via fw_cfg is supported for all kernels 4.6+ w/ CONFIG_FW_CFG_SYSFS enabled +/// https://cateee.net/lkddb/web-lkddb/FW_CFG_SYSFS.html +/// No kernel requirement if above functionality is not required, +/// only firmware must implement mechanism to interact with this fw_cfg device +use std::{ + fs::File, + io::Result, + mem::size_of_val, + os::unix::fs::FileExt, + sync::{Arc, Barrier}, +}; + +use vm_device::BusDevice; +use vmm_sys_util::sock_ctrl_msg::IntoIovec; +use zerocopy::{FromBytes, IntoBytes}; + +#[cfg(target_arch = "x86_64")] +const PORT_FW_CFG_SELECTOR: u64 = 0x510; +#[cfg(target_arch = "x86_64")] +const PORT_FW_CFG_DATA: u64 = 0x511; +#[cfg(target_arch = "x86_64")] +const PORT_FW_CFG_DMA_HI: u64 = 0x514; +#[cfg(target_arch = "x86_64")] +const PORT_FW_CFG_DMA_LO: u64 = 0x518; +#[cfg(target_arch = "x86_64")] +pub const PORT_FW_CFG_BASE: u64 = 0x510; +#[cfg(target_arch = "x86_64")] +pub const PORT_FW_CFG_WIDTH: u64 = 0xc; +#[cfg(target_arch = "aarch64")] +const PORT_FW_CFG_SELECTOR: u64 = 0x9030008; +#[cfg(target_arch = "aarch64")] +const PORT_FW_CFG_DATA: u64 = 0x9030000; +#[cfg(target_arch = "aarch64")] +const PORT_FW_CFG_DMA_HI: u64 = 0x9030010; +#[cfg(target_arch = "aarch64")] +const PORT_FW_CFG_DMA_LO: u64 = 0x9030014; +#[cfg(target_arch = "aarch64")] +pub const PORT_FW_CFG_BASE: u64 = 0x9030000; +#[cfg(target_arch = "aarch64")] +pub const PORT_FW_CFG_WIDTH: u64 = 0x10; + +const FW_CFG_SIGNATURE: u16 = 0x00; +const FW_CFG_ID: u16 = 0x01; +const FW_CFG_FILE_DIR: u16 = 0x19; +const FW_CFG_KNOWN_ITEMS: usize = 0x20; + +pub const FW_CFG_FILE_FIRST: u16 = 0x20; +pub const FW_CFG_DMA_SIGNATURE: [u8; 8] = *b"QEMU CFG"; +// Reserved (must be enabled) +const FW_CFG_F_RESERVED: u8 = 1 << 0; +// DMA Toggle Bit (enabled by default) +const FW_CFG_F_DMA: u8 = 1 << 1; +pub const FW_CFG_FEATURE: [u8; 4] = [FW_CFG_F_RESERVED | FW_CFG_F_DMA, 0, 0, 0]; + +#[derive(Debug)] +pub enum FwCfgContent { + Bytes(Vec), + Slice(&'static [u8]), + File(u64, File), + U32(u32), +} + +impl Default for FwCfgContent { + fn default() -> Self { + FwCfgContent::Slice(&[]) + } +} + +impl FwCfgContent { + fn size(&self) -> Result { + let ret = match self { + FwCfgContent::Bytes(v) => v.len(), + FwCfgContent::File(offset, f) => (f.metadata()?.len() - offset) as usize, + FwCfgContent::Slice(s) => s.len(), + FwCfgContent::U32(n) => size_of_val(n), + }; + u32::try_from(ret).map_err(|_| std::io::ErrorKind::InvalidInput.into()) + } +} + +#[derive(Debug, Default)] +pub struct FwCfgItem { + pub name: String, + pub content: FwCfgContent, +} + +/// https://www.qemu.org/docs/master/specs/fw_cfg.html +#[derive(Debug, Default)] +pub struct FwCfg { + selector: u16, + data_offset: u32, + items: Vec, // 0x20 and above + known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19 +} + +#[repr(C)] +#[derive(Debug, IntoBytes, FromBytes)] +struct FwCfgFilesHeader { + count_be: u32, +} + +pub const FILE_NAME_SIZE: usize = 56; + +pub fn create_file_name(name: &str) -> [u8; FILE_NAME_SIZE] { + let mut c_name = [0u8; FILE_NAME_SIZE]; + let c_len = std::cmp::min(FILE_NAME_SIZE - 1, name.len()); + c_name[0..c_len].copy_from_slice(&name.as_bytes()[0..c_len]); + c_name +} + +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(Debug, IntoBytes, FromBytes, Clone, Copy)] +struct BootE820Entry { + addr: u64, + size: u64, + type_: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, FromBytes)] +struct FwCfgFile { + size_be: u32, + select_be: u16, + _reserved: u16, + name: [u8; FILE_NAME_SIZE], +} + +impl FwCfg { + pub fn new() -> FwCfg { + const DEFAULT_ITEM: FwCfgContent = FwCfgContent::Slice(&[]); + let mut known_items = [DEFAULT_ITEM; FW_CFG_KNOWN_ITEMS]; + known_items[FW_CFG_SIGNATURE as usize] = FwCfgContent::Slice(&FW_CFG_DMA_SIGNATURE); + known_items[FW_CFG_ID as usize] = FwCfgContent::Slice(&FW_CFG_FEATURE); + let file_buf = Vec::from(FwCfgFilesHeader { count_be: 0 }.as_mut_bytes()); + known_items[FW_CFG_FILE_DIR as usize] = FwCfgContent::Bytes(file_buf); + + FwCfg { + selector: 0, + data_offset: 0, + items: vec![], + known_items, + } + } + + fn file_dir_mut(&mut self) -> &mut Vec { + let FwCfgContent::Bytes(file_buf) = &mut self.known_items[FW_CFG_FILE_DIR as usize] else { + unreachable!("fw_cfg: selector {FW_CFG_FILE_DIR:#x} should be FwCfgContent::Byte!") + }; + file_buf + } + + fn update_count(&mut self) { + let mut header = FwCfgFilesHeader { + count_be: (self.items.len() as u32).to_be(), + }; + self.file_dir_mut()[0..4].copy_from_slice(header.as_mut_bytes()); + } + + pub fn add_item(&mut self, item: FwCfgItem) -> Result<()> { + let index = self.items.len(); + let c_name = create_file_name(&item.name); + let size = item.content.size()?; + let mut cfg_file = FwCfgFile { + size_be: size.to_be(), + select_be: (FW_CFG_FILE_FIRST + index as u16).to_be(), + _reserved: 0, + name: c_name, + }; + self.file_dir_mut() + .extend_from_slice(cfg_file.as_mut_bytes()); + self.items.push(item); + self.update_count(); + Ok(()) + } + + fn read_content(content: &FwCfgContent, offset: u32, data: &mut [u8], size: u32) -> Option { + let start = offset as usize; + let end = start + size as usize; + match content { + FwCfgContent::Bytes(b) => { + if b.len() >= size as usize { + data.copy_from_slice(&b[start..end]); + } + } + FwCfgContent::Slice(s) => { + if s.len() >= size as usize { + data.copy_from_slice(&s[start..end]); + } + } + FwCfgContent::File(o, f) => { + f.read_exact_at(data, o + offset as u64).ok()?; + } + FwCfgContent::U32(n) => { + let bytes = n.to_le_bytes(); + data.copy_from_slice(&bytes[start..end]); + } + }; + Some(size as u8) + } + + fn read_data(&mut self, data: &mut [u8], size: u32) -> u8 { + let ret = if let Some(content) = self.known_items.get(self.selector as usize) { + Self::read_content(content, self.data_offset, data, size) + } else if let Some(item) = self.items.get((self.selector - FW_CFG_FILE_FIRST) as usize) { + Self::read_content(&item.content, self.data_offset, data, size) + } else { + error!("fw_cfg: selector {:#x} does not exist.", self.selector); + None + }; + if let Some(val) = ret { + self.data_offset += size; + val + } else { + 0 + } + } +} + +impl BusDevice for FwCfg { + fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { + let port = offset + PORT_FW_CFG_BASE; + let size = data.len(); + match (port, size) { + (PORT_FW_CFG_SELECTOR, _) => { + error!("fw_cfg: selector register is write-only."); + } + (PORT_FW_CFG_DATA, _) => _ = self.read_data(data, size as u32), + (PORT_FW_CFG_DMA_HI, 4) => { + unimplemented!() + } + (PORT_FW_CFG_DMA_LO, 4) => { + unimplemented!() + } + _ => { + debug!("fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}."); + } + }; + } + + fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { + let port = offset + PORT_FW_CFG_BASE; + let size = data.size(); + match (port, size) { + (PORT_FW_CFG_SELECTOR, 2) => { + let mut buf = [0u8; 2]; + buf[..size].copy_from_slice(&data[..size]); + #[cfg(target_arch = "x86_64")] + let val = u16::from_le_bytes(buf); + #[cfg(target_arch = "aarch64")] + let val = u16::from_be_bytes(buf); + self.selector = val; + self.data_offset = 0; + } + (PORT_FW_CFG_DATA, 1) => error!("fw_cfg: data register is read-only."), + (PORT_FW_CFG_DMA_HI, 4) => { + unimplemented!() + } + (PORT_FW_CFG_DMA_LO, 4) => { + unimplemented!() + } + _ => debug!( + "fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ." + ), + }; + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(target_arch = "x86_64")] + const SELECTOR_OFFSET: u64 = 0; + #[cfg(target_arch = "aarch64")] + const SELECTOR_OFFSET: u64 = 8; + #[cfg(target_arch = "x86_64")] + const DATA_OFFSET: u64 = 1; + #[cfg(target_arch = "aarch64")] + const DATA_OFFSET: u64 = 0; + + #[test] + fn test_signature() { + let mut fw_cfg = FwCfg::new(); + + let mut data = vec![0u8]; + + let mut sig_iter = FW_CFG_DMA_SIGNATURE.into_iter(); + fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_SIGNATURE as u8, 0]); + loop { + if let Some(char) = sig_iter.next() { + fw_cfg.read(0, DATA_OFFSET, &mut data); + assert_eq!(data[0], char); + } else { + return; + } + } + } +} diff --git a/devices/src/legacy/mod.rs b/devices/src/legacy/mod.rs index 3f58e5c842..1087d3d27d 100644 --- a/devices/src/legacy/mod.rs +++ b/devices/src/legacy/mod.rs @@ -8,6 +8,8 @@ mod cmos; #[cfg(target_arch = "x86_64")] mod debug_port; +#[cfg(feature = "fw_cfg")] +pub mod fw_cfg; #[cfg(target_arch = "x86_64")] mod fwdebug; #[cfg(target_arch = "aarch64")] @@ -22,6 +24,8 @@ mod uart_pl011; pub use self::cmos::Cmos; #[cfg(target_arch = "x86_64")] pub use self::debug_port::DebugPort; +#[cfg(feature = "fw_cfg")] +pub use self::fw_cfg::FwCfg; #[cfg(target_arch = "x86_64")] pub use self::fwdebug::FwDebugDevice; #[cfg(target_arch = "aarch64")] diff --git a/docs/fw_cfg.md b/docs/fw_cfg.md new file mode 100644 index 0000000000..73f10a7808 --- /dev/null +++ b/docs/fw_cfg.md @@ -0,0 +1,77 @@ +# Firmware Configuration (fw_cfg) Device + +The `fw_cfg` device is a QEMU-compatible device that allows the hypervisor to pass configuration and data to the guest operating system. This is particularly useful for firmware to access information like ACPI tables, kernel images, initramfs, kernel command lines, and other arbitrary data blobs. + +Cloud Hypervisor implements the `fw_cfg` device with DMA-enabled access. + +## Purpose + +The `fw_cfg` device serves as a generic information channel between the VMM and the guest. It can be used to: + +* Load the kernel, initramfs, and kernel command line for direct kernel boot with firmware. +* Provide ACPI tables to the guest firmware or OS. +* Pass custom configuration files or data blobs (e.g., attestation data, SEV-SNP launch secrets) to the guest. +* Supply an E820 memory map to the guest. + +## Enabling `fw_cfg` + +The `fw_cfg` device is enabled via the `fw_cfg` feature flag when building Cloud Hypervisor: + +```bash +cargo build --features fw_cfg +``` + +## Guest Kernel Configuration + +For the guest Linux kernel to recognize and use the `fw_cfg` device via sysfs, the following kernel configuration option must be enabled: + +* `CONFIG_FW_CFG_SYSFS=y` + +This option allows the kernel to expose `fw_cfg` entries under `/sys/firmware/qemu_fw_cfg/by_name/`. + +## Command Line Options + +The `fw_cfg` device is configured using the `--fw-cfg-config` command-line option. + +**Parameters:** +* `e820=on|off`: (Default: `on`) Whether to add an E820 memory map entry to `fw_cfg`. +* `kernel=on|off`: (Default: `on`) Whether to add the kernel image (specified by `--kernel`) to `fw_cfg`. +* `cmdline=on|off`: (Default: `on`) Whether to add the kernel command line (specified by `--cmdline`) to `fw_cfg`. +* `initramfs=on|off`: (Default: `on`) Whether to add the initramfs image (specified by `--initramfs`) to `fw_cfg`. +* `acpi_table=on|off`: (Default: `on`) Whether to add generated ACPI tables to `fw_cfg`. +* `items=[... : ...]`: A list of custom key-value pairs to be exposed via `fw_cfg`. + * `name=`: The path under which the item will appear in the guest's sysfs (e.g., `opt/org.example/my-data`). + * `file=`: The path to the file on the host whose content will be provided to the guest for this item. + +**Example Usage:** + +1. **Direct kernel boot with custom `fw_cfg` entries:** + + ```bash + cloud-hypervisor \ + --kernel /path/to/vmlinux \ + --cmdline "console=hvc0 root=/dev/vda1" \ + --disk path=/path/to/rootfs.img \ + --fw-cfg-config initramfs=off,items=[name=opt/org.mycorp/setup_info,file=/tmp/guest_setup.txt] \ + ... + ``` + In the guest, `/tmp/guest_setup.txt` from the host will be accessible at `/sys/firmware/qemu_fw_cfg/by_name/opt/org.mycorp/setup_info/raw`. + +2. **Disabling `fw_cfg` explicitly:** + + ```bash + cloud-hypervisor \ + --fw-cfg-config disable \ + ... + ``` + +## Accessing `fw_cfg` Items in the Guest + +If `CONFIG_FW_CFG_SYSFS` is enabled in the guest kernel, items added to `fw_cfg` can be accessed via sysfs. + +For example, an item added with `name=opt/org.example/my-data` will be available at: +`/sys/firmware/qemu_fw_cfg/by_name/opt/org.example/my-data/raw` + +The `raw` file contains the binary content of the host file provided. + +Standard items like kernel, initramfs, cmdline, and ACPI tables also have predefined names (e.g., `etc/kernel`, `etc/cmdline`) if they are enabled to be passed via `fw_cfg`. diff --git a/src/main.rs b/src/main.rs index 56b48d59b5..bf0d9089d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -853,6 +853,8 @@ fn main() { compile_error!("Feature 'tdx' and 'sev_snp' are mutually exclusive."); #[cfg(all(feature = "sev_snp", not(target_arch = "x86_64")))] compile_error!("Feature 'sev_snp' needs target 'x86_64'"); + #[cfg(all(feature = "fw_cfg", target_arch = "riscv64"))] + compile_error!("Feature 'fw_cfg' needs targets 'x86_64' or 'aarch64'"); #[cfg(feature = "dhat-heap")] let _profiler = dhat::Profiler::new_heap(); diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index bceba9bdd2..5567a58579 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -8,6 +8,7 @@ version = "0.1.0" dbus_api = ["blocking", "futures", "zbus"] default = [] dhat-heap = ["dhat"] # For heap profiling +fw_cfg = ["devices/fw_cfg"] guest_debug = ["gdbstub", "gdbstub_arch", "kvm"] igvm = ["dep:igvm", "hex", "igvm_defs", "mshv-bindings", "range_map_vec"] io_uring = ["block/io_uring"] diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 2df6f1d585..afff343354 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -57,6 +57,11 @@ use devices::ioapic; use devices::legacy::Pl011; #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] use devices::legacy::Serial; +#[cfg(feature = "fw_cfg")] +use devices::legacy::{ + fw_cfg::{PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH}, + FwCfg, +}; #[cfg(feature = "pvmemcontrol")] use devices::pvmemcontrol::{PvmemcontrolBusDevice, PvmemcontrolPciDevice}; use devices::{interrupt_controller, AcpiNotificationFlags}; @@ -644,6 +649,11 @@ pub enum DeviceManagerError { /// Cannot lock images of all block devices. #[error("Cannot lock images of all block devices")] DiskLockError(#[source] virtio_devices::block::Error), + + #[cfg(feature = "fw_cfg")] + /// Error adding fw_cfg to bus. + #[error("Error adding fw_cfg to bus")] + ErrorAddingFwCfgToBus(#[source] vm_device::BusError), } pub type DeviceManagerResult = result::Result; @@ -1070,6 +1080,9 @@ pub struct DeviceManager { rate_limit_groups: HashMap>, mmio_regions: Arc>>, + + #[cfg(feature = "fw_cfg")] + fw_cfg: Option>>, } fn create_mmio_allocators( @@ -1334,6 +1347,8 @@ impl DeviceManager { snapshot, rate_limit_groups, mmio_regions: Arc::new(Mutex::new(Vec::new())), + #[cfg(feature = "fw_cfg")] + fw_cfg: None, }; let device_manager = Arc::new(Mutex::new(device_manager)); @@ -1460,6 +1475,32 @@ impl DeviceManager { Ok(()) } + #[cfg(feature = "fw_cfg")] + pub fn create_fw_cfg_device(&mut self) -> Result<(), DeviceManagerError> { + let fw_cfg = Arc::new(Mutex::new(devices::legacy::FwCfg::new())); + + self.fw_cfg = Some(fw_cfg.clone()); + + self.bus_devices + .push(Arc::clone(&fw_cfg) as Arc); + + #[cfg(target_arch = "x86_64")] + self.address_manager + .io_bus + .insert(fw_cfg, PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH) + .map_err(DeviceManagerError::ErrorAddingFwCfgToBus)?; + + // default address for fw_cfg on arm via mmio + // https://github.com/torvalds/linux/blob/master/drivers/firmware/qemu_fw_cfg.c#L27 + #[cfg(target_arch = "aarch64")] + self.address_manager + .mmio_bus + .insert(fw_cfg.clone(), PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH) + .map_err(DeviceManagerError::ErrorAddingFwCfgToBus)?; + + Ok(()) + } + fn state(&self) -> DeviceManagerState { DeviceManagerState { device_tree: self.device_tree.lock().unwrap().clone(), @@ -4181,6 +4222,11 @@ impl DeviceManager { &self.address_manager.mmio_bus } + #[cfg(feature = "fw_cfg")] + pub fn fw_cfg(&self) -> Option<&Arc>> { + self.fw_cfg.as_ref() + } + pub fn allocator(&self) -> &Arc> { &self.address_manager.allocator } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 2cf1cb6e95..ef99fb31a6 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -405,6 +405,8 @@ pub fn feature_list() -> Vec { "dbus_api".to_string(), #[cfg(feature = "dhat-heap")] "dhat-heap".to_string(), + #[cfg(feature = "fw_cfg")] + "fw_cfg".to_string(), #[cfg(feature = "guest_debug")] "guest_debug".to_string(), #[cfg(feature = "igvm")] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index e8354c5cd6..683aa3df9b 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -720,6 +720,13 @@ impl Vm { vm.sev_snp_init().map_err(Error::InitializeSevSnpVm)?; } + #[cfg(feature = "fw_cfg")] + device_manager + .lock() + .unwrap() + .create_fw_cfg_device() + .map_err(Error::DeviceManager)?; + #[cfg(feature = "tdx")] let kernel = config .lock() From 623fadfa9dd26d1b619f900f673b21ff2d112d55 Mon Sep 17 00:00:00 2001 From: Alex Orozco Date: Mon, 24 Mar 2025 19:50:46 +0000 Subject: [PATCH 0078/1893] devices: Add kernel cmdline, kernel, and initramfs to fw_cfg device The kernel and initramfs are passed to the fw_cfg device as file references. The cmdline is passed directly. Signed-off-by: Alex Orozco --- Cargo.lock | 4 +- Cargo.toml | 3 +- devices/Cargo.toml | 7 ++- devices/src/legacy/fw_cfg.rs | 107 ++++++++++++++++++++++++++++++++++- fuzz/Cargo.toml | 7 ++- vmm/src/vm.rs | 81 ++++++++++++++++++++++++++ 6 files changed, 203 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f26735d3cb..e4f4454ece 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,6 +531,7 @@ dependencies = [ "event_monitor", "hypervisor", "libc", + "linux-loader", "log", "num_enum", "pci", @@ -1143,8 +1144,7 @@ dependencies = [ [[package]] name = "linux-loader" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870c3814345f050991f99869417779f6062542bcf4ed81db7a1b926ad1306638" +source = "git+https://github.com/rust-vmm/linux-loader?branch=main#d5f39c09d59c8f50d5313b78ce4de511b12d1848" dependencies = [ "vm-memory", ] diff --git a/Cargo.toml b/Cargo.toml index 3fdfef690d..89f994aafe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,8 @@ members = [ acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" } kvm-bindings = "0.12.0" kvm-ioctls = "0.22.0" -linux-loader = "0.13.0" +# TODO: update to 0.13.1+ +linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" } mshv-bindings = "0.5.2" mshv-ioctls = "0.5.2" seccompiler = "0.5.0" diff --git a/devices/Cargo.toml b/devices/Cargo.toml index dc025948cc..c35f58b880 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -13,6 +13,11 @@ byteorder = { workspace = true } event_monitor = { path = "../event_monitor" } hypervisor = { path = "../hypervisor" } libc = { workspace = true } +linux-loader = { workspace = true, features = [ + "bzimage", + "elf", + "pe", +], optional = true } log = { workspace = true } num_enum = "0.7.2" pci = { path = "../pci" } @@ -38,6 +43,6 @@ arch = { path = "../arch" } [features] default = [] -fw_cfg = ["zerocopy"] +fw_cfg = ["linux-loader", "zerocopy"] kvm = ["arch/kvm"] pvmemcontrol = [] diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 8810c14353..87e4e66a30 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -14,12 +14,17 @@ use std::{ fs::File, io::Result, - mem::size_of_val, + mem::{size_of, size_of_val}, os::unix::fs::FileExt, sync::{Arc, Barrier}, }; +#[cfg(target_arch = "x86_64")] +use linux_loader::bootparam::boot_params; +#[cfg(target_arch = "aarch64")] +use linux_loader::loader::pe::arm64_image_header as boot_params; use vm_device::BusDevice; +use vm_memory::ByteValued; use vmm_sys_util::sock_ctrl_msg::IntoIovec; use zerocopy::{FromBytes, IntoBytes}; @@ -50,6 +55,14 @@ pub const PORT_FW_CFG_WIDTH: u64 = 0x10; const FW_CFG_SIGNATURE: u16 = 0x00; const FW_CFG_ID: u16 = 0x01; +const FW_CFG_KERNEL_SIZE: u16 = 0x08; +const FW_CFG_INITRD_SIZE: u16 = 0x0b; +const FW_CFG_KERNEL_DATA: u16 = 0x11; +const FW_CFG_INITRD_DATA: u16 = 0x12; +const FW_CFG_CMDLINE_SIZE: u16 = 0x14; +const FW_CFG_CMDLINE_DATA: u16 = 0x15; +const FW_CFG_SETUP_SIZE: u16 = 0x17; +const FW_CFG_SETUP_DATA: u16 = 0x18; const FW_CFG_FILE_DIR: u16 = 0x19; const FW_CFG_KNOWN_ITEMS: usize = 0x20; @@ -183,6 +196,46 @@ impl FwCfg { Ok(()) } + pub fn add_kernel_data(&mut self, file: &File) -> Result<()> { + let mut buffer = vec![0u8; size_of::()]; + file.read_exact_at(&mut buffer, 0)?; + let bp = boot_params::from_mut_slice(&mut buffer).unwrap(); + #[cfg(target_arch = "x86_64")] + { + // must set to 4 for backwards compatibility + // https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header + if bp.hdr.setup_sects == 0 { + bp.hdr.setup_sects = 4; + } + // wildcard boot loader type + bp.hdr.type_of_loader = 0xff; + } + #[cfg(target_arch = "aarch64")] + let kernel_start = bp.text_offset; + #[cfg(target_arch = "x86_64")] + let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512; + self.known_items[FW_CFG_SETUP_SIZE as usize] = FwCfgContent::U32(buffer.len() as u32); + self.known_items[FW_CFG_SETUP_DATA as usize] = FwCfgContent::Bytes(buffer); + self.known_items[FW_CFG_KERNEL_SIZE as usize] = + FwCfgContent::U32(file.metadata()?.len() as u32 - kernel_start as u32); + self.known_items[FW_CFG_KERNEL_DATA as usize] = + FwCfgContent::File(kernel_start as u64, file.try_clone()?); + Ok(()) + } + + pub fn add_kernel_cmdline(&mut self, s: std::ffi::CString) { + let bytes = s.into_bytes_with_nul(); + self.known_items[FW_CFG_CMDLINE_SIZE as usize] = FwCfgContent::U32(bytes.len() as u32); + self.known_items[FW_CFG_CMDLINE_DATA as usize] = FwCfgContent::Bytes(bytes); + } + + pub fn add_initramfs_data(&mut self, file: &File) -> Result<()> { + let initramfs_size = file.metadata()?.len(); + self.known_items[FW_CFG_INITRD_SIZE as usize] = FwCfgContent::U32(initramfs_size as _); + self.known_items[FW_CFG_INITRD_DATA as usize] = FwCfgContent::File(0, file.try_clone()?); + Ok(()) + } + fn read_content(content: &FwCfgContent, offset: u32, data: &mut [u8], size: u32) -> Option { let start = offset as usize; let end = start + size as usize; @@ -278,6 +331,11 @@ impl BusDevice for FwCfg { #[cfg(test)] mod tests { + use std::ffi::CString; + use std::io::Write; + + use vmm_sys_util::tempfile::TempFile; + use super::*; #[cfg(target_arch = "x86_64")] @@ -306,4 +364,51 @@ mod tests { } } } + #[test] + fn test_kernel_cmdline() { + let mut fw_cfg = FwCfg::new(); + + let cmdline = *b"cmdline\0"; + + fw_cfg.add_kernel_cmdline(CString::from_vec_with_nul(cmdline.to_vec()).unwrap()); + + let mut data = vec![0u8]; + + let mut cmdline_iter = cmdline.into_iter(); + fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_CMDLINE_DATA as u8, 0]); + loop { + if let Some(char) = cmdline_iter.next() { + fw_cfg.read(0, DATA_OFFSET, &mut data); + assert_eq!(data[0], char); + } else { + return; + } + } + } + + #[test] + fn test_initram_fs() { + let mut fw_cfg = FwCfg::new(); + + let temp = TempFile::new().unwrap(); + let mut temp_file = temp.as_file(); + + let initram_content = b"this is the initramfs"; + let written = temp_file.write(initram_content); + assert_eq!(written.unwrap(), 21); + let _ = fw_cfg.add_initramfs_data(temp_file); + + let mut data = vec![0u8]; + + let mut initram_iter = (*initram_content).into_iter(); + fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_INITRD_DATA as u8, 0]); + loop { + if let Some(char) = initram_iter.next() { + fw_cfg.read(0, DATA_OFFSET, &mut data); + assert_eq!(data[0], char); + } else { + return; + } + } + } } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 5d6ac0a29e..8a7e1d4849 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -22,7 +22,12 @@ epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } libc = "0.2.155" libfuzzer-sys = "0.4.7" -linux-loader = { version = "0.13.0", features = ["bzimage", "elf", "pe"] } +# TODO: update to 0.13.1+ +linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main", features = [ + "bzimage", + "elf", + "pe", +] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } mshv-bindings = "0.5.2" net_util = { path = "../net_util" } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 683aa3df9b..f7fafeb6f7 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -339,6 +339,18 @@ pub enum Error { #[error("Error locking disk images: Another instance likely holds a lock")] LockingError(#[source] DeviceManagerError), + + #[cfg(feature = "fw_cfg")] + #[error("Fw Cfg missing kernel")] + MissingFwCfgKernelFile(#[source] io::Error), + + #[cfg(feature = "fw_cfg")] + #[error("Fw Cfg missing initramfs")] + MissingFwCfgInitramfs(#[source] io::Error), + + #[cfg(feature = "fw_cfg")] + #[error("Fw Cfg missing kernel cmdline")] + MissingFwCfgCmdline, } pub type Result = result::Result; @@ -784,6 +796,72 @@ impl Vm { }) } + #[cfg(feature = "fw_cfg")] + fn populate_fw_cfg( + device_manager: &Arc>, + config: &Arc>, + ) -> Result<()> { + let kernel = config + .lock() + .unwrap() + .payload + .as_ref() + .map(|p| p.kernel.as_ref().map(File::open)) + .unwrap_or_default() + .transpose() + .map_err(Error::MissingFwCfgKernelFile)?; + if let Some(kernel_file) = kernel { + device_manager + .lock() + .unwrap() + .fw_cfg() + .expect("fw_cfg device must be present") + .lock() + .unwrap() + .add_kernel_data(&kernel_file) + .map_err(Error::MissingFwCfgKernelFile)? + } + let cmdline = Vm::generate_cmdline( + config.lock().unwrap().payload.as_ref().unwrap(), + #[cfg(target_arch = "aarch64")] + device_manager, + ) + .map_err(|_| Error::MissingFwCfgCmdline)? + .as_cstring() + .map_err(|_| Error::MissingFwCfgCmdline)?; + device_manager + .lock() + .unwrap() + .fw_cfg() + .expect("fw_cfg device must be present") + .lock() + .unwrap() + .add_kernel_cmdline(cmdline); + let initramfs = config + .lock() + .unwrap() + .payload + .as_ref() + .map(|p| p.initramfs.as_ref().map(File::open)) + .unwrap_or_default() + .transpose() + .map_err(Error::MissingFwCfgInitramfs)?; + // We measure the initramfs when running Oak Containers in SNP mode (initramfs = Stage1) + // o/w use Stage0 to launch cloud disk images + if let Some(initramfs_file) = initramfs { + device_manager + .lock() + .unwrap() + .fw_cfg() + .expect("fw_cfg device must be present") + .lock() + .unwrap() + .add_initramfs_data(&initramfs_file) + .map_err(Error::MissingFwCfgInitramfs)?; + } + Ok(()) + } + fn create_numa_nodes( configs: Option>, memory_manager: &Arc>, @@ -2272,6 +2350,9 @@ impl Vm { }; current_state.valid_transition(new_state)?; + #[cfg(feature = "fw_cfg")] + Self::populate_fw_cfg(&self.device_manager, &self.config)?; + // Do earlier to parallelise with loading kernel #[cfg(target_arch = "x86_64")] cfg_if::cfg_if! { From f0b69d56d05ad4d96f1c8669f8483f5bb8d5a05e Mon Sep 17 00:00:00 2001 From: Alex Orozco Date: Fri, 18 Apr 2025 16:20:58 +0000 Subject: [PATCH 0079/1893] devices: Add e820/memory_map to fw_cfg device We build the memory map in the fw_cfg device based on the memory size. Signed-off-by: Alex Orozco --- devices/src/legacy/fw_cfg.rs | 77 ++++++++++++++++++++++++++++++++++++ vmm/src/vm.rs | 13 ++++++ 2 files changed, 90 insertions(+) diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 87e4e66a30..ac8f20e858 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -19,15 +19,35 @@ use std::{ sync::{Arc, Barrier}, }; +#[cfg(target_arch = "aarch64")] +use arch::aarch64::layout::{ + MEM_32BIT_DEVICES_START, MEM_32BIT_RESERVED_START, RAM_64BIT_START, RAM_START as HIGH_RAM_START, +}; +#[cfg(target_arch = "x86_64")] +use arch::layout::{ + EBDA_START, HIGH_RAM_START, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, + MEM_32BIT_RESERVED_START, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START, RAM_64BIT_START, +}; +use arch::RegionType; #[cfg(target_arch = "x86_64")] use linux_loader::bootparam::boot_params; #[cfg(target_arch = "aarch64")] use linux_loader::loader::pe::arm64_image_header as boot_params; use vm_device::BusDevice; use vm_memory::ByteValued; +#[cfg(target_arch = "x86_64")] +use vm_memory::GuestAddress; use vmm_sys_util::sock_ctrl_msg::IntoIovec; use zerocopy::{FromBytes, IntoBytes}; +#[cfg(target_arch = "x86_64")] +// https://github.com/project-oak/oak/tree/main/stage0_bin#memory-layout +const STAGE0_START_ADDRESS: GuestAddress = GuestAddress(0xfffe_0000); +#[cfg(target_arch = "x86_64")] +const STAGE0_SIZE: usize = 0x2_0000; +const E820_RAM: u32 = 1; +const E820_RESERVED: u32 = 2; + #[cfg(target_arch = "x86_64")] const PORT_FW_CFG_SELECTOR: u64 = 0x510; #[cfg(target_arch = "x86_64")] @@ -165,6 +185,63 @@ impl FwCfg { } } + pub fn add_e820(&mut self, mem_size: usize) -> Result<()> { + #[cfg(target_arch = "x86_64")] + let mut mem_regions = vec![ + (GuestAddress(0), EBDA_START.0 as usize, RegionType::Ram), + ( + MEM_32BIT_DEVICES_START, + MEM_32BIT_DEVICES_SIZE as usize, + RegionType::Reserved, + ), + ( + PCI_MMCONFIG_START, + PCI_MMCONFIG_SIZE as usize, + RegionType::Reserved, + ), + (STAGE0_START_ADDRESS, STAGE0_SIZE, RegionType::Reserved), + ]; + #[cfg(target_arch = "aarch64")] + let mut mem_regions = arch::aarch64::arch_memory_regions(); + if mem_size < MEM_32BIT_DEVICES_START.0 as usize { + mem_regions.push(( + HIGH_RAM_START, + mem_size - HIGH_RAM_START.0 as usize, + RegionType::Ram, + )); + } else { + mem_regions.push(( + HIGH_RAM_START, + MEM_32BIT_RESERVED_START.0 as usize - HIGH_RAM_START.0 as usize, + RegionType::Ram, + )); + mem_regions.push(( + RAM_64BIT_START, + mem_size - (MEM_32BIT_DEVICES_START.0 as usize), + RegionType::Ram, + )); + } + let mut bytes = vec![]; + for (addr, size, region) in mem_regions.iter() { + let type_ = match region { + RegionType::Ram => E820_RAM, + RegionType::Reserved => E820_RESERVED, + RegionType::SubRegion => continue, + }; + let mut entry = BootE820Entry { + addr: addr.0, + size: *size as u64, + type_, + }; + bytes.extend_from_slice(entry.as_mut_bytes()); + } + let item = FwCfgItem { + name: "etc/e820".to_owned(), + content: FwCfgContent::Bytes(bytes), + }; + self.add_item(item) + } + fn file_dir_mut(&mut self) -> &mut Vec { let FwCfgContent::Bytes(file_buf) = &mut self.known_items[FW_CFG_FILE_DIR as usize] else { unreachable!("fw_cfg: selector {FW_CFG_FILE_DIR:#x} should be FwCfgContent::Byte!") diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index f7fafeb6f7..c527741b4d 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -351,6 +351,10 @@ pub enum Error { #[cfg(feature = "fw_cfg")] #[error("Fw Cfg missing kernel cmdline")] MissingFwCfgCmdline, + + #[cfg(feature = "fw_cfg")] + #[error("Error creating e820 map")] + CreatingE820Map(#[source] io::Error), } pub type Result = result::Result; @@ -801,6 +805,15 @@ impl Vm { device_manager: &Arc>, config: &Arc>, ) -> Result<()> { + device_manager + .lock() + .unwrap() + .fw_cfg() + .expect("fw_cfg device must be present") + .lock() + .unwrap() + .add_e820(config.lock().unwrap().memory.size as usize) + .map_err(Error::CreatingE820Map)?; let kernel = config .lock() .unwrap() From 1f51e4525bbfcc0d618daf75c26cc927a46cc5ab Mon Sep 17 00:00:00 2001 From: Alex Orozco Date: Mon, 24 Mar 2025 20:12:08 +0000 Subject: [PATCH 0080/1893] devices: Add acpi tables to fw_cfg The acpi tables are created in the same place the acpi tables would be created for the regular bootflow, except here we add them to the fw_cfg device to be measured by the fw and then the fw will put the acpi tables into memory. Signed-off-by: Alex Orozco --- devices/src/legacy/fw_cfg.rs | 198 ++++++++++++++++++++++++++++++++++- vmm/src/acpi.rs | 63 ++++++++++- vmm/src/vm.rs | 17 ++- 3 files changed, 274 insertions(+), 4 deletions(-) diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index ac8f20e858..c12c50d5da 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -14,11 +14,12 @@ use std::{ fs::File, io::Result, - mem::{size_of, size_of_val}, + mem::offset_of, os::unix::fs::FileExt, sync::{Arc, Barrier}, }; +use acpi_tables::rsdp::Rsdp; #[cfg(target_arch = "aarch64")] use arch::aarch64::layout::{ MEM_32BIT_DEVICES_START, MEM_32BIT_RESERVED_START, RAM_64BIT_START, RAM_START as HIGH_RAM_START, @@ -38,7 +39,7 @@ use vm_memory::ByteValued; #[cfg(target_arch = "x86_64")] use vm_memory::GuestAddress; use vmm_sys_util::sock_ctrl_msg::IntoIovec; -use zerocopy::{FromBytes, IntoBytes}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; #[cfg(target_arch = "x86_64")] // https://github.com/project-oak/oak/tree/main/stage0_bin#memory-layout @@ -94,6 +95,17 @@ const FW_CFG_F_RESERVED: u8 = 1 << 0; const FW_CFG_F_DMA: u8 = 1 << 1; pub const FW_CFG_FEATURE: [u8; 4] = [FW_CFG_F_RESERVED | FW_CFG_F_DMA, 0, 0, 0]; +const COMMAND_ALLOCATE: u32 = 0x1; +const COMMAND_ADD_POINTER: u32 = 0x2; +const COMMAND_ADD_CHECKSUM: u32 = 0x3; + +const ALLOC_ZONE_HIGH: u8 = 0x1; +const ALLOC_ZONE_FSEG: u8 = 0x2; + +const FW_CFG_FILENAME_TABLE_LOADER: &str = "etc/table-loader"; +const FW_CFG_FILENAME_RSDP: &str = "acpi/rsdp"; +const FW_CFG_FILENAME_ACPI_TABLES: &str = "acpi/tables"; + #[derive(Debug)] pub enum FwCfgContent { Bytes(Vec), @@ -168,6 +180,169 @@ struct FwCfgFile { name: [u8; FILE_NAME_SIZE], } +#[repr(C, align(4))] +#[derive(Debug, IntoBytes, Immutable)] +struct Allocate { + command: u32, + file: [u8; FILE_NAME_SIZE], + align: u32, + zone: u8, + _pad: [u8; 63], +} + +#[repr(C, align(4))] +#[derive(Debug, IntoBytes, Immutable)] +struct AddPointer { + command: u32, + dst: [u8; FILE_NAME_SIZE], + src: [u8; FILE_NAME_SIZE], + offset: u32, + size: u8, + _pad: [u8; 7], +} + +#[repr(C, align(4))] +#[derive(Debug, IntoBytes, Immutable)] +struct AddChecksum { + command: u32, + file: [u8; FILE_NAME_SIZE], + offset: u32, + start: u32, + len: u32, + _pad: [u8; 56], +} + +fn create_intra_pointer(name: &str, offset: usize, size: u8) -> AddPointer { + AddPointer { + command: COMMAND_ADD_POINTER, + dst: create_file_name(name), + src: create_file_name(name), + offset: offset as u32, + size, + _pad: [0; 7], + } +} + +fn create_acpi_table_checksum(offset: usize, len: usize) -> AddChecksum { + AddChecksum { + command: COMMAND_ADD_CHECKSUM, + file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES), + offset: (offset + offset_of!(AcpiTableHeader, checksum)) as u32, + start: offset as u32, + len: len as u32, + _pad: [0; 56], + } +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, IntoBytes)] +struct AcpiTableHeader { + signature: [u8; 4], + length: u32, + revision: u8, + checksum: u8, + oem_id: [u8; 6], + oem_table_id: [u8; 8], + oem_revision: u32, + asl_compiler_id: [u8; 4], + asl_compiler_revision: u32, +} + +struct AcpiTable { + rsdp: Rsdp, + tables: Vec, + table_pointers: Vec, + table_checksums: Vec<(usize, usize)>, +} + +impl AcpiTable { + fn pointers(&self) -> &[usize] { + &self.table_pointers + } + + fn checksums(&self) -> &[(usize, usize)] { + &self.table_checksums + } + + fn take(self) -> (Rsdp, Vec) { + (self.rsdp, self.tables) + } +} + +// Creates fw_cfg items used by firmware to load and verify Acpi tables +// https://github.com/qemu/qemu/blob/master/hw/acpi/bios-linker-loader.c +fn create_acpi_loader(acpi_table: AcpiTable) -> [FwCfgItem; 3] { + let mut table_loader_bytes: Vec = Vec::new(); + let allocate_rsdp = Allocate { + command: COMMAND_ALLOCATE, + file: create_file_name(FW_CFG_FILENAME_RSDP), + align: 4, + zone: ALLOC_ZONE_FSEG, + _pad: [0; 63], + }; + table_loader_bytes.extend(allocate_rsdp.as_bytes()); + + let allocate_tables = Allocate { + command: COMMAND_ALLOCATE, + file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES), + align: 4, + zone: ALLOC_ZONE_HIGH, + _pad: [0; 63], + }; + table_loader_bytes.extend(allocate_tables.as_bytes()); + + for pointer_offset in acpi_table.pointers().iter() { + let pointer = create_intra_pointer(FW_CFG_FILENAME_ACPI_TABLES, *pointer_offset, 8); + table_loader_bytes.extend(pointer.as_bytes()); + } + for (offset, len) in acpi_table.checksums().iter() { + let checksum = create_acpi_table_checksum(*offset, *len); + table_loader_bytes.extend(checksum.as_bytes()); + } + let pointer_rsdp_to_xsdt = AddPointer { + command: COMMAND_ADD_POINTER, + dst: create_file_name(FW_CFG_FILENAME_RSDP), + src: create_file_name(FW_CFG_FILENAME_ACPI_TABLES), + offset: offset_of!(Rsdp, xsdt_addr) as u32, + size: 8, + _pad: [0; 7], + }; + table_loader_bytes.extend(pointer_rsdp_to_xsdt.as_bytes()); + let checksum_rsdp = AddChecksum { + command: COMMAND_ADD_CHECKSUM, + file: create_file_name(FW_CFG_FILENAME_RSDP), + offset: offset_of!(Rsdp, checksum) as u32, + start: 0, + len: offset_of!(Rsdp, length) as u32, + _pad: [0; 56], + }; + let checksum_rsdp_ext = AddChecksum { + command: COMMAND_ADD_CHECKSUM, + file: create_file_name(FW_CFG_FILENAME_RSDP), + offset: offset_of!(Rsdp, extended_checksum) as u32, + start: 0, + len: size_of::() as u32, + _pad: [0; 56], + }; + table_loader_bytes.extend(checksum_rsdp.as_bytes()); + table_loader_bytes.extend(checksum_rsdp_ext.as_bytes()); + + let table_loader = FwCfgItem { + name: FW_CFG_FILENAME_TABLE_LOADER.to_owned(), + content: FwCfgContent::Bytes(table_loader_bytes), + }; + let (rsdp, tables) = acpi_table.take(); + let acpi_rsdp = FwCfgItem { + name: FW_CFG_FILENAME_RSDP.to_owned(), + content: FwCfgContent::Bytes(rsdp.as_bytes().to_owned()), + }; + let apci_tables = FwCfgItem { + name: FW_CFG_FILENAME_ACPI_TABLES.to_owned(), + content: FwCfgContent::Bytes(tables), + }; + [table_loader, acpi_rsdp, apci_tables] +} + impl FwCfg { pub fn new() -> FwCfg { const DEFAULT_ITEM: FwCfgContent = FwCfgContent::Slice(&[]); @@ -306,6 +481,25 @@ impl FwCfg { self.known_items[FW_CFG_CMDLINE_DATA as usize] = FwCfgContent::Bytes(bytes); } + pub fn add_acpi( + &mut self, + rsdp: Rsdp, + tables: Vec, + table_checksums: Vec<(usize, usize)>, + table_pointers: Vec, + ) -> Result<()> { + let acpi_table = AcpiTable { + rsdp, + tables, + table_checksums, + table_pointers, + }; + let [table_loader, acpi_rsdp, apci_tables] = create_acpi_loader(acpi_table); + self.add_item(table_loader)?; + self.add_item(acpi_rsdp)?; + self.add_item(apci_tables) + } + pub fn add_initramfs_data(&mut self, file: &File) -> Result<()> { let initramfs_size = file.metadata()?.len(); self.known_items[FW_CFG_INITRD_SIZE as usize] = FwCfgContent::U32(initramfs_size as _); diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 2185ee8b32..8dc2c3276c 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -192,6 +192,8 @@ pub fn create_dsdt_table( dsdt } +const FACP_DSDT_OFFSET: usize = 140; + fn create_facp_table(dsdt_offset: GuestAddress, device_manager: &Arc>) -> Sdt { trace_scoped!("create_facp_table"); @@ -241,7 +243,7 @@ fn create_facp_table(dsdt_offset: GuestAddress, device_manager: &Arc>, + cpu_manager: &Arc>, + memory_manager: &Arc>, + numa_nodes: &NumaNodes, + tpm_enabled: bool, +) -> Result<(), crate::vm::Error> { + let dsdt_offset = GuestAddress(0); + let (rsdp, table_bytes, xsdt_table_pointers) = create_acpi_tables_internal( + dsdt_offset, + device_manager, + cpu_manager, + memory_manager, + numa_nodes, + tpm_enabled, + ); + let mut pointer_offsets: Vec = vec![]; + let mut checksums: Vec<(usize, usize)> = vec![]; + + let xsdt_addr = rsdp.xsdt_addr.get() as usize; + let xsdt_checksum = (xsdt_addr, table_bytes.len() - xsdt_addr); + + // create pointer offsets (use location of pointers in XSDT table) + // XSDT doesn't have a pointer to DSDT so we use FACP's pointer to DSDT + let facp_offset = xsdt_table_pointers[0] as usize; + pointer_offsets.push(facp_offset + FACP_DSDT_OFFSET); + let mut current_offset = xsdt_addr + 36; + for _ in 0..xsdt_table_pointers.len() { + pointer_offsets.push(current_offset); + current_offset += 8; + } + + // create (offset, len) pairs for firmware to calculate + // table checksums and verify ACPI tables + let mut i = 0; + while i < xsdt_table_pointers.len() - 1 { + let current_table_offset = xsdt_table_pointers[i]; + let current_table_length = xsdt_table_pointers[i + 1] - current_table_offset; + checksums.push((current_table_offset as usize, current_table_length as usize)); + i += 1; + } + checksums.push(( + xsdt_table_pointers[xsdt_table_pointers.len() - 1] as usize, + 0, + )); + checksums.push(xsdt_checksum); + + device_manager + .lock() + .unwrap() + .fw_cfg() + .expect("fw_cfg must be present") + .lock() + .unwrap() + .add_acpi(rsdp, table_bytes, checksums, pointer_offsets) + .map_err(crate::vm::Error::CreatingAcpiTables) +} + pub fn create_acpi_tables( guest_mem: &GuestMemoryMmap, device_manager: &Arc>, diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index c527741b4d..bed0a4363a 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -355,6 +355,10 @@ pub enum Error { #[cfg(feature = "fw_cfg")] #[error("Error creating e820 map")] CreatingE820Map(#[source] io::Error), + + #[cfg(feature = "fw_cfg")] + #[error("Error creating acpi tables")] + CreatingAcpiTables(#[source] io::Error), } pub type Result = result::Result; @@ -2361,10 +2365,21 @@ impl Vm { } else { VmState::Running }; + current_state.valid_transition(new_state)?; #[cfg(feature = "fw_cfg")] - Self::populate_fw_cfg(&self.device_manager, &self.config)?; + { + Self::populate_fw_cfg(&self.device_manager, &self.config)?; + let tpm_enabled = self.config.lock().unwrap().tpm.is_some(); + crate::acpi::create_acpi_tables_for_fw_cfg( + &self.device_manager, + &self.cpu_manager, + &self.memory_manager, + &self.numa_nodes, + tpm_enabled, + )? + } // Do earlier to parallelise with loading kernel #[cfg(target_arch = "x86_64")] From edee53ac1aff69cf95cf3cb99a9da8cee595f75d Mon Sep 17 00:00:00 2001 From: Alex Orozco Date: Fri, 18 Apr 2025 16:37:11 +0000 Subject: [PATCH 0081/1893] devices: Implement DMA for fw_cfg device We pass a reference to the guest memory when we create the device in DeviceManager. This allows us to access the guest memory for DMA. Signed-off-by: Alex Orozco --- Cargo.lock | 1 + devices/Cargo.toml | 3 +- devices/src/legacy/fw_cfg.rs | 265 +++++++++++++++++++++++++++++++++-- vmm/src/device_manager.rs | 4 +- 4 files changed, 257 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4f4454ece..5a78ae54c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,6 +526,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", + "bitfield-struct", "bitflags 2.9.0", "byteorder", "event_monitor", diff --git a/devices/Cargo.toml b/devices/Cargo.toml index c35f58b880..81776dbc34 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -8,6 +8,7 @@ version = "0.1.0" acpi_tables = { workspace = true } anyhow = { workspace = true } arch = { path = "../arch" } +bitfield-struct = { version = "0.10.1", optional = true } bitflags = { workspace = true } byteorder = { workspace = true } event_monitor = { path = "../event_monitor" } @@ -43,6 +44,6 @@ arch = { path = "../arch" } [features] default = [] -fw_cfg = ["linux-loader", "zerocopy"] +fw_cfg = ["bitfield-struct", "linux-loader", "zerocopy"] kvm = ["arch/kvm"] pvmemcontrol = [] diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index c12c50d5da..78bf875740 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -13,7 +13,7 @@ /// only firmware must implement mechanism to interact with this fw_cfg device use std::{ fs::File, - io::Result, + io::{ErrorKind, Read, Result, Seek, SeekFrom}, mem::offset_of, os::unix::fs::FileExt, sync::{Arc, Barrier}, @@ -30,16 +30,18 @@ use arch::layout::{ MEM_32BIT_RESERVED_START, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START, RAM_64BIT_START, }; use arch::RegionType; +use bitfield_struct::bitfield; #[cfg(target_arch = "x86_64")] use linux_loader::bootparam::boot_params; #[cfg(target_arch = "aarch64")] use linux_loader::loader::pe::arm64_image_header as boot_params; use vm_device::BusDevice; -use vm_memory::ByteValued; -#[cfg(target_arch = "x86_64")] -use vm_memory::GuestAddress; +use vm_memory::bitmap::AtomicBitmap; +use vm_memory::{ + ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, +}; use vmm_sys_util::sock_ctrl_msg::IntoIovec; -use zerocopy::{FromBytes, Immutable, IntoBytes}; +use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes}; #[cfg(target_arch = "x86_64")] // https://github.com/project-oak/oak/tree/main/stage0_bin#memory-layout @@ -114,6 +116,34 @@ pub enum FwCfgContent { U32(u32), } +struct FwCfgContentAccess<'a> { + content: &'a FwCfgContent, + offset: u32, +} + +impl Read for FwCfgContentAccess<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + match self.content { + FwCfgContent::File(offset, f) => { + Seek::seek(&mut (&*f), SeekFrom::Start(offset + self.offset as u64))?; + Read::read(&mut (&*f), buf) + } + FwCfgContent::Bytes(b) => match b.get(self.offset as usize..) { + Some(mut s) => s.read(buf), + None => Err(ErrorKind::UnexpectedEof)?, + }, + FwCfgContent::Slice(b) => match b.get(self.offset as usize..) { + Some(mut s) => s.read(buf), + None => Err(ErrorKind::UnexpectedEof)?, + }, + FwCfgContent::U32(n) => match n.to_le_bytes().get(self.offset as usize..) { + Some(mut s) => s.read(buf), + None => Err(ErrorKind::UnexpectedEof)?, + }, + } + } +} + impl Default for FwCfgContent { fn default() -> Self { FwCfgContent::Slice(&[]) @@ -130,6 +160,12 @@ impl FwCfgContent { }; u32::try_from(ret).map_err(|_| std::io::ErrorKind::InvalidInput.into()) } + fn access(&self, offset: u32) -> FwCfgContentAccess<'_> { + FwCfgContentAccess { + content: self, + offset, + } + } } #[derive(Debug, Default)] @@ -139,12 +175,45 @@ pub struct FwCfgItem { } /// https://www.qemu.org/docs/master/specs/fw_cfg.html -#[derive(Debug, Default)] +#[derive(Debug)] pub struct FwCfg { selector: u16, data_offset: u32, + dma_address: u64, items: Vec, // 0x20 and above known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19 + memory: GuestMemoryAtomic>, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, FromBytes)] +struct FwCfgDmaAccess { + control_be: u32, + length_be: u32, + address_be: u64, +} + +// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h#L67 +#[bitfield(u32)] +struct AccessControl { + // FW_CFG_DMA_CTL_ERROR = 0x01 + error: bool, + // FW_CFG_DMA_CTL_READ = 0x02 + read: bool, + #[bits(1)] + _unused2: u8, + // FW_CFG_DMA_CTL_SKIP = 0x04 + skip: bool, + #[bits(3)] + _unused3: u8, + // FW_CFG_DMA_CTL_ERROR = 0x08 + select: bool, + #[bits(7)] + _unused4: u8, + // FW_CFG_DMA_CTL_WRITE = 0x10 + write: bool, + #[bits(16)] + _unused: u32, } #[repr(C)] @@ -344,7 +413,7 @@ fn create_acpi_loader(acpi_table: AcpiTable) -> [FwCfgItem; 3] { } impl FwCfg { - pub fn new() -> FwCfg { + pub fn new(memory: GuestMemoryAtomic>) -> FwCfg { const DEFAULT_ITEM: FwCfgContent = FwCfgContent::Slice(&[]); let mut known_items = [DEFAULT_ITEM; FW_CFG_KNOWN_ITEMS]; known_items[FW_CFG_SIGNATURE as usize] = FwCfgContent::Slice(&FW_CFG_DMA_SIGNATURE); @@ -355,8 +424,10 @@ impl FwCfg { FwCfg { selector: 0, data_offset: 0, + dma_address: 0, items: vec![], known_items, + memory, } } @@ -448,6 +519,87 @@ impl FwCfg { Ok(()) } + fn dma_read_content( + &self, + content: &FwCfgContent, + offset: u32, + len: u32, + address: u64, + ) -> Result { + let content_size = content.size()?.saturating_sub(offset); + let op_size = std::cmp::min(content_size, len); + let mut access = content.access(offset); + let mut buf = vec![0u8; op_size as usize]; + access.read_exact(buf.as_mut_bytes())?; + let r = self + .memory + .memory() + .write(buf.as_bytes(), GuestAddress(address)); + match r { + Err(e) => { + error!("fw_cfg: dma read error: {e:x?}"); + Err(ErrorKind::InvalidInput.into()) + } + Ok(size) => Ok(size as u32), + } + } + + fn dma_read(&mut self, selector: u16, len: u32, address: u64) -> Result<()> { + let op_size = if let Some(content) = self.known_items.get(selector as usize) { + self.dma_read_content(content, self.data_offset, len, address) + } else if let Some(item) = self.items.get((selector - FW_CFG_FILE_FIRST) as usize) { + self.dma_read_content(&item.content, self.data_offset, len, address) + } else { + error!("fw_cfg: selector {selector:#x} does not exist."); + Err(ErrorKind::NotFound.into()) + }?; + self.data_offset += op_size; + Ok(()) + } + + fn do_dma(&mut self) { + let dma_address = self.dma_address; + let mut access = FwCfgDmaAccess::new_zeroed(); + let dma_access = match self + .memory + .memory() + .read(access.as_mut_bytes(), GuestAddress(dma_address)) + { + Ok(_) => access, + Err(e) => { + error!("fw_cfg: invalid address of dma access {dma_address:#x}: {e:?}"); + return; + } + }; + let control = AccessControl(u32::from_be(dma_access.control_be)); + if control.select() { + self.selector = control.select() as u16; + } + let len = u32::from_be(dma_access.length_be); + let addr = u64::from_be(dma_access.address_be); + let ret = if control.read() { + self.dma_read(self.selector, len, addr) + } else if control.write() { + Err(ErrorKind::InvalidInput.into()) + } else if control.skip() { + self.data_offset += len; + Ok(()) + } else { + Err(ErrorKind::InvalidData.into()) + }; + let mut access_resp = AccessControl(0); + if let Err(e) = ret { + error!("fw_cfg: dma operation {dma_access:x?}: {e:x?}"); + access_resp.set_error(true); + } + if let Err(e) = self.memory.memory().write( + &access_resp.0.to_be_bytes(), + GuestAddress(dma_address + core::mem::offset_of!(FwCfgDmaAccess, control_be) as u64), + ) { + error!("fw_cfg: finishing dma: {e:?}") + } + } + pub fn add_kernel_data(&mut self, file: &File) -> Result<()> { let mut buffer = vec![0u8; size_of::()]; file.read_exact_at(&mut buffer, 0)?; @@ -560,10 +712,14 @@ impl BusDevice for FwCfg { } (PORT_FW_CFG_DATA, _) => _ = self.read_data(data, size as u32), (PORT_FW_CFG_DMA_HI, 4) => { - unimplemented!() + let addr = self.dma_address; + let addr_hi = (addr >> 32) as u32; + data.copy_from_slice(&addr_hi.to_be_bytes()); } (PORT_FW_CFG_DMA_LO, 4) => { - unimplemented!() + let addr = self.dma_address; + let addr_lo = (addr & 0xffff_ffff) as u32; + data.copy_from_slice(&addr_lo.to_be_bytes()); } _ => { debug!("fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}."); @@ -587,10 +743,19 @@ impl BusDevice for FwCfg { } (PORT_FW_CFG_DATA, 1) => error!("fw_cfg: data register is read-only."), (PORT_FW_CFG_DMA_HI, 4) => { - unimplemented!() + let mut buf = [0u8; 4]; + buf[..size].copy_from_slice(&data[..size]); + let val = u32::from_be_bytes(buf); + self.dma_address &= 0xffff_ffff; + self.dma_address |= (val as u64) << 32; } (PORT_FW_CFG_DMA_LO, 4) => { - unimplemented!() + let mut buf = [0u8; 4]; + buf[..size].copy_from_slice(&data[..size]); + let val = u32::from_be_bytes(buf); + self.dma_address &= !0xffff_ffff; + self.dma_address |= val as u64; + self.do_dma(); } _ => debug!( "fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ." @@ -617,10 +782,18 @@ mod tests { const DATA_OFFSET: u64 = 1; #[cfg(target_arch = "aarch64")] const DATA_OFFSET: u64 = 0; + #[cfg(target_arch = "x86_64")] + const DMA_OFFSET: u64 = 4; + #[cfg(target_arch = "aarch64")] + const DMA_OFFSET: u64 = 16; #[test] fn test_signature() { - let mut fw_cfg = FwCfg::new(); + let gm = GuestMemoryAtomic::new( + GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(), + ); + + let mut fw_cfg = FwCfg::new(gm); let mut data = vec![0u8]; @@ -637,7 +810,11 @@ mod tests { } #[test] fn test_kernel_cmdline() { - let mut fw_cfg = FwCfg::new(); + let gm = GuestMemoryAtomic::new( + GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(), + ); + + let mut fw_cfg = FwCfg::new(gm); let cmdline = *b"cmdline\0"; @@ -659,7 +836,11 @@ mod tests { #[test] fn test_initram_fs() { - let mut fw_cfg = FwCfg::new(); + let gm = GuestMemoryAtomic::new( + GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(), + ); + + let mut fw_cfg = FwCfg::new(gm); let temp = TempFile::new().unwrap(); let mut temp_file = temp.as_file(); @@ -682,4 +863,60 @@ mod tests { } } } + + #[test] + fn test_dma() { + let code = [ + 0xba, 0xf8, 0x03, 0x00, 0xd8, 0x04, b'0', 0xee, 0xb0, b'\n', 0xee, 0xf4, + ]; + + let content = FwCfgContent::Bytes(code.to_vec()); + + let mem_size = 0x1000; + let load_addr = GuestAddress(0x1000); + let mem: GuestMemoryMmap = + GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap(); + + // Note: In firmware we would just allocate FwCfgDmaAccess struct + // and use address of struct (&) as dma address + let mut access_control = AccessControl(0); + // bit 1 = read access + access_control.set_read(true); + // length of data to access + let length_be = (code.len() as u32).to_be(); + // guest address for data + let code_address = 0x1900_u64; + let address_be = code_address.to_be(); + let mut access = FwCfgDmaAccess { + control_be: access_control.0.to_be(), // bit(1) = read bit + length_be, + address_be, + }; + // access address is where to put the code + let access_address = GuestAddress(load_addr.0); + let address_bytes = access_address.0.to_be_bytes(); + let dma_lo: [u8; 4] = address_bytes[0..4].try_into().unwrap(); + let dma_hi: [u8; 4] = address_bytes[4..8].try_into().unwrap(); + + // writing the FwCfgDmaAccess to mem (this would just be self.dma_access.as_ref() in guest) + let _ = mem.write(access.as_mut_bytes(), access_address); + let mem_m = GuestMemoryAtomic::new(mem.clone()); + let mut fw_cfg = FwCfg::new(mem_m); + let cfg_item = FwCfgItem { + name: "code".to_string(), + content, + }; + let _ = fw_cfg.add_item(cfg_item); + + let mut data = [0u8; 12]; + + let _ = mem.read(&mut data, GuestAddress(code_address)); + assert_ne!(data, code); + + fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_FILE_FIRST as u8, 0]); + fw_cfg.write(0, DMA_OFFSET, &dma_lo); + fw_cfg.write(0, DMA_OFFSET + 4, &dma_hi); + let _ = mem.read(&mut data, GuestAddress(code_address)); + assert_eq!(data, code); + } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index afff343354..db9c0792b6 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1477,7 +1477,9 @@ impl DeviceManager { #[cfg(feature = "fw_cfg")] pub fn create_fw_cfg_device(&mut self) -> Result<(), DeviceManagerError> { - let fw_cfg = Arc::new(Mutex::new(devices::legacy::FwCfg::new())); + let fw_cfg = Arc::new(Mutex::new(devices::legacy::FwCfg::new( + self.memory_manager.lock().as_ref().unwrap().guest_memory(), + ))); self.fw_cfg = Some(fw_cfg.clone()); From 971f552e093445f9cc6205e570622fd8033905dc Mon Sep 17 00:00:00 2001 From: Alex Orozco Date: Mon, 19 May 2025 21:38:21 +0000 Subject: [PATCH 0082/1893] vmm: Add acpi table for fw_cfg device This allows the fw_cfg device to be recognized by the guest linux kernel. This becomes more relavnt in the following cl where I add the option to load files into the guest via fw_cfg. The Linux kernel already has a fw_cfg driver that will automatically load these files under /sys when CONFIG_FW_CFG_SYSFS is enabled in the kernel config For arm we must add fw_cfg to the devices tree Signed-off-by: Alex Orozco --- arch/Cargo.toml | 1 + arch/src/aarch64/fdt.rs | 17 +++++++++++++ arch/src/lib.rs | 3 +++ devices/Cargo.toml | 2 +- devices/src/legacy/fw_cfg.rs | 2 ++ vmm/src/device_manager.rs | 49 +++++++++++++++++++++++++++++++++--- 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/arch/Cargo.toml b/arch/Cargo.toml index 3e2202f025..4739c14f36 100644 --- a/arch/Cargo.toml +++ b/arch/Cargo.toml @@ -6,6 +6,7 @@ version = "0.1.0" [features] default = [] +fw_cfg = [] kvm = ["hypervisor/kvm"] sev_snp = [] tdx = [] diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index 23df4d805a..3b8cfbcc48 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -850,6 +850,21 @@ fn create_gpio_node( Ok(()) } +// https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/fw-cfg.txt +#[cfg(feature = "fw_cfg")] +fn create_fw_cfg_node( + fdt: &mut FdtWriter, + dev_info: &T, +) -> FdtWriterResult<()> { + // FwCfg node + let fw_cfg_node = fdt.begin_node(&format!("fw-cfg@{:x}", dev_info.addr()))?; + fdt.property("compatible", b"qemu,fw-cfg-mmio\0")?; + fdt.property_array_u64("reg", &[dev_info.addr(), dev_info.length()])?; + fdt.end_node(fw_cfg_node)?; + + Ok(()) +} + fn create_devices_node( fdt: &mut FdtWriter, dev_info: &HashMap<(DeviceType, String), T, S>, @@ -865,6 +880,8 @@ fn create_devices_node { ordered_virtio_device.push(info); } + #[cfg(feature = "fw_cfg")] + DeviceType::FwCfg => create_fw_cfg_node(fdt, info)?, } } diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 333a65d9c4..bbca3e4ea7 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -155,6 +155,9 @@ pub enum DeviceType { /// Device Type: GPIO. #[cfg(target_arch = "aarch64")] Gpio, + /// Device Type: fw_cfg. + #[cfg(feature = "fw_cfg")] + FwCfg, } /// Default (smallest) memory page size for the supported architectures. diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 81776dbc34..d2fcc4a94a 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -44,6 +44,6 @@ arch = { path = "../arch" } [features] default = [] -fw_cfg = ["bitfield-struct", "linux-loader", "zerocopy"] +fw_cfg = ["arch/fw_cfg", "bitfield-struct", "linux-loader", "zerocopy"] kvm = ["arch/kvm"] pvmemcontrol = [] diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 78bf875740..4e96d45359 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -91,6 +91,8 @@ const FW_CFG_KNOWN_ITEMS: usize = 0x20; pub const FW_CFG_FILE_FIRST: u16 = 0x20; pub const FW_CFG_DMA_SIGNATURE: [u8; 8] = *b"QEMU CFG"; +// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h +pub const FW_CFG_ACPI_ID: &str = "QEMU0002"; // Reserved (must be enabled) const FW_CFG_F_RESERVED: u8 = 1 << 0; // DMA Toggle Bit (enabled by default) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index db9c0792b6..53e2d1a4ac 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -53,6 +53,8 @@ use devices::gic; use devices::interrupt_controller::InterruptController; #[cfg(target_arch = "x86_64")] use devices::ioapic; +#[cfg(all(feature = "fw_cfg", target_arch = "x86_64"))] +use devices::legacy::fw_cfg::FW_CFG_ACPI_ID; #[cfg(target_arch = "aarch64")] use devices::legacy::Pl011; #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] @@ -1495,11 +1497,29 @@ impl DeviceManager { // default address for fw_cfg on arm via mmio // https://github.com/torvalds/linux/blob/master/drivers/firmware/qemu_fw_cfg.c#L27 #[cfg(target_arch = "aarch64")] - self.address_manager - .mmio_bus - .insert(fw_cfg.clone(), PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH) - .map_err(DeviceManagerError::ErrorAddingFwCfgToBus)?; + { + self.address_manager + .mmio_bus + .insert(fw_cfg.clone(), PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH) + .map_err(DeviceManagerError::ErrorAddingFwCfgToBus)?; + let fw_cfg_irq = self + .address_manager + .allocator + .lock() + .unwrap() + .allocate_irq() + .unwrap(); + + self.id_to_dev_info.insert( + (DeviceType::FwCfg, "fw-cfg".to_string()), + MmioDeviceInfo { + addr: PORT_FW_CFG_BASE, + len: PORT_FW_CFG_WIDTH, + irq: fw_cfg_irq, + }, + ); + } Ok(()) } @@ -5003,6 +5023,27 @@ impl Aml for DeviceManager { ) .to_aml_bytes(sink); + #[cfg(all(feature = "fw_cfg", target_arch = "x86_64"))] + if self.fw_cfg.is_some() { + aml::Device::new( + "_SB_.FWCF".into(), + vec![ + &aml::Name::new("_HID".into(), &FW_CFG_ACPI_ID.to_string()), + &aml::Name::new("_STA".into(), &0xB_usize), + &aml::Name::new( + "_CRS".into(), + &aml::ResourceTemplate::new(vec![&aml::IO::new( + PORT_FW_CFG_BASE as u16, + PORT_FW_CFG_BASE as u16, + 0x01, + PORT_FW_CFG_WIDTH as u8, + )]), + ), + ], + ) + .to_aml_bytes(sink); + } + // Serial device #[cfg(target_arch = "x86_64")] let serial_irq = 4; From a70c1b38e7681fb2cf9480176401705b1086d9b4 Mon Sep 17 00:00:00 2001 From: Alex Orozco Date: Mon, 19 May 2025 21:41:41 +0000 Subject: [PATCH 0083/1893] devices: Add fw_cfg cli options This allows us to enable/disable the fw_cfg device via the cli We can also now upload files into the guest vm using fw_cfg_items via the cli Signed-off-by: Alex Orozco --- devices/src/legacy/fw_cfg.rs | 28 +++++ src/main.rs | 10 ++ vmm/src/config.rs | 183 ++++++++++++++++++++++++++++++- vmm/src/lib.rs | 2 + vmm/src/vm.rs | 206 ++++++++++++++++++++++------------- vmm/src/vm_config.rs | 75 +++++++++++++ 6 files changed, 429 insertions(+), 75 deletions(-) diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 4e96d45359..02c52c707f 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -433,6 +433,34 @@ impl FwCfg { } } + pub fn populate_fw_cfg( + &mut self, + mem_size: Option, + kernel: Option, + initramfs: Option, + cmdline: Option, + fw_cfg_item_list: Option>, + ) -> Result<()> { + if let Some(mem_size) = mem_size { + self.add_e820(mem_size)? + } + if let Some(kernel) = kernel { + self.add_kernel_data(&kernel)?; + } + if let Some(cmdline) = cmdline { + self.add_kernel_cmdline(cmdline); + } + if let Some(initramfs) = initramfs { + self.add_initramfs_data(&initramfs)? + } + if let Some(fw_cfg_item_list) = fw_cfg_item_list { + for item in fw_cfg_item_list { + self.add_item(item)?; + } + } + Ok(()) + } + pub fn add_e820(&mut self, mem_size: usize) -> Result<()> { #[cfg(target_arch = "x86_64")] let mut mem_regions = vec![ diff --git a/src/main.rs b/src/main.rs index bf0d9089d3..4ba766e055 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,8 @@ use vmm::api::ApiAction; use vmm::config::{RestoreConfig, VmParams}; use vmm::landlock::{Landlock, LandlockError}; use vmm::vm_config; +#[cfg(feature = "fw_cfg")] +use vmm::vm_config::FwCfgConfig; #[cfg(target_arch = "x86_64")] use vmm::vm_config::SgxEpcConfig; use vmm::vm_config::{ @@ -269,6 +271,12 @@ fn get_cli_options_sorted( .help(FsConfig::SYNTAX) .num_args(1..) .group("vm-config"), + #[cfg(feature = "fw_cfg")] + Arg::new("fw-cfg-config") + .long("fw-cfg-config") + .help(FwCfgConfig::SYNTAX) + .num_args(1) + .group("vm-payload"), #[cfg(feature = "guest_debug")] Arg::new("gdb") .long("gdb") @@ -979,6 +987,8 @@ mod unit_tests { igvm: None, #[cfg(feature = "sev_snp")] host_data: None, + #[cfg(feature = "fw_cfg")] + fw_cfg_config: None, }), rate_limit_groups: None, disks: None, diff --git a/vmm/src/config.rs b/vmm/src/config.rs index b2d940a66b..28d8cc6d8c 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -163,6 +163,10 @@ pub enum Error { /// Missing fields in Landlock rules #[error("Error parsing --landlock-rules: path/access field missing")] ParseLandlockMissingFields, + #[cfg(feature = "fw_cfg")] + /// Failed Parsing FwCfgItem config + #[error("Error parsing --fw-cfg-config items")] + ParseFwCfgItem(#[source] OptionParserError), } #[derive(Debug, PartialEq, Eq, Error)] @@ -318,6 +322,18 @@ pub enum ValidationError { /// Invalid block device serial length #[error("Block device serial length ({0}) exceeds maximum allowed length ({1})")] InvalidSerialLength(usize, usize), + #[cfg(feature = "fw_cfg")] + /// FwCfg missing kernel + #[error("Error --fw-cfg-config: missing --kernel")] + FwCfgMissingKernel, + #[cfg(feature = "fw_cfg")] + /// FwCfg missing cmdline + #[error("Error --fw-cfg-config: missing --cmdline")] + FwCfgMissingCmdline, + #[cfg(feature = "fw_cfg")] + /// FwCfg missing initramfs + #[error("Error --fw-cfg-config: missing --initramfs")] + FwCfgMissingInitramfs, } type ValidationResult = std::result::Result; @@ -373,6 +389,8 @@ pub struct VmParams<'a> { pub host_data: Option<&'a str>, pub landlock_enable: bool, pub landlock_rules: Option>, + #[cfg(feature = "fw_cfg")] + pub fw_cfg_config: Option<&'a str>, } impl<'a> VmParams<'a> { @@ -444,7 +462,9 @@ impl<'a> VmParams<'a> { let landlock_rules: Option> = args .get_many::("landlock-rules") .map(|x| x.map(|y| y as &str).collect()); - + #[cfg(feature = "fw_cfg")] + let fw_cfg_config: Option<&str> = + args.get_one::("fw-cfg-config").map(|x| x as &str); VmParams { cpus, memory, @@ -486,6 +506,8 @@ impl<'a> VmParams<'a> { host_data, landlock_enable, landlock_rules, + #[cfg(feature = "fw_cfg")] + fw_cfg_config, } } } @@ -1603,6 +1625,102 @@ impl FsConfig { } } +#[cfg(feature = "fw_cfg")] +impl FwCfgConfig { + pub const SYNTAX: &'static str = "Boot params to pass to FW CFG device \ + \"e820=on|off,kernel=on|off,cmdline=on|off,initramfs=on|off,acpi_table=on|off, \ + items=[name0=,file0=:name1=,file1=]\""; + pub fn parse(fw_cfg_config: &str) -> Result { + let mut parser = OptionParser::new(); + parser + .add("e820") + .add("kernel") + .add("cmdline") + .add("initramfs") + .add("acpi_table") + .add("items"); + parser.parse(fw_cfg_config).map_err(Error::ParseFwCfgItem)?; + let e820 = parser + .convert::("e820") + .map_err(Error::ParseFwCfgItem)? + .unwrap_or(Toggle(true)) + .0; + let kernel = parser + .convert::("kernel") + .map_err(Error::ParseFwCfgItem)? + .unwrap_or(Toggle(true)) + .0; + let cmdline = parser + .convert::("cmdline") + .map_err(Error::ParseFwCfgItem)? + .unwrap_or(Toggle(true)) + .0; + let initramfs = parser + .convert::("initramfs") + .map_err(Error::ParseFwCfgItem)? + .unwrap_or(Toggle(true)) + .0; + let acpi_tables = parser + .convert::("acpi_table") + .map_err(Error::ParseFwCfgItem)? + .unwrap_or(Toggle(true)) + .0; + let items = if parser.is_set("items") { + Some( + parser + .convert::("items") + .map_err(Error::ParseFwCfgItem)? + .unwrap(), + ) + } else { + None + }; + + Ok(FwCfgConfig { + e820, + kernel, + cmdline, + initramfs, + acpi_tables, + items, + }) + } + pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { + let payload = vm_config.payload.as_ref().unwrap(); + if self.kernel && payload.kernel.is_none() { + return Err(ValidationError::FwCfgMissingKernel); + } else if self.cmdline && payload.cmdline.is_none() { + return Err(ValidationError::FwCfgMissingCmdline); + } else if self.initramfs && payload.initramfs.is_none() { + return Err(ValidationError::FwCfgMissingInitramfs); + } + Ok(()) + } +} + +#[cfg(feature = "fw_cfg")] +impl FwCfgItem { + pub fn parse(fw_cfg: &str) -> Result { + let mut parser = OptionParser::new(); + parser.add("name").add("file"); + parser.parse(fw_cfg).map_err(Error::ParseFwCfgItem)?; + + let name = + parser + .get("name") + .ok_or(Error::ParseFwCfgItem(OptionParserError::InvalidValue( + "missing FwCfgItem name".to_string(), + )))?; + let file = parser + .get("file") + .map(PathBuf::from) + .ok_or(Error::ParseFwCfgItem(OptionParserError::InvalidValue( + "missing FwCfgItem file path".to_string(), + )))?; + Ok(FwCfgItem { name, file }) + } +} + impl PmemConfig { pub const SYNTAX: &'static str = "Persistent memory parameters \ \"file=,size=,iommu=on|off,\ @@ -2661,6 +2779,14 @@ impl VmConfig { disks = Some(disk_config_list); } + #[cfg(feature = "fw_cfg")] + let fw_cfg_config = if let Some(fw_cfg_config_str) = vm_params.fw_cfg_config { + let fw_cfg_config = FwCfgConfig::parse(fw_cfg_config_str)?; + Some(fw_cfg_config) + } else { + None + }; + let mut net: Option> = None; if let Some(net_list) = &vm_params.net { let mut net_config_list = Vec::new(); @@ -2797,6 +2923,8 @@ impl VmConfig { igvm: vm_params.igvm.map(PathBuf::from), #[cfg(feature = "sev_snp")] host_data: vm_params.host_data.map(|s| s.to_string()), + #[cfg(feature = "fw_cfg")] + fw_cfg_config, }) } else { None @@ -3939,6 +4067,8 @@ mod tests { host_data: Some( "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb431188673288c07".to_string(), ), + #[cfg(feature = "fw_cfg")] + fw_cfg_config: None, }), rate_limit_groups: None, disks: None, @@ -4556,6 +4686,8 @@ mod tests { igvm: None, #[cfg(feature = "sev_snp")] host_data: Some("".to_string()), + #[cfg(feature = "fw_cfg")] + fw_cfg_config: None, }); config_with_no_host_data.validate().unwrap_err(); @@ -4570,6 +4702,8 @@ mod tests { igvm: None, #[cfg(feature = "sev_snp")] host_data: None, + #[cfg(feature = "fw_cfg")] + fw_cfg_config: None, }); valid_config_with_no_host_data.validate().unwrap(); @@ -4586,6 +4720,8 @@ mod tests { host_data: Some( "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb43118867328".to_string(), ), + #[cfg(feature = "fw_cfg")] + fw_cfg_config: None, }); config_with_invalid_host_data.validate().unwrap_err(); } @@ -4617,4 +4753,49 @@ mod tests { ); Ok(()) } + #[test] + #[cfg(feature = "fw_cfg")] + fn test_fw_cfg_config_item_list_parsing() -> Result<()> { + // Empty list + FwCfgConfig::parse("items=[]").unwrap_err(); + // Missing closing bracket + FwCfgConfig::parse("items=[name=opt/org.test/fw_cfg_test_item,file=/tmp/fw_cfg_test_item") + .unwrap_err(); + // Single Item + assert_eq!( + FwCfgConfig::parse( + "items=[name=opt/org.test/fw_cfg_test_item,file=/tmp/fw_cfg_test_item]" + )?, + FwCfgConfig { + items: Some(FwCfgItemList { + item_list: vec![FwCfgItem { + name: "opt/org.test/fw_cfg_test_item".to_string(), + file: PathBuf::from("/tmp/fw_cfg_test_item"), + }] + }), + ..Default::default() + }, + ); + // Multiple Items + assert_eq!( + FwCfgConfig::parse( + "items=[name=opt/org.test/fw_cfg_test_item,file=/tmp/fw_cfg_test_item:name=opt/org.test/fw_cfg_test_item2,file=/tmp/fw_cfg_test_item2]" + )?, + FwCfgConfig { + items: Some(FwCfgItemList { + item_list: vec![FwCfgItem { + name: "opt/org.test/fw_cfg_test_item".to_string(), + file: PathBuf::from("/tmp/fw_cfg_test_item"), + }, + FwCfgItem { + name: "opt/org.test/fw_cfg_test_item2".to_string(), + file: PathBuf::from("/tmp/fw_cfg_test_item2"), + }] + }), + ..Default::default() + }, + + ); + Ok(()) + } } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index ef99fb31a6..e51573159b 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2391,6 +2391,8 @@ mod unit_tests { igvm: None, #[cfg(feature = "sev_snp")] host_data: None, + #[cfg(feature = "fw_cfg")] + fw_cfg_config: None, }), rate_limit_groups: None, disks: None, diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index bed0a4363a..4935bc2e9f 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -34,6 +34,8 @@ use arch::PciSpaceInfo; use arch::{get_host_cpu_phys_bits, EntryPoint, NumaNode, NumaNodes}; #[cfg(target_arch = "aarch64")] use devices::interrupt_controller; +#[cfg(feature = "fw_cfg")] +use devices::legacy::fw_cfg::FwCfgItem; use devices::AcpiNotificationFlags; #[cfg(all(target_arch = "aarch64", feature = "guest_debug"))] use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs; @@ -91,6 +93,8 @@ use crate::migration::get_vm_snapshot; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::migration::url_to_file; use crate::migration::{url_to_path, SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE}; +#[cfg(feature = "fw_cfg")] +use crate::vm_config::FwCfgConfig; use crate::vm_config::{ DeviceConfig, DiskConfig, FsConfig, HotplugMethod, NetConfig, NumaConfig, PayloadConfig, PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, @@ -359,6 +363,18 @@ pub enum Error { #[cfg(feature = "fw_cfg")] #[error("Error creating acpi tables")] CreatingAcpiTables(#[source] io::Error), + + #[cfg(feature = "fw_cfg")] + #[error("Error adding fw_cfg item")] + AddingFwCfgItem(#[source] io::Error), + + #[cfg(feature = "fw_cfg")] + #[error("Error populating fw_cfg")] + ErrorPopulatingFwCfg(#[source] io::Error), + + #[cfg(feature = "fw_cfg")] + #[error("Error using fw_cfg while disabled")] + FwCfgDisabled, } pub type Result = result::Result; @@ -741,11 +757,22 @@ impl Vm { } #[cfg(feature = "fw_cfg")] - device_manager - .lock() - .unwrap() - .create_fw_cfg_device() - .map_err(Error::DeviceManager)?; + { + let fw_cfg_config = config + .lock() + .unwrap() + .payload + .as_ref() + .map(|p| p.fw_cfg_config.is_some()) + .unwrap_or(false); + if fw_cfg_config { + device_manager + .lock() + .unwrap() + .create_fw_cfg_device() + .map_err(Error::DeviceManager)?; + } + } #[cfg(feature = "tdx")] let kernel = config @@ -806,76 +833,85 @@ impl Vm { #[cfg(feature = "fw_cfg")] fn populate_fw_cfg( + fw_cfg_config: &FwCfgConfig, device_manager: &Arc>, config: &Arc>, ) -> Result<()> { - device_manager - .lock() - .unwrap() - .fw_cfg() - .expect("fw_cfg device must be present") - .lock() - .unwrap() - .add_e820(config.lock().unwrap().memory.size as usize) - .map_err(Error::CreatingE820Map)?; - let kernel = config - .lock() - .unwrap() - .payload - .as_ref() - .map(|p| p.kernel.as_ref().map(File::open)) - .unwrap_or_default() - .transpose() - .map_err(Error::MissingFwCfgKernelFile)?; - if let Some(kernel_file) = kernel { - device_manager - .lock() - .unwrap() - .fw_cfg() - .expect("fw_cfg device must be present") - .lock() - .unwrap() - .add_kernel_data(&kernel_file) - .map_err(Error::MissingFwCfgKernelFile)? + let mut e820_option: Option = None; + if fw_cfg_config.e820 { + e820_option = Some(config.lock().unwrap().memory.size as usize); } - let cmdline = Vm::generate_cmdline( - config.lock().unwrap().payload.as_ref().unwrap(), - #[cfg(target_arch = "aarch64")] - device_manager, - ) - .map_err(|_| Error::MissingFwCfgCmdline)? - .as_cstring() - .map_err(|_| Error::MissingFwCfgCmdline)?; - device_manager - .lock() - .unwrap() - .fw_cfg() - .expect("fw_cfg device must be present") - .lock() - .unwrap() - .add_kernel_cmdline(cmdline); - let initramfs = config - .lock() - .unwrap() - .payload - .as_ref() - .map(|p| p.initramfs.as_ref().map(File::open)) - .unwrap_or_default() - .transpose() - .map_err(Error::MissingFwCfgInitramfs)?; - // We measure the initramfs when running Oak Containers in SNP mode (initramfs = Stage1) - // o/w use Stage0 to launch cloud disk images - if let Some(initramfs_file) = initramfs { - device_manager + let mut kernel_option: Option = None; + if fw_cfg_config.kernel { + let kernel = config .lock() .unwrap() - .fw_cfg() - .expect("fw_cfg device must be present") + .payload + .as_ref() + .map(|p| p.kernel.as_ref().map(File::open)) + .unwrap_or_default() + .transpose() + .map_err(Error::MissingFwCfgKernelFile)?; + kernel_option = kernel; + } + let mut cmdline_option: Option = None; + if fw_cfg_config.cmdline { + let cmdline = Vm::generate_cmdline( + config.lock().unwrap().payload.as_ref().unwrap(), + #[cfg(target_arch = "aarch64")] + device_manager, + ) + .map_err(|_| Error::MissingFwCfgCmdline)? + .as_cstring() + .map_err(|_| Error::MissingFwCfgCmdline)?; + cmdline_option = Some(cmdline); + } + let mut initramfs_option: Option = None; + if fw_cfg_config.initramfs { + let initramfs = config .lock() .unwrap() - .add_initramfs_data(&initramfs_file) + .payload + .as_ref() + .map(|p| p.initramfs.as_ref().map(File::open)) + .unwrap_or_default() + .transpose() .map_err(Error::MissingFwCfgInitramfs)?; + // We measure the initramfs when running Oak Containers in SNP mode (initramfs = Stage1) + // o/w use Stage0 to launch cloud disk images + initramfs_option = initramfs; + } + let mut fw_cfg_item_list_option: Option> = None; + if let Some(fw_cfg_files) = &fw_cfg_config.items { + let mut fw_cfg_item_list = vec![]; + for fw_cfg_file in fw_cfg_files.item_list.clone() { + fw_cfg_item_list.push(FwCfgItem { + name: fw_cfg_file.name, + content: devices::legacy::fw_cfg::FwCfgContent::File( + 0, + File::open(fw_cfg_file.file).map_err(Error::AddingFwCfgItem)?, + ), + }); + } + fw_cfg_item_list_option = Some(fw_cfg_item_list); } + + let device_manager_binding = device_manager.lock().unwrap(); + let Some(fw_cfg) = device_manager_binding.fw_cfg() else { + return Err(Error::FwCfgDisabled); + }; + + fw_cfg + .lock() + .unwrap() + .populate_fw_cfg( + e820_option, + kernel_option, + initramfs_option, + cmdline_option, + fw_cfg_item_list_option, + ) + .map_err(Error::ErrorPopulatingFwCfg)?; Ok(()) } @@ -2370,15 +2406,37 @@ impl Vm { #[cfg(feature = "fw_cfg")] { - Self::populate_fw_cfg(&self.device_manager, &self.config)?; - let tpm_enabled = self.config.lock().unwrap().tpm.is_some(); - crate::acpi::create_acpi_tables_for_fw_cfg( - &self.device_manager, - &self.cpu_manager, - &self.memory_manager, - &self.numa_nodes, - tpm_enabled, - )? + let fw_cfg_enabled = self + .config + .lock() + .unwrap() + .payload + .as_ref() + .map(|p| p.fw_cfg_config.is_some()) + .unwrap_or(false); + if fw_cfg_enabled { + let fw_cfg_config = self + .config + .lock() + .unwrap() + .payload + .as_ref() + .map(|p| p.fw_cfg_config.clone()) + .unwrap_or_default() + .ok_or(Error::VmMissingConfig)?; + Self::populate_fw_cfg(&fw_cfg_config, &self.device_manager, &self.config)?; + + if fw_cfg_config.acpi_tables { + let tpm_enabled = self.config.lock().unwrap().tpm.is_some(); + crate::acpi::create_acpi_tables_for_fw_cfg( + &self.device_manager, + &self.cpu_manager, + &self.memory_manager, + &self.numa_nodes, + tpm_enabled, + )? + } + } } // Do earlier to parallelise with loading kernel diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 3e6cf447da..2d829a678a 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -4,6 +4,8 @@ // use std::net::{IpAddr, Ipv4Addr}; use std::path::PathBuf; +#[cfg(feature = "fw_cfg")] +use std::str::FromStr; use std::{fs, result}; use net_util::MacAddr; @@ -699,6 +701,79 @@ pub struct PayloadConfig { #[cfg(feature = "sev_snp")] #[serde(default)] pub host_data: Option, + #[cfg(feature = "fw_cfg")] + pub fw_cfg_config: Option, +} + +#[cfg(feature = "fw_cfg")] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct FwCfgConfig { + pub e820: bool, + pub kernel: bool, + pub cmdline: bool, + pub initramfs: bool, + pub acpi_tables: bool, + pub items: Option, +} + +#[cfg(feature = "fw_cfg")] +impl Default for FwCfgConfig { + fn default() -> Self { + FwCfgConfig { + e820: true, + kernel: true, + cmdline: true, + initramfs: true, + acpi_tables: true, + items: None, + } + } +} + +#[cfg(feature = "fw_cfg")] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct FwCfgItemList { + #[serde(default)] + pub item_list: Vec, +} + +#[cfg(feature = "fw_cfg")] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct FwCfgItem { + #[serde(default)] + pub name: String, + #[serde(default)] + pub file: PathBuf, +} + +#[cfg(feature = "fw_cfg")] +pub enum FwCfgItemError { + InvalidValue(String), +} + +#[cfg(feature = "fw_cfg")] +impl FromStr for FwCfgItemList { + type Err = FwCfgItemError; + + fn from_str(s: &str) -> Result { + let body = s + .trim() + .strip_prefix('[') + .and_then(|s| s.strip_suffix(']')) + .ok_or_else(|| FwCfgItemError::InvalidValue(s.to_string()))?; + + let mut fw_cfg_items: Vec = vec![]; + let items: Vec<&str> = body.split(':').collect(); + for item in items { + fw_cfg_items.push( + FwCfgItem::parse(item) + .map_err(|_| FwCfgItemError::InvalidValue(item.to_string()))?, + ); + } + Ok(FwCfgItemList { + item_list: fw_cfg_items, + }) + } } impl ApplyLandlock for PayloadConfig { From 5d478c534ed38168cf7cc83485ee05e5c00d5e24 Mon Sep 17 00:00:00 2001 From: Alex Orozco Date: Thu, 12 Jun 2025 20:33:03 +0000 Subject: [PATCH 0084/1893] tests: Add fw_cfg device integration test This test verifies that we can see custom items added to the fw_cfg device from inside the guest Signed-off-by: Alex Orozco --- .github/workflows/build.yaml | 3 ++ .github/workflows/quality.yaml | 7 +++- scripts/run_integration_tests_aarch64.sh | 8 ++++ scripts/run_integration_tests_x86_64.sh | 8 ++++ tests/integration.rs | 52 ++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 297b2e153d..5c32406646 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -52,6 +52,9 @@ jobs: - name: Build (default features + pvmemcontrol) run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + - name: Build (default features + fw_cfg) + run: cargo rustc --locked --bin cloud-hypervisor --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + - name: Build (mshv) run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index b4cddb3ce9..576acaffa9 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -111,7 +111,12 @@ jobs: toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - + - 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states - name: Clippy (sev_snp) if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} uses: houseabsolute/actions-rust-cross@v1 diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 0daa672e84..262faff9a4 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -250,4 +250,12 @@ if [ $RES -eq 0 ]; then RES=$? fi +# Run tests on fw_cfg +if [ $RES -eq 0 ]; then + cargo build --features "fw_cfg" --all --release --target "$BUILD_TARGET" + export RUST_BACKTRACE=1 + time cargo test "fw_cfg::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + RES=$? +fi + exit $RES diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 4fec7d04c9..4f4491aa7f 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -198,4 +198,12 @@ if [ $RES -eq 0 ]; then RES=$? fi +# Run tests on fw_cfg +if [ $RES -eq 0 ]; then + cargo build --features "mshv,fw_cfg" --all --release --target "$BUILD_TARGET" + export RUST_BACKTRACE=1 + time cargo test "fw_cfg::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + RES=$? +fi + exit $RES diff --git a/tests/integration.rs b/tests/integration.rs index d754ccff8a..dc19b7aee2 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -11069,3 +11069,55 @@ mod rate_limiter { _test_rate_limiter_group_block(false, 2, 2); } } + +#[cfg(not(target_arch = "riscv64"))] +mod fw_cfg { + use crate::*; + + #[test] + fn test_fw_cfg() { + let jammy = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(jammy)); + let mut cmd = GuestCommand::new(&guest); + + let kernel_path = direct_kernel_boot_path(); + let cmd_line = DIRECT_KERNEL_BOOT_CMDLINE; + + let test_file = guest.tmp_dir.as_path().join("test-file"); + std::fs::write(&test_file, "test-file-content").unwrap(); + + cmd.args(["--cpus", "boot=4"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", cmd_line]) + .default_disks() + .default_net() + .args([ + "--fw-cfg-config", + &format!( + "initramfs=off,items=[name=opt/org.test/test-file,file={}]", + test_file.to_str().unwrap() + ), + ]) + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot(None).unwrap(); + // Wait a while for guest + thread::sleep(std::time::Duration::new(3, 0)); + let result = guest + .ssh_command( + "sudo cat /sys/firmware/qemu_fw_cfg/by_name/opt/org.test/test-file/raw", + ) + .unwrap(); + assert_eq!(result, "test-file-content"); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } +} From aa8e9cd91a026646d24481725042e045a0bc9d5c Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Tue, 29 Jul 2025 19:02:20 +0000 Subject: [PATCH 0085/1893] misc: Change cpu ID type from u8 to u32 This is the first change to Cloud Hypervisor in a series of changes intended to increase the max number of supported vCPUs in guest VMs, which is currently limited to 255 (254 on x86_64). No user-visible/behavior changes are expected as a result of applying this patch, as the type of boot_cpus and related fields in config structs remains u8 for now, and all configuration validations remain the same. Signed-off-by: Barret Rhoden Signed-off-by: Neel Natu Signed-off-by: Ofir Weisse Signed-off-by: Peter Oskolkov --- arch/src/aarch64/fdt.rs | 2 +- arch/src/aarch64/mod.rs | 2 +- arch/src/lib.rs | 2 +- arch/src/riscv64/mod.rs | 2 +- arch/src/x86_64/mod.rs | 10 +-- arch/src/x86_64/mptable.rs | 23 +++---- devices/src/aia.rs | 2 +- devices/src/gic.rs | 2 +- fuzz/Cargo.lock | 28 +++----- fuzz/fuzz_targets/http_api.rs | 2 +- hypervisor/src/cpu.rs | 4 +- hypervisor/src/kvm/mod.rs | 8 +-- hypervisor/src/mshv/mod.rs | 7 +- hypervisor/src/vm.rs | 2 +- src/bin/ch-remote.rs | 2 +- vmm/src/acpi.rs | 4 +- vmm/src/api/mod.rs | 4 +- vmm/src/cpu.rs | 117 ++++++++++++++++++---------------- vmm/src/device_manager.rs | 4 +- vmm/src/lib.rs | 4 +- vmm/src/vm.rs | 8 +-- 21 files changed, 116 insertions(+), 123 deletions(-) diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index 3b8cfbcc48..238ad0b32a 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -370,7 +370,7 @@ fn create_cpu_nodes( if numa_nodes.len() > 1 { 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 u8)) { + if numa_node.unwrap().cpus.contains(&(cpu_id as u32)) { fdt.property_u32("numa-node-id", numa_node_idx as u32)?; } } diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index 51f51ccaf6..c807429146 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -67,7 +67,7 @@ pub struct EntryPoint { /// Configure the specified VCPU, and return its MPIDR. pub fn configure_vcpu( vcpu: &Arc, - id: u8, + id: u32, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, ) -> super::Result { if let Some((kernel_entry_point, _guest_memory)) = boot_setup { diff --git a/arch/src/lib.rs b/arch/src/lib.rs index bbca3e4ea7..cbeb37f51d 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -123,7 +123,7 @@ fn pagesize() -> usize { pub struct NumaNode { pub memory_regions: Vec>, pub hotplug_regions: Vec>, - pub cpus: Vec, + pub cpus: Vec, pub pci_segments: Vec, pub distances: BTreeMap, pub memory_zones: Vec, diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index 128698961d..62554bd1a7 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -59,7 +59,7 @@ pub struct EntryPoint { /// Configure the specified VCPU, and return its MPIDR. pub fn configure_vcpu( vcpu: &Arc, - id: u8, + id: u32, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, ) -> super::Result<()> { if let Some((kernel_entry_point, _guest_memory)) = boot_setup { diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index baa984c94b..35ec4b9253 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -820,14 +820,14 @@ pub fn generate_common_cpuid( pub fn configure_vcpu( vcpu: &Arc, - id: u8, + id: u32, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, cpuid: Vec, kvm_hyperv: bool, cpu_vendor: CpuVendor, topology: Option<(u8, u8, u8)>, ) -> super::Result<()> { - let x2apic_id = get_x2apic_id(id as u32, topology); + let x2apic_id = get_x2apic_id(id, topology); // Per vCPU CPUID changes; common are handled via generate_common_cpuid() let mut cpuid = cpuid; @@ -946,7 +946,7 @@ pub fn configure_system( cmdline_addr: GuestAddress, cmdline_size: usize, initramfs: &Option, - _num_cpus: u8, + _num_cpus: u32, setup_header: Option, rsdp_addr: Option, sgx_epc_region: Option, @@ -1365,10 +1365,10 @@ fn update_cpuid_topology( cores_per_die: u8, dies_per_package: u8, cpu_vendor: CpuVendor, - id: u8, + id: u32, ) { let x2apic_id = get_x2apic_id( - id as u32, + id, Some((threads_per_core, cores_per_die, dies_per_package)), ); diff --git a/arch/src/x86_64/mptable.rs b/arch/src/x86_64/mptable.rs index aaf6f1ddd7..42667df9c2 100644 --- a/arch/src/x86_64/mptable.rs +++ b/arch/src/x86_64/mptable.rs @@ -121,7 +121,7 @@ fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 { (!checksum).wrapping_add(1) } -fn compute_mp_size(num_cpus: u8) -> usize { +fn compute_mp_size(num_cpus: u32) -> usize { mem::size_of::() + mem::size_of::() + mem::size_of::() * (num_cpus as usize) @@ -135,12 +135,12 @@ fn compute_mp_size(num_cpus: u8) -> usize { pub fn setup_mptable( offset: GuestAddress, mem: &GuestMemoryMmap, - num_cpus: u8, + num_cpus: u32, topology: Option<(u8, u8, u8)>, ) -> Result<()> { if num_cpus > 0 { let cpu_id_max = num_cpus - 1; - let x2apic_id_max = get_x2apic_id(cpu_id_max.into(), topology); + let x2apic_id_max = get_x2apic_id(cpu_id_max, topology); if x2apic_id_max >= MAX_SUPPORTED_CPUS { return Err(Error::TooManyCpus); } @@ -195,7 +195,7 @@ pub fn setup_mptable( 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; - mpc_cpu.0.apicid = get_x2apic_id(cpu_id as u32, topology) as u8; + mpc_cpu.0.apicid = get_x2apic_id(cpu_id, topology) as u8; mpc_cpu.0.apicver = APIC_VERSION; mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8 | if cpu_id == 0 { @@ -392,13 +392,11 @@ mod tests { #[test] fn cpu_entry_count() { - let mem = GuestMemoryMmap::from_ranges(&[( - MPTABLE_START, - compute_mp_size(MAX_SUPPORTED_CPUS as u8), - )]) - .unwrap(); + let mem = + GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(MAX_SUPPORTED_CPUS))]) + .unwrap(); - for i in 0..MAX_SUPPORTED_CPUS as u8 { + for i in 0..MAX_SUPPORTED_CPUS { setup_mptable(MPTABLE_START, &mem, i, None).unwrap(); let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap(); @@ -429,10 +427,9 @@ mod tests { #[test] fn cpu_entry_count_max() { let cpus = MAX_SUPPORTED_CPUS + 1; - let mem = - GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap(); + let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus))]).unwrap(); - let result = setup_mptable(MPTABLE_START, &mem, cpus as u8, None); + let result = setup_mptable(MPTABLE_START, &mem, cpus, None); result.unwrap_err(); } } diff --git a/devices/src/aia.rs b/devices/src/aia.rs index 83ed1585f4..f3956727ac 100644 --- a/devices/src/aia.rs +++ b/devices/src/aia.rs @@ -40,7 +40,7 @@ pub struct Aia { impl Aia { pub fn new( - vcpu_count: u8, + vcpu_count: u32, interrupt_manager: Arc>, vm: Arc, ) -> Result { diff --git a/devices/src/gic.rs b/devices/src/gic.rs index afa5814a16..dcae0be375 100644 --- a/devices/src/gic.rs +++ b/devices/src/gic.rs @@ -39,7 +39,7 @@ pub struct Gic { impl Gic { pub fn new( - vcpu_count: u8, + vcpu_count: u32, interrupt_manager: Arc>, vm: Arc, ) -> Result { diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index bb325964f1..f6692014dc 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -61,9 +61,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" @@ -93,7 +93,6 @@ dependencies = [ "uuid", "vm-fdt", "vm-memory", - "vm-migration", "vmm-sys-util", ] @@ -564,13 +563,13 @@ dependencies = [ [[package]] name = "landlock" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18738c5d4c7fae6727a96adb94722ef7ce82f3eafea0a11777e258a93816537e" +checksum = "b3d2ef408b88e913bfc6594f5e693d57676f6463ded7d8bf994175364320c706" dependencies = [ "enumflags2", "libc", - "thiserror 1.0.64", + "thiserror 2.0.12", ] [[package]] @@ -936,9 +935,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "serde", "serde_derive", @@ -947,9 +946,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", @@ -1080,7 +1079,6 @@ name = "tpm" version = "0.1.0" dependencies = [ "anyhow", - "byteorder", "libc", "log", "net_gen", @@ -1190,20 +1188,17 @@ name = "virtio-devices" version = "0.1.0" dependencies = [ "anyhow", - "arc-swap", "block", "byteorder", "epoll", "event_monitor", "libc", "log", - "net_gen", "net_util", "pci", "rate_limiter", "seccompiler", "serde", - "serde_json", "serde_with", "serial_buffer", "thiserror 2.0.12", @@ -1243,12 +1238,10 @@ dependencies = [ name = "vm-device" version = "0.1.0" dependencies = [ - "anyhow", "hypervisor", "serde", "thiserror 2.0.12", "vfio-ioctls", - "vm-memory", "vmm-sys-util", ] @@ -1284,7 +1277,6 @@ dependencies = [ name = "vm-virtio" version = "0.1.0" dependencies = [ - "log", "virtio-queue", "vm-memory", ] @@ -1295,7 +1287,6 @@ version = "0.1.0" dependencies = [ "acpi_tables", "anyhow", - "arc-swap", "arch", "bitflags 2.9.0", "block", @@ -1329,7 +1320,6 @@ dependencies = [ "vfio_user", "virtio-bindings", "virtio-devices", - "virtio-queue", "vm-allocator", "vm-device", "vm-memory", diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index 8f41903f6f..5c146ad9d8 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -221,7 +221,7 @@ impl RequestHandler for StubApiRequestHandler { Ok(()) } - fn vm_resize(&mut self, _: Option, _: Option, _: Option) -> Result<(), VmError> { + fn vm_resize(&mut self, _: Option, _: Option, _: Option) -> Result<(), VmError> { Ok(()) } diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index c2eb03a267..46401cc66c 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -471,7 +471,7 @@ pub trait Vcpu: Send + Sync { &self, vm: &Arc, kvi: &mut VcpuInit, - id: u8, + id: u32, ) -> Result<()>; /// /// Returns VcpuInit with default value set @@ -498,7 +498,7 @@ pub trait Vcpu: Send + Sync { /// Configure core registers for a given CPU. /// #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] - fn setup_regs(&self, cpu_id: u8, boot_ip: u64, fdt_start: u64) -> Result<()>; + fn setup_regs(&self, cpu_id: u32, boot_ip: u64, fdt_start: u64) -> Result<()>; /// /// Check if the CPU supports PMU /// diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 8b8351b6de..f92720779e 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -559,7 +559,7 @@ impl vm::Vm for KvmVm { /// fn create_vcpu( &self, - id: u8, + id: u32, vm_ops: Option>, ) -> vm::Result> { let fd = self @@ -2150,7 +2150,7 @@ impl cpu::Vcpu for KvmVcpu { &self, vm: &Arc, kvi: &mut crate::VcpuInit, - id: u8, + id: u32, ) -> cpu::Result<()> { use std::arch::is_aarch64_feature_detected; #[allow(clippy::nonminimal_bool)] @@ -2280,7 +2280,7 @@ impl cpu::Vcpu for KvmVcpu { /// Configure core registers for a given CPU. /// #[cfg(target_arch = "aarch64")] - fn setup_regs(&self, cpu_id: u8, boot_ip: u64, fdt_start: u64) -> cpu::Result<()> { + fn setup_regs(&self, cpu_id: u32, boot_ip: u64, fdt_start: u64) -> cpu::Result<()> { // Get the register index of the PSTATE (Processor State) register. let pstate = offset_of!(kvm_regs, regs.pstate); self.fd @@ -2326,7 +2326,7 @@ impl cpu::Vcpu for KvmVcpu { /// /// Configure registers for a given RISC-V CPU. /// - fn setup_regs(&self, cpu_id: u8, boot_ip: u64, fdt_start: u64) -> cpu::Result<()> { + fn setup_regs(&self, cpu_id: u32, boot_ip: u64, fdt_start: u64) -> cpu::Result<()> { // Setting the A0 () to the hartid of this CPU. let a0 = offset_of!(kvm_riscv_core, regs.a0); self.fd diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 7783a2fc18..8b331cf0a8 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -1262,7 +1262,7 @@ impl cpu::Vcpu for MshvVcpu { } #[cfg(target_arch = "aarch64")] - fn setup_regs(&self, cpu_id: u8, boot_ip: u64, fdt_start: u64) -> cpu::Result<()> { + fn setup_regs(&self, cpu_id: u32, boot_ip: u64, fdt_start: u64) -> cpu::Result<()> { let arr_reg_name_value = [( hv_register_name_HV_ARM64_REGISTER_PSTATE, regs::PSTATE_FAULT_BITS_64, @@ -1324,7 +1324,7 @@ impl cpu::Vcpu for MshvVcpu { &self, _vm: &Arc, _kvi: &mut crate::VcpuInit, - _id: u8, + _id: u32, ) -> cpu::Result<()> { Ok(()) } @@ -1834,9 +1834,10 @@ impl vm::Vm for MshvVm { /// fn create_vcpu( &self, - id: u8, + id: u32, vm_ops: Option>, ) -> vm::Result> { + let id: u8 = id.try_into().unwrap(); let vcpu_fd = self .fd .create_vcpu(id) diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 306aed0ff8..25a80666ae 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -319,7 +319,7 @@ pub trait Vm: Send + Sync + Any { /// Unregister an event that will, when signaled, trigger the `gsi` IRQ. fn unregister_irqfd(&self, fd: &EventFd, gsi: u32) -> Result<()>; /// Creates a new KVM vCPU file descriptor and maps the memory corresponding - fn create_vcpu(&self, id: u8, vm_ops: Option>) -> Result>; + fn create_vcpu(&self, id: u32, vm_ops: Option>) -> Result>; #[cfg(target_arch = "aarch64")] fn create_vgic(&self, config: VgicConfig) -> Result>>; #[cfg(target_arch = "riscv64")] diff --git a/src/bin/ch-remote.rs b/src/bin/ch-remote.rs index 4278af6175..904565e5cd 100644 --- a/src/bin/ch-remote.rs +++ b/src/bin/ch-remote.rs @@ -725,7 +725,7 @@ fn resize_config( memory: Option<&str>, balloon: Option<&str>, ) -> Result { - let desired_vcpus: Option = if let Some(cpus) = cpus { + let desired_vcpus: Option = if let Some(cpus) = cpus { Some(cpus.parse().map_err(Error::InvalidCpuCount)?) } else { None diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 8dc2c3276c..b9e809e4a1 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -326,9 +326,9 @@ fn create_srat_table( for cpu in &node.cpus { #[cfg(target_arch = "x86_64")] - let x2apic_id = arch::x86_64::get_x2apic_id(*cpu as u32, topology); + let x2apic_id = arch::x86_64::get_x2apic_id(*cpu, topology); #[cfg(target_arch = "aarch64")] - let x2apic_id = *cpu as u32; + let x2apic_id = *cpu; // Flags // - Enabled = 1 (bit 0) diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 95c4019b48..a856f49f55 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -217,7 +217,7 @@ pub struct VmmPingResponse { #[derive(Clone, Deserialize, Serialize, Default, Debug)] pub struct VmResizeData { - pub desired_vcpus: Option, + pub desired_vcpus: Option, pub desired_ram: Option, pub desired_balloon: Option, } @@ -307,7 +307,7 @@ pub trait RequestHandler { fn vm_resize( &mut self, - desired_vcpus: Option, + desired_vcpus: Option, desired_ram: Option, desired_balloon: Option, ) -> Result<(), VmError>; diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index d8278067a5..e5813562b1 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -338,7 +338,7 @@ macro_rules! round_up { pub struct Vcpu { // The hypervisor abstracted CPU. vcpu: Arc, - id: u8, + id: u32, #[cfg(target_arch = "aarch64")] mpidr: u64, saved_state: Option, @@ -356,8 +356,8 @@ impl Vcpu { /// * `vm_ops` - Optional object for exit handling. /// * `cpu_vendor` - CPU vendor as reported by __cpuid(0x0) pub fn new( - id: u8, - apic_id: u8, + id: u32, + apic_id: u32, vm: &Arc, vm_ops: Option>, #[cfg(target_arch = "x86_64")] cpu_vendor: CpuVendor, @@ -532,8 +532,8 @@ pub struct CpuManager { vm_ops: Arc, #[cfg_attr(target_arch = "aarch64", allow(dead_code))] acpi_address: Option, - proximity_domain_per_cpu: BTreeMap, - affinity: BTreeMap>, + proximity_domain_per_cpu: BTreeMap, + affinity: BTreeMap>, dynamic: bool, hypervisor: Arc, #[cfg(feature = "sev_snp")] @@ -558,7 +558,7 @@ impl BusDevice for CpuManager { data[0] = self.selected_cpu; } CPU_STATUS_OFFSET => { - if self.selected_cpu < self.max_vcpus() { + if (self.selected_cpu as u32) < self.max_vcpus() { let state = &self.vcpu_states[usize::from(self.selected_cpu)]; if state.active() { data[0] |= 1 << CPU_ENABLE_FLAG; @@ -588,7 +588,7 @@ impl BusDevice for CpuManager { self.selected_cpu = data[0]; } CPU_STATUS_OFFSET => { - if self.selected_cpu < self.max_vcpus() { + if (self.selected_cpu as u32) < self.max_vcpus() { let state = &mut self.vcpu_states[usize::from(self.selected_cpu)]; // The ACPI code writes back a 1 to acknowledge the insertion if (data[0] & (1 << CPU_INSERTING_FLAG) == 1 << CPU_INSERTING_FLAG) @@ -604,7 +604,7 @@ impl BusDevice for CpuManager { } // Trigger removal of vCPU if data[0] & (1 << CPU_EJECT_FLAG) == 1 << CPU_EJECT_FLAG { - if let Err(e) = self.remove_vcpu(self.selected_cpu) { + if let Err(e) = self.remove_vcpu(self.selected_cpu as u32) { error!("Error removing vCPU: {:?}", e); } } @@ -730,7 +730,7 @@ impl CpuManager { } } - let proximity_domain_per_cpu: BTreeMap = { + let proximity_domain_per_cpu: BTreeMap = { let mut cpu_list = Vec::new(); for (proximity_domain, numa_node) in numa_nodes.iter() { for cpu in numa_node.cpus.iter() { @@ -745,7 +745,7 @@ impl CpuManager { let affinity = if let Some(cpu_affinity) = config.affinity.as_ref() { cpu_affinity .iter() - .map(|a| (a.vcpu, a.host_cpus.clone())) + .map(|a| (a.vcpu as u32, a.host_cpus.clone())) .collect() } else { BTreeMap::new() @@ -817,19 +817,19 @@ impl CpuManager { Ok(()) } - fn create_vcpu(&mut self, cpu_id: u8, snapshot: Option) -> Result>> { + fn create_vcpu(&mut self, cpu_id: u32, snapshot: Option) -> Result>> { info!("Creating vCPU: cpu_id = {}", cpu_id); #[cfg(target_arch = "x86_64")] let topology = self.get_vcpu_topology(); #[cfg(target_arch = "x86_64")] - let x2apic_id = arch::x86_64::get_x2apic_id(cpu_id as u32, topology); + let x2apic_id = arch::x86_64::get_x2apic_id(cpu_id, topology); #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] - let x2apic_id = cpu_id as u32; + let x2apic_id = cpu_id; let mut vcpu = Vcpu::new( cpu_id, - x2apic_id as u8, + x2apic_id, &self.vm, Some(self.vm_ops.clone()), #[cfg(target_arch = "x86_64")] @@ -884,7 +884,7 @@ impl CpuManager { #[cfg(target_arch = "x86_64")] let topology = self.config.topology.clone().map_or_else( - || Some((1, self.boot_vcpus(), 1)), + || Some((1, self.boot_vcpus().try_into().unwrap(), 1)), |t| Some((t.threads_per_core, t.cores_per_die, t.dies_per_package)), ); #[cfg(target_arch = "x86_64")] @@ -907,7 +907,7 @@ impl CpuManager { /// Only create new vCPUs if there aren't any inactive ones to reuse fn create_vcpus( &mut self, - desired_vcpus: u8, + desired_vcpus: u32, snapshot: Option, ) -> Result>>> { let mut vcpus: Vec>> = vec![]; @@ -919,12 +919,12 @@ impl CpuManager { self.present_vcpus() ); - if desired_vcpus > self.config.max_vcpus { + if desired_vcpus > self.config.max_vcpus as u32 { return Err(Error::DesiredVCpuCountExceedsMax); } // Only create vCPUs in excess of all the allocated vCPUs. - for cpu_id in self.vcpus.len() as u8..desired_vcpus { + for cpu_id in self.vcpus.len() as u32..desired_vcpus { vcpus.push(self.create_vcpu( cpu_id, // TODO: The special format of the CPU id can be removed once @@ -962,7 +962,7 @@ impl CpuManager { fn start_vcpu( &mut self, vcpu: Arc>, - vcpu_id: u8, + vcpu_id: u32, vcpu_thread_barrier: Arc, inserting: bool, ) -> Result<()> { @@ -977,12 +977,16 @@ impl CpuManager { let vcpu_pause_signalled = self.vcpus_pause_signalled.clone(); let vcpu_kick_signalled = self.vcpus_kick_signalled.clone(); - let vcpu_kill = self.vcpu_states[usize::from(vcpu_id)].kill.clone(); - let vcpu_run_interrupted = self.vcpu_states[usize::from(vcpu_id)] + let vcpu_kill = self.vcpu_states[usize::try_from(vcpu_id).unwrap()] + .kill + .clone(); + let vcpu_run_interrupted = self.vcpu_states[usize::try_from(vcpu_id).unwrap()] .vcpu_run_interrupted .clone(); let panic_vcpu_run_interrupted = vcpu_run_interrupted.clone(); - let vcpu_paused = self.vcpu_states[usize::from(vcpu_id)].paused.clone(); + let vcpu_paused = self.vcpu_states[usize::try_from(vcpu_id).unwrap()] + .paused + .clone(); // Prepare the CPU set the current vCPU is expected to run onto. let cpuset = self.affinity.get(&vcpu_id).map(|host_cpus| { @@ -1213,8 +1217,8 @@ impl CpuManager { // On hot plug calls into this function entry_point is None. It is for // those hotplug CPU additions that we need to set the inserting flag. - self.vcpu_states[usize::from(vcpu_id)].handle = handle; - self.vcpu_states[usize::from(vcpu_id)].inserting = inserting; + self.vcpu_states[usize::try_from(vcpu_id).unwrap()].handle = handle; + self.vcpu_states[usize::try_from(vcpu_id).unwrap()].inserting = inserting; Ok(()) } @@ -1222,11 +1226,11 @@ impl CpuManager { /// Start up as many vCPUs threads as needed to reach `desired_vcpus` fn activate_vcpus( &mut self, - desired_vcpus: u8, + desired_vcpus: u32, inserting: bool, paused: Option, ) -> Result<()> { - if desired_vcpus > self.config.max_vcpus { + if desired_vcpus > self.config.max_vcpus as u32 { return Err(Error::DesiredVCpuCountExceedsMax); } @@ -1257,11 +1261,11 @@ impl CpuManager { Ok(()) } - fn mark_vcpus_for_removal(&mut self, desired_vcpus: u8) { + fn mark_vcpus_for_removal(&mut self, desired_vcpus: u32) { // Mark vCPUs for removal, actual removal happens on ejection for cpu_id in desired_vcpus..self.present_vcpus() { - self.vcpu_states[usize::from(cpu_id)].removing = true; - self.vcpu_states[usize::from(cpu_id)] + self.vcpu_states[usize::try_from(cpu_id).unwrap()].removing = true; + self.vcpu_states[usize::try_from(cpu_id).unwrap()] .pending_removal .store(true, Ordering::SeqCst); } @@ -1276,9 +1280,9 @@ impl CpuManager { false } - fn remove_vcpu(&mut self, cpu_id: u8) -> Result<()> { + fn remove_vcpu(&mut self, cpu_id: u32) -> Result<()> { info!("Removing vCPU: cpu_id = {}", cpu_id); - let state = &mut self.vcpu_states[usize::from(cpu_id)]; + let state = &mut self.vcpu_states[usize::try_from(cpu_id).unwrap()]; state.kill.store(true, Ordering::SeqCst); state.signal_thread(); state.join_thread()?; @@ -1306,7 +1310,7 @@ impl CpuManager { } pub fn start_restored_vcpus(&mut self) -> Result<()> { - self.activate_vcpus(self.vcpus.len() as u8, false, Some(true)) + self.activate_vcpus(self.vcpus.len() as u32, false, Some(true)) .map_err(|e| { Error::StartRestoreVcpu(anyhow!("Failed to start restored vCPUs: {:#?}", e)) })?; @@ -1314,7 +1318,7 @@ impl CpuManager { Ok(()) } - pub fn resize(&mut self, desired_vcpus: u8) -> Result { + pub fn resize(&mut self, desired_vcpus: u32) -> Result { if desired_vcpus.cmp(&self.present_vcpus()) == cmp::Ordering::Equal { return Ok(false); } @@ -1387,12 +1391,12 @@ impl CpuManager { Ok(()) } - pub fn boot_vcpus(&self) -> u8 { - self.config.boot_vcpus + pub fn boot_vcpus(&self) -> u32 { + self.config.boot_vcpus as u32 } - pub fn max_vcpus(&self) -> u8 { - self.config.max_vcpus + pub fn max_vcpus(&self) -> u32 { + self.config.max_vcpus as u32 } #[cfg(target_arch = "x86_64")] @@ -1401,10 +1405,10 @@ impl CpuManager { self.cpuid.clone() } - fn present_vcpus(&self) -> u8 { + fn present_vcpus(&self) -> u32 { self.vcpu_states .iter() - .fold(0, |acc, state| acc + state.active() as u8) + .fold(0, |acc, state| acc + state.active() as u32) } #[cfg(target_arch = "aarch64")] @@ -1441,15 +1445,15 @@ impl CpuManager { { madt.write(36, arch::layout::APIC_START.0); - for cpu in 0..self.config.max_vcpus { - let x2apic_id = get_x2apic_id(cpu.into(), self.get_vcpu_topology()); + for cpu in 0..self.config.max_vcpus as u32 { + let x2apic_id = get_x2apic_id(cpu, self.get_vcpu_topology()); let lapic = LocalX2Apic { r#type: acpi::ACPI_X2APIC_PROCESSOR, length: 16, - processor_id: cpu.into(), + processor_id: cpu, apic_id: x2apic_id, - flags: if cpu < self.config.boot_vcpus { + flags: if cpu < self.config.boot_vcpus as u32 { 1 << MADT_CPU_ENABLE_FLAG } else { 0 @@ -1571,7 +1575,8 @@ impl CpuManager { // 1 package, multiple cores, 1 thread per core // This is also the behavior when PPTT is missing. let (threads_per_core, cores_per_package, packages) = - self.get_vcpu_topology().unwrap_or((1, self.max_vcpus(), 1)); + self.get_vcpu_topology() + .unwrap_or((1, self.max_vcpus().try_into().unwrap(), 1)); let mut pptt = Sdt::new(*b"PPTT", 36, 2, *b"CLOUDH", *b"CHPPTT ", 1); @@ -1922,7 +1927,7 @@ impl CpuManager { } struct Cpu { - cpu_id: u8, + cpu_id: u32, proximity_domain: u32, dynamic: bool, #[cfg(target_arch = "x86_64")] @@ -1938,12 +1943,12 @@ const MADT_CPU_ONLINE_CAPABLE_FLAG: usize = 1; impl Cpu { #[cfg(target_arch = "x86_64")] fn generate_mat(&self) -> Vec { - let x2apic_id = arch::x86_64::get_x2apic_id(self.cpu_id.into(), self.topology); + let x2apic_id = arch::x86_64::get_x2apic_id(self.cpu_id, self.topology); let lapic = LocalX2Apic { r#type: crate::acpi::ACPI_X2APIC_PROCESSOR, length: 16, - processor_id: self.cpu_id.into(), + processor_id: self.cpu_id, apic_id: x2apic_id, flags: 1 << MADT_CPU_ENABLE_FLAG, _reserved: 0, @@ -2045,7 +2050,7 @@ impl Aml for Cpu { } struct CpuNotify { - cpu_id: u8, + cpu_id: u32, } impl Aml for CpuNotify { @@ -2060,7 +2065,7 @@ impl Aml for CpuNotify { } struct CpuMethods { - max_vcpus: u8, + max_vcpus: u32, dynamic: bool, } @@ -2098,7 +2103,7 @@ impl Aml for CpuMethods { let mut cpu_notifies_refs: Vec<&dyn Aml> = Vec::new(); for cpu_id in 0..self.max_vcpus { - cpu_notifies_refs.push(&cpu_notifies[usize::from(cpu_id)]); + cpu_notifies_refs.push(&cpu_notifies[usize::try_from(cpu_id).unwrap()]); } aml::Method::new("CTFY".into(), 2, true, cpu_notifies_refs).to_aml_bytes(sink); @@ -2242,7 +2247,7 @@ impl Aml for CpuManager { let uid = aml::Name::new("_CID".into(), &aml::EISAName::new("PNP0A05")); // Bundle methods together under a common object let methods = CpuMethods { - max_vcpus: self.config.max_vcpus, + max_vcpus: self.config.max_vcpus as u32, dynamic: self.dynamic, }; let mut cpu_data_inner: Vec<&dyn Aml> = vec![&hid, &uid, &methods]; @@ -2250,7 +2255,7 @@ impl Aml for CpuManager { #[cfg(target_arch = "x86_64")] let topology = self.get_vcpu_topology(); let mut cpu_devices = Vec::new(); - for cpu_id in 0..self.config.max_vcpus { + for cpu_id in 0..(self.config.max_vcpus as u32) { let proximity_domain = *self.proximity_domain_per_cpu.get(&cpu_id).unwrap_or(&0); let cpu_device = Cpu { cpu_id, @@ -2624,7 +2629,7 @@ impl CpuElf64Writable for CpuManager { pos += descsz - size_of::() - size_of::(); let orig_rax: u64 = 0; - let gregs = self.vcpus[usize::from(vcpu_id)] + let gregs = self.vcpus[usize::try_from(vcpu_id).unwrap()] .lock() .unwrap() .vcpu @@ -2652,7 +2657,7 @@ impl CpuElf64Writable for CpuManager { orig_rax, ]; - let sregs = self.vcpus[usize::from(vcpu_id)] + let sregs = self.vcpus[usize::try_from(vcpu_id).unwrap()] .lock() .unwrap() .vcpu @@ -2726,7 +2731,7 @@ impl CpuElf64Writable for CpuManager { pos += round_up!(COREDUMP_NAME_SIZE as usize, 4); - let gregs = self.vcpus[usize::from(vcpu_id)] + let gregs = self.vcpus[usize::try_from(vcpu_id).unwrap()] .lock() .unwrap() .vcpu @@ -2755,7 +2760,7 @@ impl CpuElf64Writable for CpuManager { gregs.get_r15(), ]; - let sregs = self.vcpus[usize::from(vcpu_id)] + let sregs = self.vcpus[usize::try_from(vcpu_id).unwrap()] .lock() .unwrap() .vcpu diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 53e2d1a4ac..f43c50b11f 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1672,7 +1672,7 @@ impl DeviceManager { ) -> DeviceManagerResult>> { let interrupt_controller: Arc> = Arc::new(Mutex::new( gic::Gic::new( - self.config.lock().unwrap().cpus.boot_vcpus, + self.config.lock().unwrap().cpus.boot_vcpus as u32, Arc::clone(&self.msi_interrupt_manager), self.address_manager.vm.clone(), ) @@ -1725,7 +1725,7 @@ impl DeviceManager { ) -> DeviceManagerResult>> { let interrupt_controller: Arc> = Arc::new(Mutex::new( aia::Aia::new( - self.config.lock().unwrap().cpus.boot_vcpus, + self.config.lock().unwrap().cpus.boot_vcpus as u32, Arc::clone(&self.msi_interrupt_manager), self.address_manager.vm.clone(), ) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index e51573159b..f17c4b79d0 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1808,7 +1808,7 @@ impl RequestHandler for Vmm { fn vm_resize( &mut self, - desired_vcpus: Option, + desired_vcpus: Option, desired_ram: Option, desired_balloon: Option, ) -> result::Result<(), VmError> { @@ -1824,7 +1824,7 @@ impl RequestHandler for Vmm { } else { let mut config = self.vm_config.as_ref().unwrap().lock().unwrap(); if let Some(desired_vcpus) = desired_vcpus { - config.cpus.boot_vcpus = desired_vcpus; + config.cpus.boot_vcpus = desired_vcpus.try_into().unwrap(); } if let Some(desired_ram) = desired_ram { config.memory.size = desired_ram; diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 4935bc2e9f..40579b9ccb 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -621,7 +621,7 @@ impl Vm { #[cfg(feature = "tdx")] if tdx_enabled { let cpuid = cpu_manager.lock().unwrap().common_cpuid(); - let max_vcpus = cpu_manager.lock().unwrap().max_vcpus() as u32; + let max_vcpus = cpu_manager.lock().unwrap().max_vcpus(); vm.tdx_init(&cpuid, max_vcpus) .map_err(Error::InitializeTdxVm)?; } @@ -948,7 +948,7 @@ impl Vm { } if let Some(cpus) = &config.cpus { - node.cpus.extend(cpus); + node.cpus.extend(cpus.iter().map(|cpu| *cpu as u32)); } if let Some(pci_segments) = &config.pci_segments { @@ -1719,7 +1719,7 @@ impl Vm { pub fn resize( &mut self, - desired_vcpus: Option, + desired_vcpus: Option, desired_memory: Option, desired_balloon: Option, ) -> Result<()> { @@ -1739,7 +1739,7 @@ impl Vm { .notify_hotplug(AcpiNotificationFlags::CPU_DEVICES_CHANGED) .map_err(Error::DeviceManager)?; } - self.config.lock().unwrap().cpus.boot_vcpus = desired_vcpus; + self.config.lock().unwrap().cpus.boot_vcpus = desired_vcpus.try_into().unwrap(); } if let Some(desired_memory) = desired_memory { From 35121c44db2ae14c2e306018eaacc6367d6362fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 06:55:55 +0000 Subject: [PATCH 0086/1893] build: Bump slab from 0.4.9 to 0.4.11 Bumps [slab](https://github.com/tokio-rs/slab) from 0.4.9 to 0.4.11. - [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.9...v0.4.11) --- updated-dependencies: - dependency-name: slab dependency-version: 0.4.11 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a78ae54c2..e55ceabf94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1977,12 +1977,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" From d70d132a5c5fd86a5a77663524575eb75adcdd81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:34:39 +0000 Subject: [PATCH 0087/1893] build: Bump errno from 0.3.12 to 0.3.13 Bumps [errno](https://github.com/lambda-fairy/rust-errno) from 0.3.12 to 0.3.13. - [Release notes](https://github.com/lambda-fairy/rust-errno/releases) - [Changelog](https://github.com/lambda-fairy/rust-errno/blob/main/CHANGELOG.md) - [Commits](https://github.com/lambda-fairy/rust-errno/compare/v0.3.12...v0.3.13) --- updated-dependencies: - dependency-name: errno dependency-version: 0.3.13 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e55ceabf94..63204332ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -652,12 +652,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1838,7 +1838,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1851,7 +1851,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] From 458721629c981d21abe38d7eb707f9fb7190a2df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:16:24 +0000 Subject: [PATCH 0088/1893] build: Bump zvariant from 5.5.3 to 5.6.0 Bumps [zvariant](https://github.com/dbus2/zbus) from 5.5.3 to 5.6.0. - [Release notes](https://github.com/dbus2/zbus/releases) - [Commits](https://github.com/dbus2/zbus/compare/zvariant-5.5.3...zvariant-5.6.0) --- updated-dependencies: - dependency-name: zvariant dependency-version: 5.6.0 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63204332ba..110c9a5919 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2957,9 +2957,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.5.3" +version = "5.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d30786f75e393ee63a21de4f9074d4c038d52c5b1bb4471f955db249f9dffb1" +checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" dependencies = [ "endi", "enumflags2", @@ -2971,9 +2971,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.5.3" +version = "5.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75fda702cd42d735ccd48117b1630432219c0e9616bf6cb0f8350844ee4d9580" +checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" dependencies = [ "proc-macro-crate", "proc-macro2", From 9283f87f907bbd4483fc794ecd75c7702f67db1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:19:53 +0000 Subject: [PATCH 0089/1893] build: Bump igvm from `01daa63` to `dff4ebc` Bumps [igvm](https://github.com/microsoft/igvm) from `01daa63` to `dff4ebc`. - [Release notes](https://github.com/microsoft/igvm/releases) - [Commits](https://github.com/microsoft/igvm/compare/01daa631a596459cb4de58505881007dd13d4410...dff4ebc9c5bd16707ff75de26ccabe2d4dfdbcd8) --- updated-dependencies: - dependency-name: igvm dependency-version: dff4ebc9c5bd16707ff75de26ccabe2d4dfdbcd8 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 110c9a5919..606be04202 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,8 +950,8 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "igvm" -version = "0.3.4" -source = "git+https://github.com/microsoft/igvm?branch=main#01daa631a596459cb4de58505881007dd13d4410" +version = "0.4.0" +source = "git+https://github.com/microsoft/igvm?branch=main#dff4ebc9c5bd16707ff75de26ccabe2d4dfdbcd8" dependencies = [ "bitfield-struct", "crc32fast", @@ -967,8 +967,8 @@ dependencies = [ [[package]] name = "igvm_defs" -version = "0.3.4" -source = "git+https://github.com/microsoft/igvm?branch=main#01daa631a596459cb4de58505881007dd13d4410" +version = "0.4.0" +source = "git+https://github.com/microsoft/igvm?branch=main#dff4ebc9c5bd16707ff75de26ccabe2d4dfdbcd8" dependencies = [ "bitfield-struct", "open-enum", From 364a0972f04703731a1e04f5e8ed15ca3256afac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 23:09:12 +0000 Subject: [PATCH 0090/1893] build: Bump rustversion from 1.0.21 to 1.0.22 Bumps [rustversion](https://github.com/dtolnay/rustversion) from 1.0.21 to 1.0.22. - [Release notes](https://github.com/dtolnay/rustversion/releases) - [Commits](https://github.com/dtolnay/rustversion/compare/1.0.21...1.0.22) --- updated-dependencies: - dependency-name: rustversion dependency-version: 1.0.22 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 606be04202..cce1452830 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1856,9 +1856,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" From 6e0403a959092495cda632a8df2bea66bebd9237 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Tue, 12 Aug 2025 22:30:37 +0000 Subject: [PATCH 0091/1893] misc: make topology a 4-tuple of u16s This is the second patch in a series intended to let Cloud Hypervisor support more than 255 vCPUs in guest VMs; the first patch/commit is https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7231 At the moment, CPU topology in Cloud Hypervisor is using u8 for components, and somewhat inconsistently: - struct CpuTopology in vmm/src/vm_config.rs uses four components (threads_per_core, cores_per_die, dies_per_package, packages); - when passed around as a tuple, it is a 3-tuple of u8, with some inconsistency: - in get_x2apic_id in arch/src/x86_64/mod.rs the three u8 are assumed to be (correctly) threads_per_core, cores_per_die, and dies_per_package, but - in get_vcpu_topology() in vmm/src/cpu.rs the three-tuple is threads_per_core, cores_per_die, and packages (dies_per_package is assumed to always be one? not clear). So for consistency, a 4-tuple is always passed around. In addition, the types of the tuple components is changed from u8 to u16, as on x86_64 subcomponents can consume up to 16 bits. Again, config constraints have not been changed, so this patch is mostly NOOP. Signed-off-by: Barret Rhoden Signed-off-by: Neel Natu Signed-off-by: Ofir Weisse Signed-off-by: Peter Oskolkov --- arch/src/aarch64/fdt.rs | 14 ++++++---- arch/src/aarch64/mod.rs | 2 +- arch/src/x86_64/mod.rs | 57 +++++++++++++++++++++++++------------- arch/src/x86_64/mptable.rs | 2 +- vmm/src/acpi.rs | 2 +- vmm/src/cpu.rs | 43 ++++++++++++++++++++-------- vmm/src/vm.rs | 2 +- 7 files changed, 81 insertions(+), 41 deletions(-) diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index 238ad0b32a..aefecf8b2d 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -218,7 +218,7 @@ pub fn create_fdt, - vcpu_topology: Option<(u8, u8, u8)>, + vcpu_topology: Option<(u16, u16, u16, u16)>, device_info: &HashMap<(DeviceType, String), T, S>, gic_device: &Arc>, initrd: &Option, @@ -280,7 +280,7 @@ pub fn write_fdt_to_memory(fdt_final: Vec, guest_mem: &GuestMemoryMmap) -> R fn create_cpu_nodes( fdt: &mut FdtWriter, vcpu_mpidr: &[u64], - vcpu_topology: Option<(u8, u8, u8)>, + vcpu_topology: Option<(u16, u16, u16, u16)>, numa_nodes: &NumaNodes, ) -> FdtWriterResult<()> { // See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml. @@ -289,8 +289,11 @@ fn create_cpu_nodes( fdt.property_u32("#size-cells", 0x0)?; let num_cpus = vcpu_mpidr.len(); - let (threads_per_core, cores_per_package, packages) = vcpu_topology.unwrap_or((1, 1, 1)); - let max_cpus: u32 = (threads_per_core * cores_per_package * packages).into(); + let (threads_per_core, cores_per_die, dies_per_package, packages) = + vcpu_topology.unwrap_or((1, 1, 1, 1)); + let cores_per_package = cores_per_die * dies_per_package; + let max_cpus: u32 = + 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. @@ -462,7 +465,8 @@ fn create_cpu_nodes( } if let Some(topology) = vcpu_topology { - let (threads_per_core, cores_per_package, packages) = topology; + let (threads_per_core, cores_per_die, dies_per_package, packages) = topology; + let cores_per_package = cores_per_die * dies_per_package; let cpu_map_node = fdt.begin_node("cpu-map")?; // Create device tree nodes with regard of above mapping. diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index c807429146..f7a6c36539 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -126,7 +126,7 @@ pub fn configure_system, - vcpu_topology: Option<(u8, u8, u8)>, + vcpu_topology: Option<(u16, u16, u16, u16)>, device_info: &HashMap<(DeviceType, String), T, S>, initrd: &Option, pci_space_info: &[PciSpaceInfo], diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 35ec4b9253..6dcac04f1b 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -209,11 +209,11 @@ pub enum Error { E820Configuration, } -pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u8, u8, u8)>) -> u32 { +pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u16, u16, u16, u16)>) -> u32 { if let Some(t) = topology { - let thread_mask_width = u8::BITS - (t.0 - 1).leading_zeros(); - let core_mask_width = u8::BITS - (t.1 - 1).leading_zeros(); - let die_mask_width = u8::BITS - (t.2 - 1).leading_zeros(); + let thread_mask_width = u16::BITS - (t.0 - 1).leading_zeros(); + let core_mask_width = u16::BITS - (t.1 - 1).leading_zeros(); + let die_mask_width = u16::BITS - (t.2 - 1).leading_zeros(); let thread_id = cpu_id % (t.0 as u32); let core_id = cpu_id / (t.0 as u32) % (t.1 as u32); @@ -229,6 +229,13 @@ pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u8, u8, u8)>) -> u32 { cpu_id } +pub fn get_max_x2apic_id(topology: (u16, u16, u16, u16)) -> u32 { + get_x2apic_id( + (topology.0 as u32 * topology.1 as u32 * topology.2 as u32 * topology.3 as u32) - 1, + Some(topology), + ) +} + #[derive(Copy, Clone, Debug)] pub enum CpuidReg { EAX, @@ -825,7 +832,7 @@ pub fn configure_vcpu( cpuid: Vec, kvm_hyperv: bool, cpu_vendor: CpuVendor, - topology: Option<(u8, u8, u8)>, + topology: Option<(u16, u16, u16, u16)>, ) -> super::Result<()> { let x2apic_id = get_x2apic_id(id, topology); @@ -850,7 +857,7 @@ pub fn configure_vcpu( assert!(apic_id_patched); if let Some(t) = topology { - update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, cpu_vendor, id); + update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, t.3, cpu_vendor, id); } // The TSC frequency CPUID leaf should not be included when running with HyperV emulation @@ -953,7 +960,7 @@ pub fn configure_system( serial_number: Option<&str>, uuid: Option<&str>, oem_strings: Option<&[&str]>, - topology: Option<(u8, u8, u8)>, + topology: Option<(u16, u16, u16, u16)>, ) -> super::Result<()> { // Write EBDA address to location where ACPICA expects to find it guest_mem @@ -1361,21 +1368,24 @@ pub fn get_host_cpu_phys_bits(hypervisor: &Arc) -> u fn update_cpuid_topology( cpuid: &mut Vec, - threads_per_core: u8, - cores_per_die: u8, - dies_per_package: u8, + threads_per_core: u16, + cores_per_die: u16, + dies_per_package: u16, + packages: u16, cpu_vendor: CpuVendor, id: u32, ) { let x2apic_id = get_x2apic_id( id, - Some((threads_per_core, cores_per_die, dies_per_package)), + Some((threads_per_core, cores_per_die, dies_per_package, packages)), ); - let thread_width = 8 - (threads_per_core - 1).leading_zeros(); - let core_width = (8 - (cores_per_die - 1).leading_zeros()) + thread_width; - let die_width = (8 - (dies_per_package - 1).leading_zeros()) + core_width; + // Note: the topology defined here is per "package" (~NUMA node). + let thread_width = u16::BITS - (threads_per_core - 1).leading_zeros(); + let core_width = u16::BITS - (cores_per_die - 1).leading_zeros() + thread_width; + let die_width = u16::BITS - (dies_per_package - 1).leading_zeros() + core_width; + // The very old way: a flat number of logical CPUs per package: CPUID.1H:EBX[23:16] bits. let mut cpu_ebx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX).unwrap_or(0); cpu_ebx |= ((dies_per_package as u32) * (cores_per_die as u32) * (threads_per_core as u32)) & (0xff << 16); @@ -1385,6 +1395,7 @@ fn update_cpuid_topology( cpu_edx |= 1 << 28; CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX, cpu_edx); + // The legacy way: threads+cores per package. // CPU Topology leaf 0xb CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(0), CpuidReg::EAX, thread_width); CpuidPatch::set_cpuid_reg( @@ -1407,6 +1418,7 @@ fn update_cpuid_topology( CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::ECX, 2 << 8); CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::EDX, x2apic_id); + // The modern way: many-level hierarchy (but we here only support four levels). // CPU Topology leaf 0x1f CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::EAX, thread_width); CpuidPatch::set_cpuid_reg( @@ -1721,22 +1733,27 @@ mod tests { #[test] fn test_get_x2apic_id() { - let x2apic_id = get_x2apic_id(0, Some((2, 3, 1))); + let x2apic_id = get_x2apic_id(0, Some((2, 3, 1, 1))); assert_eq!(x2apic_id, 0); - let x2apic_id = get_x2apic_id(1, Some((2, 3, 1))); + let x2apic_id = get_x2apic_id(1, Some((2, 3, 1, 1))); assert_eq!(x2apic_id, 1); - let x2apic_id = get_x2apic_id(2, Some((2, 3, 1))); + let x2apic_id = get_x2apic_id(2, Some((2, 3, 1, 1))); assert_eq!(x2apic_id, 2); - let x2apic_id = get_x2apic_id(6, Some((2, 3, 1))); + let x2apic_id = get_x2apic_id(6, Some((2, 3, 1, 1))); assert_eq!(x2apic_id, 8); - let x2apic_id = get_x2apic_id(7, Some((2, 3, 1))); + let x2apic_id = get_x2apic_id(7, Some((2, 3, 1, 1))); assert_eq!(x2apic_id, 9); - let x2apic_id = get_x2apic_id(8, Some((2, 3, 1))); + let x2apic_id = get_x2apic_id(8, Some((2, 3, 1, 1))); assert_eq!(x2apic_id, 10); + + let x2apic_id = get_x2apic_id(257, Some((1, 312, 1, 1))); + assert_eq!(x2apic_id, 257); + + assert_eq!(255, get_max_x2apic_id((1, 256, 1, 1))); } } diff --git a/arch/src/x86_64/mptable.rs b/arch/src/x86_64/mptable.rs index 42667df9c2..d709a0043c 100644 --- a/arch/src/x86_64/mptable.rs +++ b/arch/src/x86_64/mptable.rs @@ -136,7 +136,7 @@ pub fn setup_mptable( offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u32, - topology: Option<(u8, u8, u8)>, + topology: Option<(u16, u16, u16, u16)>, ) -> Result<()> { if num_cpus > 0 { let cpu_id_max = num_cpus - 1; diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index b9e809e4a1..a2299acd84 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -285,7 +285,7 @@ fn create_tpm2_table() -> Sdt { fn create_srat_table( numa_nodes: &NumaNodes, - #[cfg(target_arch = "x86_64")] topology: Option<(u8, u8, u8)>, + #[cfg(target_arch = "x86_64")] topology: Option<(u16, u16, u16, u16)>, ) -> Sdt { let mut srat = Sdt::new(*b"SRAT", 36, 3, *b"CLOUDH", *b"CHSRAT ", 1); // SRAT reserved 12 bytes diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index e5813562b1..7b16b39886 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -390,7 +390,7 @@ impl Vcpu { boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, #[cfg(target_arch = "x86_64")] cpuid: Vec, #[cfg(target_arch = "x86_64")] kvm_hyperv: bool, - #[cfg(target_arch = "x86_64")] topology: Option<(u8, u8, u8)>, + #[cfg(target_arch = "x86_64")] topology: Option<(u16, u16, u16, u16)>, ) -> Result<()> { #[cfg(target_arch = "aarch64")] { @@ -884,8 +884,22 @@ impl CpuManager { #[cfg(target_arch = "x86_64")] let topology = self.config.topology.clone().map_or_else( - || Some((1, self.boot_vcpus().try_into().unwrap(), 1)), - |t| Some((t.threads_per_core, t.cores_per_die, t.dies_per_package)), + || { + Some(( + 1_u16, + u16::try_from(self.boot_vcpus()).unwrap(), + 1_u16, + 1_u16, + )) + }, + |t| { + Some(( + t.threads_per_core.into(), + t.cores_per_die.into(), + t.dies_per_package.into(), + t.packages.into(), + )) + }, ); #[cfg(target_arch = "x86_64")] vcpu.configure( @@ -1427,11 +1441,15 @@ impl CpuManager { .collect() } - pub fn get_vcpu_topology(&self) -> Option<(u8, u8, u8)> { - self.config - .topology - .clone() - .map(|t| (t.threads_per_core, t.cores_per_die, t.packages)) + pub fn get_vcpu_topology(&self) -> Option<(u16, u16, u16, u16)> { + self.config.topology.clone().map(|t| { + ( + t.threads_per_core.into(), + t.cores_per_die.into(), + t.dies_per_package.into(), + t.packages.into(), + ) + }) } #[cfg(not(target_arch = "riscv64"))] @@ -1574,9 +1592,10 @@ impl CpuManager { // If topology is not specified, the default setting is: // 1 package, multiple cores, 1 thread per core // This is also the behavior when PPTT is missing. - let (threads_per_core, cores_per_package, packages) = - self.get_vcpu_topology() - .unwrap_or((1, self.max_vcpus().try_into().unwrap(), 1)); + let (threads_per_core, cores_per_die, dies_per_package, packages) = self + .get_vcpu_topology() + .unwrap_or((1, u16::try_from(self.max_vcpus()).unwrap(), 1, 1)); + let cores_per_package = cores_per_die * dies_per_package; let mut pptt = Sdt::new(*b"PPTT", 36, 2, *b"CLOUDH", *b"CHPPTT ", 1); @@ -1931,7 +1950,7 @@ struct Cpu { proximity_domain: u32, dynamic: bool, #[cfg(target_arch = "x86_64")] - topology: Option<(u8, u8, u8)>, + topology: Option<(u16, u16, u16, u16)>, } #[cfg(target_arch = "x86_64")] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 40579b9ccb..7e8851194d 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -3582,7 +3582,7 @@ mod tests { &mem, "console=tty0", vec![0], - Some((0, 0, 0)), + Some((0, 0, 0, 0)), &dev_info, &gic, &None, From 68a1bf38e52a39dc47ad250d8ee1f080a51258e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 01:12:38 +0000 Subject: [PATCH 0092/1893] build: Bump gdbstub_arch from 0.3.0 to 0.3.2 Bumps [gdbstub_arch](https://github.com/daniel5151/gdbstub) from 0.3.0 to 0.3.2. - [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) --- updated-dependencies: - dependency-name: gdbstub_arch dependency-version: 0.3.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 10 +++++----- vmm/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cce1452830..a0d22f949d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,7 +657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -839,9 +839,9 @@ dependencies = [ [[package]] name = "gdbstub_arch" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3b1357bd3203fc09a6601327ae0ab38865d14231d0b65d3143f5762cc7977d" +checksum = "22dde0e1b68787036ccedd0b1ff6f953527a0e807e571fbe898975203027278f" dependencies = [ "gdbstub", "num-traits", @@ -1838,7 +1838,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1851,7 +1851,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 5567a58579..6b33e6344d 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -48,7 +48,7 @@ event_monitor = { path = "../event_monitor" } flume = { workspace = true } futures = { version = "0.3.31", optional = true } gdbstub = { version = "0.7.6", optional = true } -gdbstub_arch = { version = "0.3.0", optional = true } +gdbstub_arch = { version = "0.3.2", optional = true } hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } igvm = { workspace = true, optional = true } From 3d5e5f318ba8a8b2bebe6d95369bdff2385d065f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:48:26 +0000 Subject: [PATCH 0093/1893] build: Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [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/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/audit.yaml | 2 +- .github/workflows/build.yaml | 2 +- .github/workflows/dco.yaml | 2 +- .github/workflows/docker-image.yaml | 2 +- .github/workflows/formatting.yaml | 2 +- .github/workflows/fuzz-build.yaml | 2 +- .github/workflows/gitlint.yaml | 2 +- .github/workflows/hadolint.yaml | 2 +- .github/workflows/integration-arm64.yaml | 2 +- .github/workflows/integration-metrics.yaml | 2 +- .github/workflows/integration-rate-limiter.yaml | 2 +- .github/workflows/integration-vfio.yaml | 2 +- .github/workflows/integration-windows.yaml | 2 +- .github/workflows/integration-x86-64.yaml | 2 +- .github/workflows/lychee.yaml | 2 +- .github/workflows/openapi.yaml | 2 +- .github/workflows/package-consistency.yaml | 2 +- .github/workflows/preview-riscv64.yaml | 2 +- .github/workflows/quality.yaml | 4 ++-- .github/workflows/release.yaml | 2 +- .github/workflows/reuse.yaml | 2 +- .github/workflows/shlint.yaml | 2 +- .github/workflows/taplo.yaml | 2 +- 23 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/audit.yaml b/.github/workflows/audit.yaml index 2e44b9af40..cfc21696e1 100644 --- a/.github/workflows/audit.yaml +++ b/.github/workflows/audit.yaml @@ -10,7 +10,7 @@ jobs: name: Audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions-rust-lang/audit@v1 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5c32406646..c78750f78d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: - x86_64-unknown-linux-musl steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/dco.yaml b/.github/workflows/dco.yaml index 888b685820..4c83547872 100644 --- a/.github/workflows/dco.yaml +++ b/.github/workflows/dco.yaml @@ -6,7 +6,7 @@ jobs: name: DCO Check ("Signed-Off-By") runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python 3.x uses: actions/setup-python@v5 with: diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index b5bbdf4284..3e5f56458c 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/formatting.yaml b/.github/workflows/formatting.yaml index b6dd6cafc1..75e4492559 100644 --- a/.github/workflows/formatting.yaml +++ b/.github/workflows/formatting.yaml @@ -19,7 +19,7 @@ jobs: RUSTFLAGS: -D warnings steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Rust toolchain (${{ matrix.rust }}) uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/fuzz-build.yaml b/.github/workflows/fuzz-build.yaml index db868de2be..427189b01e 100644 --- a/.github/workflows/fuzz-build.yaml +++ b/.github/workflows/fuzz-build.yaml @@ -18,7 +18,7 @@ jobs: RUSTFLAGS: -D warnings steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Rust toolchain (${{ matrix.rust }}) uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/gitlint.yaml b/.github/workflows/gitlint.yaml index 11ebf707a4..c31fee202d 100644 --- a/.github/workflows/gitlint.yaml +++ b/.github/workflows/gitlint.yaml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 diff --git a/.github/workflows/hadolint.yaml b/.github/workflows/hadolint.yaml index 31b8910984..641d911c0c 100644 --- a/.github/workflows/hadolint.yaml +++ b/.github/workflows/hadolint.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Lint Dockerfile uses: hadolint/hadolint-action@master diff --git a/.github/workflows/integration-arm64.yaml b/.github/workflows/integration-arm64.yaml index d580a991cc..41a7bc824a 100644 --- a/.github/workflows/integration-arm64.yaml +++ b/.github/workflows/integration-arm64.yaml @@ -13,7 +13,7 @@ jobs: - name: Fix workspace permissions run: sudo chown -R runner:runner ${GITHUB_WORKSPACE} - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Run unit tests (musl) diff --git a/.github/workflows/integration-metrics.yaml b/.github/workflows/integration-metrics.yaml index 440e9ad850..e8dd72ea84 100644 --- a/.github/workflows/integration-metrics.yaml +++ b/.github/workflows/integration-metrics.yaml @@ -12,7 +12,7 @@ jobs: METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }} steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Run metrics tests diff --git a/.github/workflows/integration-rate-limiter.yaml b/.github/workflows/integration-rate-limiter.yaml index 5700bfe46f..91682f77f8 100644 --- a/.github/workflows/integration-rate-limiter.yaml +++ b/.github/workflows/integration-rate-limiter.yaml @@ -13,7 +13,7 @@ jobs: steps: - name: Code checkout if: ${{ github.event_name != 'pull_request' }} - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Run rate-limiter integration tests diff --git a/.github/workflows/integration-vfio.yaml b/.github/workflows/integration-vfio.yaml index 3549ace272..edd7399b15 100644 --- a/.github/workflows/integration-vfio.yaml +++ b/.github/workflows/integration-vfio.yaml @@ -16,7 +16,7 @@ jobs: run: sudo chown -R runner:runner ${GITHUB_WORKSPACE} - name: Code checkout if: ${{ github.event_name != 'pull_request' }} - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Run VFIO integration tests diff --git a/.github/workflows/integration-windows.yaml b/.github/workflows/integration-windows.yaml index 29aa04a78f..0769789a9d 100644 --- a/.github/workflows/integration-windows.yaml +++ b/.github/workflows/integration-windows.yaml @@ -11,7 +11,7 @@ jobs: steps: - name: Code checkout if: ${{ github.event_name != 'pull_request' }} - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install Docker diff --git a/.github/workflows/integration-x86-64.yaml b/.github/workflows/integration-x86-64.yaml index 80690512f5..8ed76f16a1 100644 --- a/.github/workflows/integration-x86-64.yaml +++ b/.github/workflows/integration-x86-64.yaml @@ -17,7 +17,7 @@ jobs: steps: - name: Code checkout if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }} - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install Docker diff --git a/.github/workflows/lychee.yaml b/.github/workflows/lychee.yaml index 68271c509b..191d53e6a3 100644 --- a/.github/workflows/lychee.yaml +++ b/.github/workflows/lychee.yaml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # Fetch the entire history so git diff can compare against the base branch fetch-depth: 0 diff --git a/.github/workflows/openapi.yaml b/.github/workflows/openapi.yaml index 0cd5b848cc..9c1266e4d7 100644 --- a/.github/workflows/openapi.yaml +++ b/.github/workflows/openapi.yaml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest container: openapitools/openapi-generator-cli steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Validate OpenAPI env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package-consistency.yaml b/.github/workflows/package-consistency.yaml index 0c57baa6c0..719aa3d8df 100644 --- a/.github/workflows/package-consistency.yaml +++ b/.github/workflows/package-consistency.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/preview-riscv64.yaml b/.github/workflows/preview-riscv64.yaml index 6e4c5071e3..02f796b590 100644 --- a/.github/workflows/preview-riscv64.yaml +++ b/.github/workflows/preview-riscv64.yaml @@ -19,7 +19,7 @@ jobs: steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 576acaffa9..feecbe60cb 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -29,7 +29,7 @@ jobs: steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 @@ -155,6 +155,6 @@ jobs: name: Typos / Spellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Executes "typos ." - uses: crate-ci/typos@v1.35.3 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ef1eb3573c..44842475d1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install musl-gcc if: contains(matrix.platform.target, 'musl') run: sudo apt install -y musl-tools diff --git a/.github/workflows/reuse.yaml b/.github/workflows/reuse.yaml index a2161c2818..3a463eedcd 100644 --- a/.github/workflows/reuse.yaml +++ b/.github/workflows/reuse.yaml @@ -7,6 +7,6 @@ jobs: name: REUSE Compliance Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: REUSE Compliance Check uses: fsfe/reuse-action@v5 diff --git a/.github/workflows/shlint.yaml b/.github/workflows/shlint.yaml index 9089964f06..b9208f3f20 100644 --- a/.github/workflows/shlint.yaml +++ b/.github/workflows/shlint.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Run the shell script checkers uses: luizm/action-sh-checker@master env: diff --git a/.github/workflows/taplo.yaml b/.github/workflows/taplo.yaml index 2b1e618984..75b61d9236 100644 --- a/.github/workflows/taplo.yaml +++ b/.github/workflows/taplo.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Install build dependencies From 10b79431f6c332d0f53f61abeb4207ec95c3f52f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:04:01 +0000 Subject: [PATCH 0094/1893] build: Bump linux-loader from `d5f39c0` to `5fdaed8` Bumps [linux-loader](https://github.com/rust-vmm/linux-loader) from `d5f39c0` to `5fdaed8`. - [Release notes](https://github.com/rust-vmm/linux-loader/releases) - [Commits](https://github.com/rust-vmm/linux-loader/compare/d5f39c09d59c8f50d5313b78ce4de511b12d1848...5fdaed87ddafc89d6abf0b50195a12d19133000d) --- updated-dependencies: - dependency-name: linux-loader dependency-version: 5fdaed87ddafc89d6abf0b50195a12d19133000d dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a0d22f949d..969857013a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1145,7 +1145,7 @@ dependencies = [ [[package]] name = "linux-loader" version = "0.13.0" -source = "git+https://github.com/rust-vmm/linux-loader?branch=main#d5f39c09d59c8f50d5313b78ce4de511b12d1848" +source = "git+https://github.com/rust-vmm/linux-loader?branch=main#5fdaed87ddafc89d6abf0b50195a12d19133000d" dependencies = [ "vm-memory", ] From 34385e99f20058e5a537ea36d795844c12fc2258 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Wed, 13 Aug 2025 19:26:24 +0000 Subject: [PATCH 0095/1893] misc: simplify configure_vcpu() signature on x86_64 It is always called with topology provided, so there is no need to pass topology as an Option. Simplifying the signature makes further topology-related changes to arc/src/x86_64 module simpler. Signed-off-by: Peter Oskolkov --- arch/src/x86_64/mod.rs | 10 +++++----- vmm/src/cpu.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 6dcac04f1b..e4c626835d 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -832,9 +832,9 @@ pub fn configure_vcpu( cpuid: Vec, kvm_hyperv: bool, cpu_vendor: CpuVendor, - topology: Option<(u16, u16, u16, u16)>, + topology: (u16, u16, u16, u16), ) -> super::Result<()> { - let x2apic_id = get_x2apic_id(id, topology); + let x2apic_id = get_x2apic_id(id, Some(topology)); // Per vCPU CPUID changes; common are handled via generate_common_cpuid() let mut cpuid = cpuid; @@ -856,9 +856,9 @@ pub fn configure_vcpu( } assert!(apic_id_patched); - if let Some(t) = topology { - update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, t.3, cpu_vendor, id); - } + update_cpuid_topology( + &mut cpuid, topology.0, topology.1, topology.2, topology.3, cpu_vendor, id, + ); // The TSC frequency CPUID leaf should not be included when running with HyperV emulation if !kvm_hyperv { diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 7b16b39886..629b0152b6 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -390,7 +390,7 @@ impl Vcpu { boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, #[cfg(target_arch = "x86_64")] cpuid: Vec, #[cfg(target_arch = "x86_64")] kvm_hyperv: bool, - #[cfg(target_arch = "x86_64")] topology: Option<(u16, u16, u16, u16)>, + #[cfg(target_arch = "x86_64")] topology: (u16, u16, u16, u16), ) -> Result<()> { #[cfg(target_arch = "aarch64")] { @@ -885,20 +885,20 @@ impl CpuManager { #[cfg(target_arch = "x86_64")] let topology = self.config.topology.clone().map_or_else( || { - Some(( + ( 1_u16, u16::try_from(self.boot_vcpus()).unwrap(), 1_u16, 1_u16, - )) + ) }, |t| { - Some(( + ( t.threads_per_core.into(), t.cores_per_die.into(), t.dies_per_package.into(), t.packages.into(), - )) + ) }, ); #[cfg(target_arch = "x86_64")] From 84613d4273481f8a11845d73fbcdc1809ca54c33 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Wed, 13 Aug 2025 19:36:56 +0000 Subject: [PATCH 0096/1893] arch: enable x2apic mode on x86_64 if max apic id > 254 This patch builds on PRs https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7231 https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7261 As before, the changes won't be effective until config parsing/validation raises the vCPU limit. Signed-off-by: Barret Rhoden Signed-off-by: Neel Natu Signed-off-by: Ofir Weisse Signed-off-by: Peter Oskolkov --- arch/src/x86_64/mod.rs | 16 +++++++++++++++- arch/src/x86_64/regs.rs | 16 +++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index e4c626835d..a4c0b88bf1 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -57,6 +57,8 @@ const KVM_FEATURE_ASYNC_PF_VMEXIT_BIT: u8 = 10; #[cfg(feature = "tdx")] const KVM_FEATURE_STEAL_TIME_BIT: u8 = 5; +const KVM_FEATURE_MSI_EXT_DEST_ID: u8 = 15; + pub const _NSIG: i32 = 65; #[derive(Debug, Copy, Clone)] @@ -745,6 +747,10 @@ pub fn generate_common_cpuid( entry.eax = (entry.eax & 0xffff_ff00) | (config.phys_bits as u32 & 0xff); } 0x4000_0001 => { + // Enable KVM_FEATURE_MSI_EXT_DEST_ID. This allows the guest to target + // device interrupts to cpus with APIC IDs > 254 without interrupt remapping. + entry.eax |= 1 << KVM_FEATURE_MSI_EXT_DEST_ID; + // These features are not supported by TDX #[cfg(feature = "tdx")] if config.tdx { @@ -903,7 +909,15 @@ pub fn configure_vcpu( 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)?; - regs::setup_sregs(&guest_memory.memory(), vcpu).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) >= 255; + regs::setup_sregs(&guest_memory.memory(), vcpu, enable_x2_apic_mode) + .map_err(Error::SregsConfiguration)?; } interrupts::set_lint(vcpu).map_err(|e| Error::LocalIntConfiguration(e.into()))?; Ok(()) diff --git a/arch/src/x86_64/regs.rs b/arch/src/x86_64/regs.rs index 3826fdb6ce..706dcd0622 100644 --- a/arch/src/x86_64/regs.rs +++ b/arch/src/x86_64/regs.rs @@ -119,9 +119,13 @@ pub fn setup_regs(vcpu: &Arc, entry_point: EntryPoint) -> /// /// * `mem` - The memory that will be passed to the guest. /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. -pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &Arc) -> Result<()> { +pub fn setup_sregs( + mem: &GuestMemoryMmap, + vcpu: &Arc, + enable_x2_apic_mode: bool, +) -> Result<()> { let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?; - configure_segments_and_sregs(mem, &mut sregs)?; + configure_segments_and_sregs(mem, &mut sregs, enable_x2_apic_mode)?; vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters) } @@ -148,6 +152,7 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> { pub fn configure_segments_and_sregs( mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters, + enable_x2_apic_mode: bool, ) -> Result<()> { let gdt_table: [u64; BOOT_GDT_MAX] = { // Configure GDT entries as specified by PVH boot protocol @@ -183,6 +188,11 @@ pub fn configure_segments_and_sregs( sregs.cr0 = CR0_PE; sregs.cr4 = 0; + if enable_x2_apic_mode { + const X2APIC_ENABLE_BIT: u64 = 1 << 10; + sregs.apic_base |= X2APIC_ENABLE_BIT; + } + Ok(()) } @@ -204,7 +214,7 @@ mod tests { fn segments_and_sregs() { let mut sregs: SpecialRegisters = Default::default(); let gm = create_guest_mem(); - configure_segments_and_sregs(&gm, &mut sregs).unwrap(); + configure_segments_and_sregs(&gm, &mut sregs, false).unwrap(); assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START)); assert_eq!( 0xcf9b000000ffff, From 55212b043702657671636238ad9cb228cbe57dc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:04:17 +0000 Subject: [PATCH 0097/1893] build: Bump crate-ci/typos from 1.35.3 to 1.35.4 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.3 to 1.35.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.35.3...v1.35.4) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.35.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index feecbe60cb..fb733b1a09 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -157,4 +157,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.35.3 + - uses: crate-ci/typos@v1.35.4 From bd17c84d3c4f2b3582851bf995cd9ac26c78d92f Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Tue, 17 Jun 2025 16:56:40 +0800 Subject: [PATCH 0098/1893] virtio-devices: move userspace mapping to vm-device Move UserspaceMapping to vm-device to avoid redefinition since UserspaceMapping is used by both `virtio-devices` and `device` crate. Signed-off-by: Songqian Li --- Cargo.lock | 1 + fuzz/fuzz_targets/pmem.rs | 3 ++- virtio-devices/src/device.rs | 10 +--------- virtio-devices/src/lib.rs | 4 ++-- virtio-devices/src/pmem.rs | 5 +++-- virtio-devices/src/vhost_user/fs.rs | 6 +++--- vm-device/Cargo.toml | 1 + vm-device/src/lib.rs | 10 ++++++++++ vmm/src/device_manager.rs | 4 ++-- 9 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 969857013a..dede7fb8d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2403,6 +2403,7 @@ dependencies = [ "serde", "thiserror 2.0.12", "vfio-ioctls", + "vm-memory", "vmm-sys-util", ] diff --git a/fuzz/fuzz_targets/pmem.rs b/fuzz/fuzz_targets/pmem.rs index e8cb488e77..e9247fb631 100644 --- a/fuzz/fuzz_targets/pmem.rs +++ b/fuzz/fuzz_targets/pmem.rs @@ -12,8 +12,9 @@ use std::{ffi, io}; use libc::{MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE}; use libfuzzer_sys::{fuzz_target, Corpus}; use seccompiler::SeccompAction; -use virtio_devices::{Pmem, UserspaceMapping, VirtioDevice, VirtioInterrupt, VirtioInterruptType}; +use virtio_devices::{Pmem, VirtioDevice, VirtioInterrupt, VirtioInterruptType}; use virtio_queue::{Queue, QueueT}; +use vm_device::UserspaceMapping; use vm_memory::bitmap::AtomicBitmap; use vm_memory::guest_memory::FileOffset; use vm_memory::{Bytes, GuestAddress, GuestMemoryAtomic, MmapRegion}; diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 06b245e271..c0d24902a1 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -15,6 +15,7 @@ use std::thread; use libc::EFD_NONBLOCK; use virtio_queue::Queue; +use vm_device::UserspaceMapping; use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestUsize}; use vm_migration::{MigratableError, Pausable}; use vm_virtio::{AccessPlatform, VirtioDeviceType}; @@ -37,15 +38,6 @@ pub trait VirtioInterrupt: Send + Sync { } } -#[derive(Clone)] -pub struct UserspaceMapping { - pub host_addr: u64, - pub mem_slot: u32, - pub addr: GuestAddress, - pub len: GuestUsize, - pub mergeable: bool, -} - #[derive(Clone)] pub struct VirtioSharedMemory { pub offset: u64, diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index 6a21eb2734..a59661eb61 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -47,8 +47,8 @@ pub use self::balloon::Balloon; pub use self::block::{Block, BlockState}; pub use self::console::{Console, ConsoleResizer, Endpoint}; pub use self::device::{ - DmaRemapping, UserspaceMapping, VirtioCommon, VirtioDevice, VirtioInterrupt, - VirtioInterruptType, VirtioSharedMemoryList, + DmaRemapping, VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioInterruptType, + VirtioSharedMemoryList, }; pub use self::epoll_helper::{ EpollHelper, EpollHelperError, EpollHelperHandler, EPOLL_HELPER_EVENT_LAST, diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 5f7ee9457b..1e4353477c 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -18,6 +18,7 @@ use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; use virtio_queue::{DescriptorChain, Queue, QueueT}; +use vm_device::UserspaceMapping; use vm_memory::{ Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryError, GuestMemoryLoadGuard, @@ -28,8 +29,8 @@ use vmm_sys_util::eventfd::EventFd; use super::{ ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, - Error as DeviceError, UserspaceMapping, VirtioCommon, VirtioDevice, VirtioDeviceType, - EPOLL_HELPER_EVENT_LAST, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, + Error as DeviceError, VirtioCommon, VirtioDevice, VirtioDeviceType, EPOLL_HELPER_EVENT_LAST, + VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 1a24f1c2dd..4aa6920475 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -11,6 +11,7 @@ use serde_with::{serde_as, Bytes}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; use virtio_queue::Queue; +use vm_device::UserspaceMapping; use vm_memory::{ByteValued, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; @@ -22,9 +23,8 @@ use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::VhostUserCommon; use crate::{ - ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, UserspaceMapping, VirtioCommon, - VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioSharedMemoryList, - VIRTIO_F_IOMMU_PLATFORM, + ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VirtioCommon, VirtioDevice, + VirtioDeviceType, VirtioInterrupt, VirtioSharedMemoryList, VIRTIO_F_IOMMU_PLATFORM, }; const NUM_QUEUE_OFFSET: usize = 1; diff --git a/vm-device/Cargo.toml b/vm-device/Cargo.toml index 9df6af3a0a..ca1d38807d 100644 --- a/vm-device/Cargo.toml +++ b/vm-device/Cargo.toml @@ -14,4 +14,5 @@ hypervisor = { path = "../hypervisor" } serde = { workspace = true, features = ["derive", "rc"] } thiserror = { workspace = true } vfio-ioctls = { workspace = true, default-features = false } +vm-memory = { workspace = true, features = ["backend-mmap"] } vmm-sys-util = { workspace = true } diff --git a/vm-device/src/lib.rs b/vm-device/src/lib.rs index c10731ea95..f484e9f14e 100644 --- a/vm-device/src/lib.rs +++ b/vm-device/src/lib.rs @@ -4,6 +4,7 @@ // use serde::{Deserialize, Serialize}; +use vm_memory::{GuestAddress, GuestUsize}; mod bus; pub mod dma_mapping; @@ -58,3 +59,12 @@ pub enum Resource { /// KVM memslot index. KvmMemSlot(u32), } + +#[derive(Clone)] +pub struct UserspaceMapping { + pub host_addr: u64, + pub mem_slot: u32, + pub addr: GuestAddress, + pub len: GuestUsize, + pub mergeable: bool, +} diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index f43c50b11f..ac1c0a406d 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -95,7 +95,7 @@ use vm_device::dma_mapping::ExternalDmaMapping; use vm_device::interrupt::{ InterruptIndex, InterruptManager, LegacyIrqGroupConfig, MsiIrqGroupConfig, }; -use vm_device::{Bus, BusDevice, BusDeviceSync, Resource}; +use vm_device::{Bus, BusDevice, BusDeviceSync, Resource, UserspaceMapping}; use vm_memory::guest_memory::FileOffset; use vm_memory::{Address, GuestAddress, GuestMemoryRegion, GuestUsize, MmapRegion}; #[cfg(target_arch = "x86_64")] @@ -3231,7 +3231,7 @@ impl DeviceManager { .create_userspace_mapping(region_base, region_size, host_addr, false, false, false) .map_err(DeviceManagerError::MemoryManager)?; - let mapping = virtio_devices::UserspaceMapping { + let mapping = UserspaceMapping { host_addr, mem_slot, addr: GuestAddress(region_base), From c72414552bfbfc2a7cc4af150ce3a6a5ff3b6ab0 Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Thu, 29 Aug 2024 15:53:32 +0800 Subject: [PATCH 0099/1893] devices: introduce ivshmem device This patch introduces the inter-vm shared memory(ivshmem) device to share a memory region between multiple processes running different guests and the host. This patch supports the basic ivshmem functions like ivshmem-plain in QEMU[1]. [1] https://www.qemu.org/docs/master/specs/ivshmem-spec.html Signed-off-by: Yi Wang Signed-off-by: Songqian Li --- devices/Cargo.toml | 1 + devices/src/ivshmem.rs | 361 +++++++++++++++++++++++++++++++++++++++++ devices/src/lib.rs | 4 + 3 files changed, 366 insertions(+) create mode 100644 devices/src/ivshmem.rs diff --git a/devices/Cargo.toml b/devices/Cargo.toml index d2fcc4a94a..2b985085f4 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -45,5 +45,6 @@ arch = { path = "../arch" } [features] default = [] fw_cfg = ["arch/fw_cfg", "bitfield-struct", "linux-loader", "zerocopy"] +ivshmem = [] kvm = ["arch/kvm"] pvmemcontrol = [] diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs new file mode 100644 index 0000000000..6e9ef4293e --- /dev/null +++ b/devices/src/ivshmem.rs @@ -0,0 +1,361 @@ +// Copyright © 2024 Tencent Corporation. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::any::Any; +use std::path::PathBuf; +use std::result; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{Arc, Barrier, Mutex}; + +use anyhow::anyhow; +use byteorder::{ByteOrder, LittleEndian}; +use pci::{ + BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, + PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass, + PCI_CONFIGURATION_ID, +}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use vm_allocator::{AddressAllocator, SystemAllocator}; +use vm_device::{BusDevice, Resource, UserspaceMapping}; +use vm_memory::bitmap::AtomicBitmap; +use vm_memory::{Address, GuestAddress}; +use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; +use vmm_sys_util::eventfd::EventFd; + +const IVSHMEM_BAR0_IDX: usize = 0; +const IVSHMEM_BAR1_IDX: usize = 1; +const IVSHMEM_BAR2_IDX: usize = 2; + +const IVSHMEM_VENDOR_ID: u16 = 0x1af4; +const IVSHMEM_DEVICE_ID: u16 = 0x1110; + +const IVSHMEM_REG_BAR_SIZE: u64 = 0x100; + +type GuestRegionMmap = vm_memory::GuestRegionMmap; + +#[derive(Debug, Error)] +pub enum IvshmemError { + #[error("Failed to retrieve PciConfigurationState: {0}")] + RetrievePciConfigurationState(#[source] anyhow::Error), + #[error("Failed to retrieve IvshmemDeviceState: {0}")] + RetrieveIvshmemDeviceStateState(#[source] anyhow::Error), +} + +#[derive(Copy, Clone)] +pub enum IvshmemSubclass { + Other = 0x00, +} + +impl PciSubclass for IvshmemSubclass { + fn get_register_value(&self) -> u8 { + *self as u8 + } +} + +pub struct IvshmemDevice { + id: String, + + // ivshmem device registers + interrupt_mask: u32, + interrupt_status: Arc, + iv_position: u32, + doorbell: u32, + + // PCI configuration registers. + configuration: PciConfiguration, + bar_regions: Vec, + + region: Option>, + region_size: u64, + userspace_mapping: Option, + reprogram_evt: EventFd, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct IvshmemDeviceState { + interrupt_mask: u32, + interrupt_status: u32, + iv_position: u32, + doorbell: u32, +} + +impl IvshmemDevice { + pub fn new( + id: String, + region_size: u64, + snapshot: Option, + ) -> Result { + let pci_configuration_state = + vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| { + IvshmemError::RetrievePciConfigurationState(anyhow!( + "Failed to get PciConfigurationState from Snapshot: {e}", + e + )) + })?; + + let state: Option = snapshot + .as_ref() + .map(|s| s.to_state()) + .transpose() + .map_err(|e| { + IvshmemError::RetrieveIvshmemDeviceStateState(anyhow!( + "Failed to get IvshmemDeviceState from Snapshot: {e}", + )) + })?; + + let configuration = PciConfiguration::new( + IVSHMEM_VENDOR_ID, + IVSHMEM_DEVICE_ID, + 0x1, + PciClassCode::MemoryController, + &IvshmemSubclass::Other, + None, + PciHeaderType::Device, + 0, + 0, + None, + pci_configuration_state, + ); + + let device = if let Some(s) = state { + IvshmemDevice { + id, + configuration, + bar_regions: vec![], + interrupt_mask: s.interrupt_mask, + interrupt_status: Arc::new(AtomicU32::new(s.interrupt_status)), + iv_position: s.iv_position, + doorbell: s.doorbell, + region_size, + region: None, + userspace_mapping: None, + } + } else { + IvshmemDevice { + id, + configuration, + bar_regions: vec![], + interrupt_mask: 0, + interrupt_status: Arc::new(AtomicU32::new(0)), + iv_position: 0, + doorbell: 0, + region_size, + region: None, + userspace_mapping: None, + } + }; + Ok(device) + } + + pub fn config_bar_addr(&self) -> u64 { + self.configuration.get_bar_addr(IVSHMEM_BAR0_IDX) + } + + pub fn data_bar_addr(&self) -> u64 { + self.configuration.get_bar_addr(IVSHMEM_BAR2_IDX) + } + + fn state(&self) -> IvshmemDeviceState { + IvshmemDeviceState { + interrupt_mask: self.interrupt_mask, + interrupt_status: self.interrupt_status.load(Ordering::SeqCst), + iv_position: self.iv_position, + doorbell: self.doorbell, + } + } +} + +impl BusDevice for IvshmemDevice { + fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) { + self.read_bar(base, offset, data) + } + + fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { + self.write_bar(base, offset, data) + } +} + +impl PciDevice for IvshmemDevice { + fn allocate_bars( + &mut self, + _allocator: &Arc>, + mmio32_allocator: &mut AddressAllocator, + mmio64_allocator: &mut AddressAllocator, + resources: Option>, + ) -> std::result::Result, PciDeviceError> { + let mut bars = Vec::new(); + let mut bar0_addr = None; + let mut bar2_addr = None; + + let restoring = resources.is_some(); + if let Some(resources) = resources { + for resource in resources { + match resource { + Resource::PciBar { index, base, .. } => { + match index { + IVSHMEM_BAR0_IDX => { + bar0_addr = Some(GuestAddress(base)); + } + IVSHMEM_BAR1_IDX => {} + IVSHMEM_BAR2_IDX => { + bar2_addr = Some(GuestAddress(base)); + } + _ => { + error!("Unexpected pci bar index {index}"); + } + }; + } + _ => { + error!("Unexpected resource {resource:?}"); + } + } + } + if bar0_addr.is_none() || bar2_addr.is_none() { + return Err(PciDeviceError::MissingResource); + } + } + + // BAR0 holds device registers (256 Byte MMIO) + let bar0_addr = mmio32_allocator + .allocate(bar0_addr, IVSHMEM_REG_BAR_SIZE, None) + .ok_or(PciDeviceError::IoAllocationFailed(IVSHMEM_REG_BAR_SIZE))?; + debug!("ivshmem bar0 address 0x{:x}", bar0_addr.0); + + let bar0 = PciBarConfiguration::default() + .set_index(IVSHMEM_BAR0_IDX) + .set_address(bar0_addr.raw_value()) + .set_size(IVSHMEM_REG_BAR_SIZE) + .set_region_type(PciBarRegionType::Memory32BitRegion) + .set_prefetchable(PciBarPrefetchable::NotPrefetchable); + + // BAR1 holds MSI-X table and PBA (only ivshmem-doorbell). + + // BAR2 maps the shared memory object + let bar2_size = self.region_size; + let bar2_addr = mmio64_allocator + .allocate(bar2_addr, bar2_size, None) + .ok_or(PciDeviceError::IoAllocationFailed(bar2_size))?; + debug!("ivshmem bar2 address 0x{:x}", bar2_addr.0); + + let bar2 = PciBarConfiguration::default() + .set_index(IVSHMEM_BAR2_IDX) + .set_address(bar2_addr.raw_value()) + .set_size(bar2_size) + .set_region_type(PciBarRegionType::Memory64BitRegion) + .set_prefetchable(PciBarPrefetchable::Prefetchable); + + if !restoring { + self.configuration + .add_pci_bar(&bar0) + .map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr.raw_value(), e))?; + self.configuration + .add_pci_bar(&bar2) + .map_err(|e| PciDeviceError::IoRegistrationFailed(bar2_addr.raw_value(), e))?; + } + + bars.push(bar0); + bars.push(bar2); + self.bar_regions = bars.clone(); + + Ok(bars) + } + + fn free_bars( + &mut self, + _allocator: &mut SystemAllocator, + _mmio32_allocator: &mut AddressAllocator, + _mmio64_allocator: &mut AddressAllocator, + ) -> std::result::Result<(), PciDeviceError> { + unimplemented!("Device hotplug and remove are not supported for ivshmem"); + } + + fn write_config_register( + &mut self, + reg_idx: usize, + offset: u64, + data: &[u8], + ) -> (Vec, Option>) { + ( + self.configuration + .write_config_register(reg_idx, offset, data), + None, + ) + } + + fn read_config_register(&mut self, reg_idx: usize) -> u32 { + self.configuration.read_reg(reg_idx) + } + + fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) { + debug!("read base {base:x} offset {offset}"); + + let mut bar_idx = 0; + for (idx, bar) in self.bar_regions.iter().enumerate() { + if bar.addr() == base { + bar_idx = idx; + } + } + match bar_idx { + // bar 0 + 0 => { + // ivshmem don't use interrupt, we return zero now. + LittleEndian::write_u32(data, 0); + } + // bar 2 + 1 => warn!("Unexpected read ivshmem memory idx: {offset}"), + _ => { + warn!("Invalid bar_idx: {bar_idx}"); + } + }; + } + + fn write_bar(&mut self, base: u64, offset: u64, _data: &[u8]) -> Option> { + debug!("write base {base:x} offset {offset}"); + warn!("Unexpected write ivshmem memory idx: {offset}"); + None + } + + fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> { + for bar in self.bar_regions.iter_mut() { + if bar.addr() == old_base { + *bar = bar.set_address(new_base); + } + } + + Ok(()) + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } +} + +impl Pausable for IvshmemDevice {} + +impl Snapshottable for IvshmemDevice { + fn id(&self) -> String { + self.id.clone() + } + + // The snapshot/restore (also live migration) support only work for ivshmem-plain mode. + // Additional work is needed for supporting ivshmem-doorbell. + fn snapshot(&mut self) -> std::result::Result { + let mut snapshot = Snapshot::new_from_state(&self.state())?; + + // Snapshot PciConfiguration + snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?); + + Ok(snapshot) + } +} + +impl Transportable for IvshmemDevice {} + +impl Migratable for IvshmemDevice {} diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 6ea4bc70bb..d7ac32d9d5 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -24,6 +24,8 @@ pub mod gic; pub mod interrupt_controller; #[cfg(target_arch = "x86_64")] pub mod ioapic; +#[cfg(feature = "ivshmem")] +pub mod ivshmem; pub mod legacy; #[cfg(feature = "pvmemcontrol")] pub mod pvmemcontrol; @@ -33,6 +35,8 @@ pub mod pvpanic; pub mod tpm; pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice}; +#[cfg(feature = "ivshmem")] +pub use self::ivshmem::IvshmemDevice; pub use self::pvpanic::{PvPanicDevice, PVPANIC_DEVICE_MMIO_SIZE}; bitflags! { From 2c282a5a540e7d694fa3b83f3abba85c101c481d Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Thu, 29 Aug 2024 15:58:50 +0800 Subject: [PATCH 0100/1893] vmm: ivshmem device support Signed-off-by: Yi Wang Signed-off-by: Songqian Li --- Cargo.toml | 1 + src/main.rs | 10 +++++ vmm/Cargo.toml | 1 + vmm/src/config.rs | 89 +++++++++++++++++++++++++++++++++++++++ vmm/src/device_manager.rs | 57 +++++++++++++++++++++++++ vmm/src/lib.rs | 4 ++ vmm/src/vm_config.rs | 22 ++++++++++ 7 files changed, 184 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 89f994aafe..c621c256d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,7 @@ fw_cfg = ["vmm/fw_cfg"] guest_debug = ["vmm/guest_debug"] igvm = ["mshv", "vmm/igvm"] io_uring = ["vmm/io_uring"] +ivshmem = ["vmm/ivshmem"] kvm = ["vmm/kvm"] mshv = ["vmm/mshv"] pvmemcontrol = ["vmm/pvmemcontrol"] diff --git a/src/main.rs b/src/main.rs index 4ba766e055..6daac338f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,8 @@ use vmm::landlock::{Landlock, LandlockError}; use vmm::vm_config; #[cfg(feature = "fw_cfg")] use vmm::vm_config::FwCfgConfig; +#[cfg(feature = "ivshmem")] +use vmm::vm_config::IvshmemConfig; #[cfg(target_arch = "x86_64")] use vmm::vm_config::SgxEpcConfig; use vmm::vm_config::{ @@ -300,6 +302,12 @@ fn get_cli_options_sorted( .help("Path to initramfs image") .num_args(1) .group("vm-config"), + #[cfg(feature = "ivshmem")] + Arg::new("ivshmem") + .long("ivshmem") + .help(IvshmemConfig::SYNTAX) + .num_args(1) + .group("vm-config"), Arg::new("kernel") .long("kernel") .help( @@ -1034,6 +1042,8 @@ mod unit_tests { preserved_fds: None, landlock_enable: false, landlock_rules: None, + #[cfg(feature = "ivshmem")] + ivshmem: None, }; assert_eq!(expected_vm_config, result_vm_config); diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 6b33e6344d..6ebabc01ae 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -12,6 +12,7 @@ fw_cfg = ["devices/fw_cfg"] guest_debug = ["gdbstub", "gdbstub_arch", "kvm"] igvm = ["dep:igvm", "hex", "igvm_defs", "mshv-bindings", "range_map_vec"] io_uring = ["block/io_uring"] +ivshmem = ["devices/ivshmem"] kvm = [ "arch/kvm", "hypervisor/kvm", diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 28d8cc6d8c..1977c8728c 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -4,6 +4,8 @@ // use std::collections::{BTreeSet, HashMap}; +#[cfg(feature = "ivshmem")] +use std::fs; use std::path::PathBuf; use std::result; use std::str::FromStr; @@ -154,9 +156,17 @@ pub enum Error { /// Failed parsing TPM device #[error("Error parsing --tpm")] ParseTpm(#[source] OptionParserError), + #[cfg(feature = "ivshmem")] + /// Failed parsing ivsmem device + #[error("Error parsing --ivshmem")] + ParseIvshmem(#[source] OptionParserError), /// Missing path for TPM device #[error("Error parsing --tpm: path missing")] ParseTpmPathMissing, + #[cfg(feature = "ivshmem")] + /// Missing path for ivsmem device + #[error("Error parsing --ivshmem: path missing")] + ParseIvshmemPathMissing, /// Error parsing Landlock rules #[error("Error parsing --landlock-rules")] ParseLandlockRules(#[source] OptionParserError), @@ -334,6 +344,18 @@ pub enum ValidationError { /// FwCfg missing initramfs #[error("Error --fw-cfg-config: missing --initramfs")] FwCfgMissingInitramfs, + #[cfg(feature = "ivshmem")] + /// Invalid Ivshmem input size + #[error("Invalid ivshmem input size")] + InvalidIvshmemInputSize(u64), + #[cfg(feature = "ivshmem")] + /// Invalid Ivshmem backend file size + #[error("Invalid ivshmem backend file size")] + InvalidIvshmemSize(u64), + #[cfg(feature = "ivshmem")] + /// Invalid Ivshmem backend file path + #[error("Invalid ivshmem backend file path")] + InvalidIvshmemPath, } type ValidationResult = std::result::Result; @@ -391,6 +413,8 @@ pub struct VmParams<'a> { pub landlock_rules: Option>, #[cfg(feature = "fw_cfg")] pub fw_cfg_config: Option<&'a str>, + #[cfg(feature = "ivshmem")] + pub ivshmem: Option<&'a str>, } impl<'a> VmParams<'a> { @@ -465,6 +489,8 @@ impl<'a> VmParams<'a> { #[cfg(feature = "fw_cfg")] let fw_cfg_config: Option<&str> = args.get_one::("fw-cfg-config").map(|x| x as &str); + #[cfg(feature = "ivshmem")] + let ivshmem: Option<&str> = args.get_one::("ivshmem").map(|x| x as &str); VmParams { cpus, memory, @@ -508,6 +534,8 @@ impl<'a> VmParams<'a> { landlock_rules, #[cfg(feature = "fw_cfg")] fw_cfg_config, + #[cfg(feature = "ivshmem")] + ivshmem, } } } @@ -2397,6 +2425,47 @@ impl LandlockConfig { } } +#[cfg(feature = "ivshmem")] +impl IvshmemConfig { + pub const SYNTAX: &'static str = "Ivshmem device. Specify the backend file path and size \ + for the shared memory: \"path=, size=\" \ + \nThe must be a power of 2 (e.g., 2M, 4M, etc.), as it represents the size \ + of the memory region mapped to the guest. Default size is 128M."; + pub fn parse(ivshmem: &str) -> Result { + let mut parser = OptionParser::new(); + parser.add("path").add("size"); + parser.parse(ivshmem).map_err(Error::ParseIvshmem)?; + let path = parser + .get("path") + .map(PathBuf::from) + .ok_or(Error::ParseIvshmemPathMissing)?; + let size = parser + .convert::("size") + .map_err(Error::ParseIvshmem)? + .unwrap_or(ByteSized((DEFAULT_IVSHMEM_SIZE << 20) as u64)) + .0; + Ok(IvshmemConfig { + path, + size: size as usize, + }) + } + + pub fn validate(&self) -> ValidationResult<()> { + let size = self.size as u64; + let path = &self.path; + // size must = 2^n + if !size.is_power_of_two() { + return Err(ValidationError::InvalidIvshmemInputSize(size)); + } + let metadata = fs::metadata(path.to_str().unwrap()) + .map_err(|_| ValidationError::InvalidIvshmemPath)?; + if metadata.len() < size { + return Err(ValidationError::InvalidIvshmemSize(metadata.len())); + } + Ok(()) + } +} + impl VmConfig { fn validate_identifier( id_list: &mut BTreeSet, @@ -2754,6 +2823,10 @@ impl VmConfig { landlock_rule.validate()?; } } + #[cfg(feature = "ivshmem")] + if let Some(ivshmem_config) = &self.ivshmem { + ivshmem_config.validate()?; + } Ok(id_list) } @@ -2951,6 +3024,14 @@ impl VmConfig { ); } + #[cfg(feature = "ivshmem")] + let mut ivshmem: Option = None; + #[cfg(feature = "ivshmem")] + if let Some(iv) = vm_params.ivshmem { + let ivshmem_conf = IvshmemConfig::parse(iv)?; + ivshmem = Some(ivshmem_conf); + } + let mut config = VmConfig { cpus: CpusConfig::parse(vm_params.cpus)?, memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?, @@ -2986,6 +3067,8 @@ impl VmConfig { preserved_fds: None, landlock_enable: vm_params.landlock_enable, landlock_rules, + #[cfg(feature = "ivshmem")] + ivshmem, }; config.validate().map_err(Error::Validation)?; Ok(config) @@ -3115,6 +3198,8 @@ impl Clone for VmConfig { // SAFETY: FFI call with valid FDs .map(|fds| fds.iter().map(|fd| unsafe { libc::dup(*fd) }).collect()), landlock_rules: self.landlock_rules.clone(), + #[cfg(feature = "ivshmem")] + ivshmem: self.ivshmem.clone(), ..*self } } @@ -3919,6 +4004,8 @@ mod tests { ]), landlock_enable: false, landlock_rules: None, + #[cfg(feature = "ivshmem")] + ivshmem: None, }; let valid_config = RestoreConfig { @@ -4114,6 +4201,8 @@ mod tests { preserved_fds: None, landlock_enable: false, landlock_rules: None, + #[cfg(feature = "ivshmem")] + ivshmem: None, }; valid_config.validate().unwrap(); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index ac1c0a406d..9f32da8ba4 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -115,6 +115,8 @@ use crate::interrupt::{LegacyUserspaceInterruptManager, MsiInterruptManager}; use crate::memory_manager::{Error as MemoryManagerError, MemoryManager, MEMORY_MANAGER_ACPI_SIZE}; use crate::pci_segment::PciSegment; use crate::serial_manager::{Error as SerialManagerError, SerialManager}; +#[cfg(feature = "ivshmem")] +use crate::vm_config::IvshmemConfig; use crate::vm_config::{ ConsoleOutputMode, DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, @@ -140,6 +142,8 @@ const PVMEMCONTROL_DEVICE_NAME: &str = "__pvmemcontrol"; const BALLOON_DEVICE_NAME: &str = "__balloon"; const CONSOLE_DEVICE_NAME: &str = "__console"; const PVPANIC_DEVICE_NAME: &str = "__pvpanic"; +#[cfg(feature = "ivshmem")] +const IVSHMEM_DEVICE_NAME: &str = "__ivshmem"; // Devices that the user may name and for which we generate // identifiers if the user doesn't give one @@ -632,6 +636,11 @@ pub enum DeviceManagerError { #[error("Cannot create a PvPanic device")] PvPanicCreate(#[source] devices::pvpanic::PvPanicError), + #[cfg(feature = "ivshmem")] + /// Cannot create a ivshmem device + #[error("Cannot create a ivshmem device: {0}")] + IvshmemCreate(devices::ivshmem::IvshmemError), + /// Cannot create a RateLimiterGroup #[error("Cannot create a RateLimiterGroup")] RateLimiterGroupCreate(#[source] rate_limiter::group::Error), @@ -1085,6 +1094,10 @@ pub struct DeviceManager { #[cfg(feature = "fw_cfg")] fw_cfg: Option>>, + + #[cfg(feature = "ivshmem")] + // ivshmem device + ivshmem_device: Option>>, } fn create_mmio_allocators( @@ -1351,6 +1364,8 @@ impl DeviceManager { mmio_regions: Arc::new(Mutex::new(Vec::new())), #[cfg(feature = "fw_cfg")] fw_cfg: None, + #[cfg(feature = "ivshmem")] + ivshmem_device: None, }; let device_manager = Arc::new(Mutex::new(device_manager)); @@ -1474,6 +1489,11 @@ impl DeviceManager { self.pvpanic_device = self.add_pvpanic_device()?; } + #[cfg(feature = "ivshmem")] + if let Some(ivshmem) = self.config.clone().lock().unwrap().ivshmem.as_ref() { + self.ivshmem_device = self.add_ivshmem_device(ivshmem)?; + } + Ok(()) } @@ -4199,6 +4219,43 @@ impl DeviceManager { Ok(Some(pvpanic_device)) } + #[cfg(feature = "ivshmem")] + fn add_ivshmem_device( + &mut self, + ivshmem_cfg: &IvshmemConfig, + ) -> DeviceManagerResult>>> { + let id = String::from(IVSHMEM_DEVICE_NAME); + let pci_segment_id = 0x0_u16; + info!("Creating ivshmem device {}", id); + + let (pci_segment_id, pci_device_bdf, resources) = + self.pci_resources(&id, pci_segment_id)?; + let snapshot = snapshot_from_id(self.snapshot.as_ref(), id.as_str()); + + let ivshmem_device = Arc::new(Mutex::new( + devices::IvshmemDevice::new( + id.clone(), + ivshmem_cfg.size as u64, + snapshot, + ) + .map_err(DeviceManagerError::IvshmemCreate)?, + )); + let new_resources = self.add_pci_device( + ivshmem_device.clone(), + ivshmem_device.clone(), + pci_segment_id, + pci_device_bdf, + resources, + )?; + let mut node = device_node!(id, ivshmem_device); + node.resources = new_resources; + node.pci_bdf = Some(pci_device_bdf); + node.pci_device_handle = None; + self.device_tree.lock().unwrap().insert(id, node); + + Ok(Some(ivshmem_device)) + } + fn pci_resources( &self, id: &str, diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index f17c4b79d0..dddfe9bd33 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -423,6 +423,8 @@ pub fn feature_list() -> Vec { "tdx".to_string(), #[cfg(feature = "tracing")] "tracing".to_string(), + #[cfg(feature = "ivshmem")] + "ivshmem".to_string(), ] } @@ -2438,6 +2440,8 @@ mod unit_tests { preserved_fds: None, landlock_enable: false, landlock_rules: None, + #[cfg(feature = "ivshmem")] + ivshmem: None, }) } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 2d829a678a..50841eeed8 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -650,6 +650,26 @@ impl ApplyLandlock for VsockConfig { } } +#[cfg(feature = "ivshmem")] +pub const DEFAULT_IVSHMEM_SIZE: usize = 128; + +#[cfg(feature = "ivshmem")] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct IvshmemConfig { + pub path: PathBuf, + pub size: usize, +} + +#[cfg(feature = "ivshmem")] +impl Default for IvshmemConfig { + fn default() -> Self { + Self { + path: PathBuf::new(), + size: DEFAULT_IVSHMEM_SIZE << 20, + } + } +} + #[cfg(target_arch = "x86_64")] #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct SgxEpcConfig { @@ -896,6 +916,8 @@ pub struct VmConfig { #[serde(default)] pub landlock_enable: bool, pub landlock_rules: Option>, + #[cfg(feature = "ivshmem")] + pub ivshmem: Option, } impl VmConfig { From f0febcae3da54a281cd287b2630a681ae433dddf Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Thu, 29 Aug 2024 15:59:19 +0800 Subject: [PATCH 0101/1893] docs: add ivshmem device introduction Signed-off-by: Songqian Li --- docs/ivshmem.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 docs/ivshmem.md diff --git a/docs/ivshmem.md b/docs/ivshmem.md new file mode 100644 index 0000000000..3a7913c3f6 --- /dev/null +++ b/docs/ivshmem.md @@ -0,0 +1,51 @@ +# Inter-VM shared memory device + +The Inter-VM shared memory device (ivshmem) is designed to share a memory +region between a guest and the host. In order for all guests to be able to +pick up the shared memory area, it is modeled as a PCI device exposing said +memory to the guest as a PCI BAR. + +Device Specification is +at https://www.qemu.org/docs/master/specs/ivshmem-spec.html. + +Now we support setting a backend file to share data between host and guest. +In other words, we only support ivshmem-plain and ivshmem-doorbell is not +supported yet. + +## Usage + +`--ivshmem`, an optional argument, can be passed to enable ivshmem device. +This argument takes a file as a `path` value and a file size as a `size` value. + +``` +--ivshmem device backend file "path=,size="; +``` + +## Example + +Create a file with a size bigger than passed to `cloud-hypervisor`: + +``` +truncate -s 1M /tmp/ivshmem.data +``` + +Start application to mmap the file data to a memory region: + +``` +./cloud-hypervisor \ + --api-socket /tmp/cloud-hypervisor.sock \ + --kernel vmlinux \ + --disk path=focal-server-cloudimg-amd64.raw \ + --cpus boot=4 \ + --memory size=1024M \ + --ivshmem path=/tmp/ivshmem.data,size=1M +``` + +Insmod a ivshmem device driver to enable the device. The file data will be +mmapped to the PCI `bar2` of ivshmem device, +guest can r/w data by accessing this memory. + +A simple example of ivshmem driver can get from: +https://github.com/lisongqian/clh-linux/commits/ch-6.12.8-ivshmem + +The host process can r/w this data by remmaping the `/tmp/ivshmem.data`. From a09c8329fb18d0088da855391d3b67dde26c7d4f Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Thu, 29 Aug 2024 16:26:48 +0800 Subject: [PATCH 0102/1893] fuzz: add ivshmem device config Signed-off-by: Songqian Li --- fuzz/Cargo.toml | 1 + fuzz/fuzz_targets/http_api.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 8a7e1d4849..f8fa6fa4b7 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -11,6 +11,7 @@ cargo-fuzz = true [features] default = ["mshv_emulator"] igvm = [] +ivshmem = [] mshv_emulator = ["hypervisor/mshv_emulator"] pvmemcontrol = [] diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index 5c146ad9d8..ee8fa52376 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -197,6 +197,8 @@ impl RequestHandler for StubApiRequestHandler { preserved_fds: None, landlock_enable: false, landlock_rules: None, + #[cfg(feature = "ivshmem")] + ivshmem: None, }), state: VmState::Running, memory_actual_size: 0, From 4c1ee0329e9e2b6fb632a08b31514bd2cdd9e68b Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Tue, 17 Jun 2025 15:26:28 +0800 Subject: [PATCH 0103/1893] tests: add ivshmem integration test case Signed-off-by: Songqian Li --- scripts/run_integration_tests_aarch64.sh | 7 + scripts/run_integration_tests_x86_64.sh | 7 + test_infra/src/lib.rs | 24 ++ tests/integration.rs | 373 ++++++++++++++++++++++- 4 files changed, 408 insertions(+), 3 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 262faff9a4..758c69c6b2 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -258,4 +258,11 @@ if [ $RES -eq 0 ]; then RES=$? fi +if [ $RES -eq 0 ]; then + cargo build --features ivshmem --all --release --target "$BUILD_TARGET" + export RUST_BACKTRACE=1 + time cargo test "ivshmem::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + RES=$? +fi + exit $RES diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 4f4491aa7f..3f28e23cdf 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -206,4 +206,11 @@ if [ $RES -eq 0 ]; then RES=$? fi +if [ $RES -eq 0 ]; then + cargo build --features ivshmem --all --release --target "$BUILD_TARGET" + export RUST_BACKTRACE=1 + time cargo test $test_features "ivshmem::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + RES=$? +fi + exit $RES diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index df47de835c..6875aa5b22 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1776,3 +1776,27 @@ pub fn measure_virtio_net_latency(guest: &Guest, test_timeout: u32) -> Result Option { + let devices: Vec<&str> = output.split("\n\n").collect(); + + for device in devices { + if device.contains(device_desc) { + for line in device.lines() { + let line = line.trim(); + let line_start_str = format!("Region {bar_index}: Memory at"); + // for example: Region 2: Memory at 200000000 (64-bit, non-prefetchable) [size=1M] + if line.starts_with(line_start_str.as_str()) { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 4 { + let addr_str = parts[4]; + return Some(String::from(addr_str)); + } + } + } + } + } + None +} diff --git a/tests/integration.rs b/tests/integration.rs index dc19b7aee2..d604a5f7c9 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -11,7 +11,9 @@ extern crate test_infra; use std::collections::HashMap; -use std::io::{BufRead, Read, Seek, Write}; +use std::ffi::CStr; +use std::fs::OpenOptions; +use std::io::{BufRead, Read, Seek, SeekFrom, Write}; use std::net::TcpListener; use std::os::unix::io::AsRawFd; use std::path::PathBuf; @@ -2341,6 +2343,147 @@ fn make_guest_panic(guest: &Guest) { guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); } +// ivshmem test +// This case validates that read data from host(host write data to ivshmem backend file, +// guest read data from ivshmem pci bar2 memory) +// and write data to host(guest write data to ivshmem pci bar2 memory, host read it from +// ivshmem backend file). +// It also checks the size of the shared memory region. +fn _test_ivshmem(guest: &Guest, ivshmem_file_path: String, file_size: &str) { + let test_message_read = String::from("ivshmem device test data read"); + // Modify backend file data before function test + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open(ivshmem_file_path.as_str()) + .unwrap(); + file.seek(SeekFrom::Start(0)).unwrap(); + file.write_all(test_message_read.as_bytes()).unwrap(); + file.write_all(b"\0").unwrap(); + file.flush().unwrap(); + + let output = fs::read_to_string(ivshmem_file_path.as_str()).unwrap(); + let nul_pos = output.as_bytes().iter().position(|&b| b == 0).unwrap(); + let c_str = CStr::from_bytes_until_nul(&output.as_bytes()[..=nul_pos]).unwrap(); + let file_message = c_str.to_string_lossy().to_string(); + // Check if the backend file data is correct + assert_eq!(test_message_read, file_message); + + let device_id_line = String::from( + guest + .ssh_command("lspci -D | grep \"Inter-VM shared memory\"") + .unwrap() + .trim(), + ); + // Check if ivshmem exists + assert!(!device_id_line.is_empty()); + let device_id = device_id_line.split(" ").next().unwrap(); + // Check shard memory size + assert_eq!( + guest + .ssh_command( + format!("lspci -vv -s {device_id} | grep -c \"Region 2.*size={file_size}\"") + .as_str(), + ) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // guest don't have gcc or g++, try to use python to test :( + // This python program try to mmap the ivshmem pci bar2 memory and read the data from it. + let ivshmem_test_read = format!( + r#" +import os +import mmap +from ctypes import create_string_buffer, c_char, memmove + +if __name__ == "__main__": + device_path = f"/sys/bus/pci/devices/{device_id}/resource2" + fd = os.open(device_path, os.O_RDWR | os.O_SYNC) + + PAGE_SIZE = os.sysconf('SC_PAGESIZE') + + with mmap.mmap(fd, PAGE_SIZE, flags=mmap.MAP_SHARED, + prot=mmap.PROT_READ | mmap.PROT_WRITE, offset=0) as shmem: + c_buf = (c_char * PAGE_SIZE).from_buffer(shmem) + null_pos = c_buf.raw.find(b'\x00') + valid_data = c_buf.raw[:null_pos] if null_pos != -1 else c_buf.raw + print(valid_data.decode('utf-8', errors='replace'), end="") + shmem.flush() + del c_buf + + os.close(fd) + "# + ); + guest + .ssh_command( + format!( + r#"cat << EOF > test_read.py +{ivshmem_test_read} +EOF +"# + ) + .as_str(), + ) + .unwrap(); + let guest_message = guest.ssh_command("sudo python3 test_read.py").unwrap(); + + // Check the probe message in host and guest + assert_eq!(test_message_read, guest_message); + + let test_message_write = "ivshmem device test data write"; + // Then the program writes a test message to the memory and flush it. + let ivshmem_test_write = format!( + r#" +import os +import mmap +from ctypes import create_string_buffer, c_char, memmove + +if __name__ == "__main__": + device_path = f"/sys/bus/pci/devices/{device_id}/resource2" + test_message = "{test_message_write}" + fd = os.open(device_path, os.O_RDWR | os.O_SYNC) + + PAGE_SIZE = os.sysconf('SC_PAGESIZE') + + with mmap.mmap(fd, PAGE_SIZE, flags=mmap.MAP_SHARED, + prot=mmap.PROT_READ | mmap.PROT_WRITE, offset=0) as shmem: + shmem.flush() + c_buf = (c_char * PAGE_SIZE).from_buffer(shmem) + encoded_msg = test_message.encode('utf-8').ljust(1000, b'\x00') + memmove(c_buf, encoded_msg, len(encoded_msg)) + shmem.flush() + del c_buf + + os.close(fd) + "# + ); + + guest + .ssh_command( + format!( + r#"cat << EOF > test_write.py +{ivshmem_test_write} +EOF +"# + ) + .as_str(), + ) + .unwrap(); + + let _ = guest.ssh_command("sudo python3 test_write.py").unwrap(); + + let output = fs::read_to_string(ivshmem_file_path.as_str()).unwrap(); + let nul_pos = output.as_bytes().iter().position(|&b| b == 0).unwrap(); + let c_str = CStr::from_bytes_until_nul(&output.as_bytes()[..=nul_pos]).unwrap(); + let file_message = c_str.to_string_lossy().to_string(); + // Check to send data from guest to host + assert_eq!(test_message_write, file_message); +} + mod common_parallel { use std::fs::OpenOptions; use std::io::SeekFrom; @@ -7275,6 +7418,226 @@ mod dbus_api { } } +mod ivshmem { + use std::fs::remove_dir_all; + use std::process::Command; + + use test_infra::{handle_child_output, kill_child, Guest, GuestCommand, UbuntuDiskConfig}; + + use crate::*; + + #[test] + fn test_ivshmem() { + let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(focal)); + let api_socket = temp_api_path(&guest.tmp_dir); + + let kernel_path = direct_kernel_boot_path(); + + let ivshmem_file_path = String::from( + guest + .tmp_dir + .as_path() + .join("ivshmem.data") + .to_str() + .unwrap(), + ); + let file_size = "1M"; + + // Create a file to be used as the shared memory + Command::new("dd") + .args([ + "if=/dev/zero", + format!("of={ivshmem_file_path}").as_str(), + format!("bs={file_size}").as_str(), + "count=1", + ]) + .status() + .unwrap(); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .args([ + "--ivshmem", + format!("path={ivshmem_file_path},size={file_size}").as_str(), + ]) + .args(["--api-socket", &api_socket]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot(None).unwrap(); + _test_ivshmem(&guest, ivshmem_file_path, file_size); + }); + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } + + #[test] + fn test_snapshot_restore_ivshmem() { + let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(focal)); + let kernel_path = direct_kernel_boot_path(); + + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); + + let ivshmem_file_path = String::from( + guest + .tmp_dir + .as_path() + .join("ivshmem.data") + .to_str() + .unwrap(), + ); + let file_size = "1M"; + + let device_params = { + let mut data = vec![]; + // Create a file to be used as the shared memory + Command::new("dd") + .args([ + "if=/dev/zero", + format!("of={ivshmem_file_path}").as_str(), + format!("bs={file_size}").as_str(), + "count=1", + ]) + .status() + .unwrap(); + data.push(String::from("--ivshmem")); + data.push(format!("path={ivshmem_file_path},size={file_size}")); + data + }; + + let socket = temp_vsock_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_source]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=1G"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .default_disks() + .default_net() + .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args(device_params) + .capture_output() + .spawn() + .unwrap(); + + let console_text = String::from("On a branch floating down river a cricket, singing."); + // Create the snapshot directory + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot(None).unwrap(); + + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + + common_sequential::snapshot_and_check_events( + &api_socket_source, + &snapshot_dir, + &event_path, + ); + }); + + // Shutdown the source VM and check console output + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + // Remove the vsock socket file. + Command::new("rm") + .arg("-f") + .arg(socket.as_str()) + .output() + .unwrap(); + + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + + // Restore the VM from the snapshot + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_restored]) + .args([ + "--event-monitor", + format!("path={event_path_restored}").as_str(), + ]) + .args([ + "--restore", + format!("source_url=file://{snapshot_dir}").as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + // Wait for the VM to be restored + thread::sleep(std::time::Duration::new(20, 0)); + + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(check_latest_events_exact( + &latest_events, + &event_path_restored + )); + + // Remove the snapshot dir + let _ = remove_dir_all(snapshot_dir.as_str()); + + let r = std::panic::catch_unwind(|| { + // Resume the VM + assert!(remote_command(&api_socket_restored, "resume", None)); + // There is no way that we can ensure the 'write()' to the + // event file is completed when the 'resume' request is + // returned successfully, because the 'write()' was done + // asynchronously from a different thread of Cloud + // Hypervisor (e.g. the event-monitor thread). + thread::sleep(std::time::Duration::new(1, 0)); + let latest_events = [ + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(check_latest_events_exact( + &latest_events, + &event_path_restored + )); + + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + guest.check_devices_common(Some(&socket), Some(&console_text), None); + _test_ivshmem(&guest, ivshmem_file_path, file_size); + }); + // Shutdown the target VM and check console output + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); + + handle_child_output(r, &output); + } +} + mod common_sequential { use std::fs::remove_dir_all; @@ -7286,7 +7649,11 @@ mod common_sequential { test_memory_mergeable(true) } - fn snapshot_and_check_events(api_socket: &str, snapshot_dir: &str, event_path: &str) { + pub(crate) fn snapshot_and_check_events( + api_socket: &str, + snapshot_dir: &str, + event_path: &str, + ) { // Pause the VM assert!(remote_command(api_socket, "pause", None)); let latest_events: [&MetaEvent; 2] = [ @@ -7833,7 +8200,7 @@ mod common_sequential { let device_params = { let mut data = vec![]; if pvpanic { - data.push("--pvpanic"); + data.push(String::from("--pvpanic")); } data }; From 51e095c54d056e1b3f9ece325b2edbd8066a3baf Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Tue, 17 Jun 2025 15:26:45 +0800 Subject: [PATCH 0104/1893] ci: add build and quality check for ivshmem feature Signed-off-by: Songqian Li --- .github/workflows/build.yaml | 3 +++ .github/workflows/quality.yaml | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c78750f78d..428740dcd0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -55,6 +55,9 @@ jobs: - name: Build (default features + fw_cfg) run: cargo rustc --locked --bin cloud-hypervisor --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + - name: Build (default features + ivshmem) + run: cargo rustc --locked --bin cloud-hypervisor --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + - name: Build (mshv) run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index fb733b1a09..bdd407b765 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -117,6 +117,16 @@ jobs: 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + + - 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + - name: Clippy (sev_snp) if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} uses: houseabsolute/actions-rust-cross@v1 From 5128ee9ba6443f985a596624bcf6d15c24973852 Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Tue, 8 Jul 2025 11:53:16 +0800 Subject: [PATCH 0105/1893] devices, vmm: Handle ivshmem bar reprogramming properly Signed-off-by: Bo Chen Signed-off-by: Songqian Li --- devices/src/ivshmem.rs | 99 ++++++++++++++++++++++++++++++-------- vmm/src/device_manager.rs | 88 ++++++++++++++++++++++++++++++++- vmm/src/seccomp_filters.rs | 1 - 3 files changed, 165 insertions(+), 23 deletions(-) diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs index 6e9ef4293e..fc7c88de31 100644 --- a/devices/src/ivshmem.rs +++ b/devices/src/ivshmem.rs @@ -23,7 +23,6 @@ use vm_device::{BusDevice, Resource, UserspaceMapping}; use vm_memory::bitmap::AtomicBitmap; use vm_memory::{Address, GuestAddress}; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; -use vmm_sys_util::eventfd::EventFd; const IVSHMEM_BAR0_IDX: usize = 0; const IVSHMEM_BAR1_IDX: usize = 1; @@ -42,6 +41,14 @@ pub enum IvshmemError { RetrievePciConfigurationState(#[source] anyhow::Error), #[error("Failed to retrieve IvshmemDeviceState: {0}")] RetrieveIvshmemDeviceStateState(#[source] anyhow::Error), + #[error("Failed to remove user memory region")] + RemoveUserMemoryRegion, + #[error("Failed to create user memory region.")] + CreateUserMemoryRegion, + #[error("Failed to create userspace mapping.")] + CreateUserspaceMapping, + #[error("Failed to remove old userspace mapping.")] + RemoveUserspaceMapping, } #[derive(Copy, Clone)] @@ -55,23 +62,41 @@ impl PciSubclass for IvshmemSubclass { } } +pub trait IvshmemOps: Send + Sync { + fn map_ram_region( + &mut self, + start_addr: u64, + size: usize, + backing_file: Option, + ) -> Result<(Arc, UserspaceMapping), IvshmemError>; + + fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError>; +} + +/// Inner-Vm Shared Memory Device (Ivshmem device) +/// +/// This device can share memory between host and guest(ivshmem-plain) +/// and share memory between guests(ivshmem-doorbell). +/// But only ivshmem-plain support now, ivshmem-doorbell doesn't support yet. pub struct IvshmemDevice { id: String, // ivshmem device registers - interrupt_mask: u32, - interrupt_status: Arc, - iv_position: u32, - doorbell: u32, + // (only used for ivshmem-doorbell, ivshmem-doorbell don't support yet) + _interrupt_mask: u32, + _interrupt_status: Arc, + _iv_position: u32, + _doorbell: u32, // PCI configuration registers. configuration: PciConfiguration, bar_regions: Vec, - region: Option>, region_size: u64, + ivshmem_ops: Arc>, + backend_file: Option, + region: Option>, userspace_mapping: Option, - reprogram_evt: EventFd, } #[derive(Serialize, Deserialize, Default, Clone)] @@ -86,13 +111,14 @@ impl IvshmemDevice { pub fn new( id: String, region_size: u64, + backend_file: Option, + ivshmem_ops: Arc>, snapshot: Option, ) -> Result { let pci_configuration_state = vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| { IvshmemError::RetrievePciConfigurationState(anyhow!( "Failed to get PciConfigurationState from Snapshot: {e}", - e )) })?; @@ -125,31 +151,44 @@ impl IvshmemDevice { id, configuration, bar_regions: vec![], - interrupt_mask: s.interrupt_mask, - interrupt_status: Arc::new(AtomicU32::new(s.interrupt_status)), - iv_position: s.iv_position, - doorbell: s.doorbell, + _interrupt_mask: s.interrupt_mask, + _interrupt_status: Arc::new(AtomicU32::new(s.interrupt_status)), + _iv_position: s.iv_position, + _doorbell: s.doorbell, region_size, + ivshmem_ops, region: None, userspace_mapping: None, + backend_file, } } else { IvshmemDevice { id, configuration, bar_regions: vec![], - interrupt_mask: 0, - interrupt_status: Arc::new(AtomicU32::new(0)), - iv_position: 0, - doorbell: 0, + _interrupt_mask: 0, + _interrupt_status: Arc::new(AtomicU32::new(0)), + _iv_position: 0, + _doorbell: 0, region_size, + ivshmem_ops, region: None, userspace_mapping: None, + backend_file, } }; Ok(device) } + pub fn set_region( + &mut self, + region: Arc, + userspace_mapping: UserspaceMapping, + ) { + self.region = Some(region); + self.userspace_mapping = Some(userspace_mapping); + } + pub fn config_bar_addr(&self) -> u64 { self.configuration.get_bar_addr(IVSHMEM_BAR0_IDX) } @@ -160,10 +199,10 @@ impl IvshmemDevice { fn state(&self) -> IvshmemDeviceState { IvshmemDeviceState { - interrupt_mask: self.interrupt_mask, - interrupt_status: self.interrupt_status.load(Ordering::SeqCst), - iv_position: self.iv_position, - doorbell: self.doorbell, + interrupt_mask: self._interrupt_mask, + interrupt_status: self._interrupt_status.load(Ordering::SeqCst), + iv_position: self._iv_position, + doorbell: self._doorbell, } } } @@ -319,6 +358,26 @@ impl PciDevice for IvshmemDevice { } fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> { + if new_base == self.data_bar_addr() { + if let Some(old_mapping) = self.userspace_mapping.take() { + self.ivshmem_ops + .lock() + .unwrap() + .unmap_ram_region(old_mapping) + .map_err(std::io::Error::other)?; + } + let (region, new_mapping) = self + .ivshmem_ops + .lock() + .unwrap() + .map_ram_region( + new_base, + self.region_size as usize, + self.backend_file.clone(), + ) + .map_err(std::io::Error::other)?; + self.set_region(region, new_mapping); + } for bar in self.bar_regions.iter_mut() { if bar.addr() == old_base { *bar = bar.set_address(new_base); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 9f32da8ba4..c097a25974 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -53,6 +53,8 @@ use devices::gic; use devices::interrupt_controller::InterruptController; #[cfg(target_arch = "x86_64")] use devices::ioapic; +#[cfg(feature = "ivshmem")] +use devices::ivshmem::{IvshmemError, IvshmemOps}; #[cfg(all(feature = "fw_cfg", target_arch = "x86_64"))] use devices::legacy::fw_cfg::FW_CFG_ACPI_ID; #[cfg(target_arch = "aarch64")] @@ -639,7 +641,7 @@ pub enum DeviceManagerError { #[cfg(feature = "ivshmem")] /// Cannot create a ivshmem device #[error("Cannot create a ivshmem device: {0}")] - IvshmemCreate(devices::ivshmem::IvshmemError), + IvshmemCreate(IvshmemError), /// Cannot create a RateLimiterGroup #[error("Cannot create a RateLimiterGroup")] @@ -4232,11 +4234,16 @@ impl DeviceManager { self.pci_resources(&id, pci_segment_id)?; let snapshot = snapshot_from_id(self.snapshot.as_ref(), id.as_str()); + let ivshmem_ops = Arc::new(Mutex::new(IvshmemHandler { + memory_manager: self.memory_manager.clone(), + })); let ivshmem_device = Arc::new(Mutex::new( devices::IvshmemDevice::new( id.clone(), ivshmem_cfg.size as u64, - snapshot, + Some(ivshmem_cfg.path.clone()), + ivshmem_ops.clone(), + snapshot, ) .map_err(DeviceManagerError::IvshmemCreate)?, )); @@ -4247,6 +4254,15 @@ impl DeviceManager { pci_device_bdf, resources, )?; + + let start_addr = ivshmem_device.lock().unwrap().data_bar_addr(); + let (region, mapping) = ivshmem_ops + .lock() + .unwrap() + .map_ram_region(start_addr, ivshmem_cfg.size, Some(ivshmem_cfg.path.clone())) + .map_err(DeviceManagerError::IvshmemCreate)?; + ivshmem_device.lock().unwrap().set_region(region, mapping); + let mut node = device_node!(id, ivshmem_device); node.resources = new_resources; node.pci_bdf = Some(pci_device_bdf); @@ -4929,6 +4945,74 @@ impl DeviceManager { } } +#[cfg(feature = "ivshmem")] +struct IvshmemHandler { + memory_manager: Arc>, +} + +#[cfg(feature = "ivshmem")] +impl IvshmemOps for IvshmemHandler { + fn map_ram_region( + &mut self, + start_addr: u64, + size: usize, + backing_file: Option, + ) -> Result<(Arc, UserspaceMapping), IvshmemError> { + info!("Creating ivshmem mem region at 0x{:x}", start_addr); + + let region = MemoryManager::create_ram_region( + &backing_file, + 0, + GuestAddress(start_addr), + size, + false, + true, + false, + None, + None, + None, + false, + ) + .map_err(|_| IvshmemError::CreateUserMemoryRegion)?; + let mem_slot = self + .memory_manager + .lock() + .unwrap() + .create_userspace_mapping( + region.start_addr().0, + region.len(), + region.as_ptr() as u64, + false, + false, + false, + ) + .map_err(|_| IvshmemError::CreateUserspaceMapping)?; + let mapping = UserspaceMapping { + host_addr: region.as_ptr() as u64, + mem_slot, + addr: GuestAddress(region.start_addr().0), + len: region.len(), + mergeable: false, + }; + Ok((region, mapping)) + } + + fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError> { + self.memory_manager + .lock() + .unwrap() + .remove_userspace_mapping( + mapping.addr.raw_value(), + mapping.len, + mapping.host_addr, + mapping.mergeable, + mapping.mem_slot, + ) + .map_err(|_| IvshmemError::RemoveUserspaceMapping)?; + Ok(()) + } +} + fn numa_node_id_from_memory_zone_id(numa_nodes: &NumaNodes, memory_zone_id: &str) -> Option { for (numa_node_id, numa_node) in numa_nodes.iter() { if numa_node.memory_zones.contains(&memory_zone_id.to_owned()) { diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 40748f0d0b..1f0a6a47e4 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -835,7 +835,6 @@ fn vcpu_thread_rules( (libc::SYS_unlinkat, vec![]), (libc::SYS_write, vec![]), (libc::SYS_writev, vec![]), - #[cfg(debug_assertions)] (libc::SYS_fcntl, vec![]), (libc::SYS_getcwd, vec![]), ]) From 9011ff21617d797de0d832078fd00f54558034ed Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Mon, 7 Jul 2025 23:18:37 +0800 Subject: [PATCH 0106/1893] tests: add ivshmem live migration test case Signed-off-by: Songqian Li --- tests/integration.rs | 235 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 216 insertions(+), 19 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index d604a5f7c9..13e612485b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -7426,6 +7426,191 @@ mod ivshmem { use crate::*; + fn _test_live_migration_ivshmem(local: bool) { + let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(focal)); + let kernel_path = direct_kernel_boot_path(); + let console_text = String::from("On a branch floating down river a cricket, singing."); + let net_id = "net123"; + let net_params = format!( + "id={},tap=,mac={},ip={},mask=255.255.255.0", + net_id, guest.network.guest_mac, guest.network.host_ip + ); + + let memory_param: &[&str] = if local { + &["--memory", "size=4G,shared=on"] + } else { + &["--memory", "size=4G"] + }; + + let boot_vcpus = 2; + let max_vcpus = 4; + + let pmem_temp_file = TempFile::new().unwrap(); + pmem_temp_file.as_file().set_len(128 << 20).unwrap(); + std::process::Command::new("mkfs.ext4") + .arg(pmem_temp_file.as_path()) + .output() + .expect("Expect creating disk image to succeed"); + let pmem_path = String::from("/dev/pmem0"); + + let ivshmem_file_path = String::from( + guest + .tmp_dir + .as_path() + .join("ivshmem.data") + .to_str() + .unwrap(), + ); + let file_size = "1M"; + + // Create a file to be used as the shared memory + Command::new("dd") + .args([ + "if=/dev/zero", + format!("of={ivshmem_file_path}").as_str(), + format!("bs={file_size}").as_str(), + "count=1", + ]) + .status() + .unwrap(); + + // Start the source VM + let src_vm_path = clh_command("cloud-hypervisor"); + let src_api_socket = temp_api_path(&guest.tmp_dir); + let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); + src_vm_cmd + .args([ + "--cpus", + format!("boot={boot_vcpus},max={max_vcpus}").as_str(), + ]) + .args(memory_param) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--net", net_params.as_str()]) + .args(["--api-socket", &src_api_socket]) + .args([ + "--pmem", + format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), + ]) + .args([ + "--ivshmem", + format!("path={ivshmem_file_path},size={file_size}").as_str(), + ]); + let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); + + // Start the destination VM + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot(None).unwrap(); + + // Make sure the source VM is functional + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + // Check the guest RAM + assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + // Check the guest virtio-devices, e.g. block, rng, console, and net + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + // x86_64: Following what's done in the `test_snapshot_restore`, we need + // to make sure that removing and adding back the virtio-net device does + // not break the live-migration support for virtio-pci. + #[cfg(target_arch = "x86_64")] + { + assert!(remote_command( + &src_api_socket, + "remove-device", + Some(net_id), + )); + thread::sleep(Duration::new(10, 0)); + + // Plug the virtio-net device again + assert!(remote_command( + &src_api_socket, + "add-net", + Some(net_params.as_str()), + )); + thread::sleep(Duration::new(10, 0)); + } + + // Check ivshmem device in src guest. + _test_ivshmem(&guest, ivshmem_file_path.clone(), file_size); + // Allow some normal time to elapse to check we don't get spurious reboots + thread::sleep(std::time::Duration::new(40, 0)); + + // Start the live-migration + let migration_socket = String::from( + guest + .tmp_dir + .as_path() + .join("live-migration.sock") + .to_str() + .unwrap(), + ); + + assert!( + live_migration::start_live_migration( + &migration_socket, + &src_api_socket, + &dest_api_socket, + local + ), + "Unsuccessful command: 'send-migration' or 'receive-migration'." + ); + }); + + // Check and report any errors occurred during the live-migration + if r.is_err() { + live_migration::print_and_panic( + src_child, + dest_child, + None, + "Error occurred during live-migration", + ); + } + + // Check the source vm has been terminated successful (give it '3s' to settle) + thread::sleep(std::time::Duration::new(3, 0)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + live_migration::print_and_panic( + src_child, + dest_child, + None, + "source VM was not terminated successfully.", + ); + }; + + // Post live-migration check to make sure the destination VM is functional + let r = std::panic::catch_unwind(|| { + // Perform same checks to validate VM has been properly migrated + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + + // Check ivshmem device + _test_ivshmem(&guest, ivshmem_file_path, file_size); + }); + + // Clean-up the destination VM and make sure it terminated correctly + let _ = dest_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + handle_child_output(r, &dest_output); + + // Check the destination VM has the expected 'console_text' from its output + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); + }); + handle_child_output(r, &dest_output); + } + #[test] fn test_ivshmem() { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); @@ -7499,22 +7684,16 @@ mod ivshmem { ); let file_size = "1M"; - let device_params = { - let mut data = vec![]; - // Create a file to be used as the shared memory - Command::new("dd") - .args([ - "if=/dev/zero", - format!("of={ivshmem_file_path}").as_str(), - format!("bs={file_size}").as_str(), - "count=1", - ]) - .status() - .unwrap(); - data.push(String::from("--ivshmem")); - data.push(format!("path={ivshmem_file_path},size={file_size}")); - data - }; + // Create a file to be used as the shared memory + Command::new("dd") + .args([ + "if=/dev/zero", + format!("of={ivshmem_file_path}").as_str(), + format!("bs={file_size}").as_str(), + "count=1", + ]) + .status() + .unwrap(); let socket = temp_vsock_path(&guest.tmp_dir); let event_path = temp_event_monitor_path(&guest.tmp_dir); @@ -7529,7 +7708,10 @@ mod ivshmem { .default_net() .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(device_params) + .args([ + "--ivshmem", + format!("path={ivshmem_file_path},size={file_size}").as_str(), + ]) .capture_output() .spawn() .unwrap(); @@ -7636,6 +7818,16 @@ mod ivshmem { handle_child_output(r, &output); } + + #[test] + fn test_live_migration_ivshmem() { + _test_live_migration_ivshmem(false) + } + + #[test] + fn test_live_migration_ivshmem_local() { + _test_live_migration_ivshmem(true) + } } mod common_sequential { @@ -9566,7 +9758,7 @@ mod vfio { mod live_migration { use crate::*; - fn start_live_migration( + pub fn start_live_migration( migration_socket: &str, src_api_socket: &str, dest_api_socket: &str, @@ -9652,7 +9844,12 @@ mod live_migration { send_success && receive_success } - fn print_and_panic(src_vm: Child, dest_vm: Child, ovs_vm: Option, message: &str) -> ! { + pub fn print_and_panic( + src_vm: Child, + dest_vm: Child, + ovs_vm: Option, + message: &str, + ) -> ! { let mut src_vm = src_vm; let mut dest_vm = dest_vm; From 78799187e8ff219f901b5b5c21193b8f49a212e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 23:50:24 +0000 Subject: [PATCH 0107/1893] build: Bump event-listener from 5.4.0 to 5.4.1 Bumps [event-listener](https://github.com/smol-rs/event-listener) from 5.4.0 to 5.4.1. - [Release notes](https://github.com/smol-rs/event-listener/releases) - [Changelog](https://github.com/smol-rs/event-listener/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/event-listener/compare/v5.4.0...v5.4.1) --- updated-dependencies: - dependency-name: event-listener dependency-version: 5.4.1 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dede7fb8d4..6da3de4dc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -662,9 +662,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", From 92f415ea3f27c6e2a4ae7e5365bb69ef8ea6c1b0 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 15 Aug 2025 09:42:27 +0200 Subject: [PATCH 0108/1893] build: Bump MSRV to 1.88 This is necessary to use the let-chains feature in a follow-up. After upgrading to Rust edition 2024, clippy wants to collapse various if's with let-chains. Update image to 20250815-0 since MSRV in Dockerfile is updated. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- .github/workflows/build.yaml | 2 +- .github/workflows/docker-image.yaml | 2 +- .github/workflows/preview-riscv64.yaml | 2 +- .github/workflows/release.yaml | 2 +- Cargo.toml | 2 +- resources/Dockerfile | 4 ++-- scripts/dev_cli.sh | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 428740dcd0..286c2af548 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,7 +15,7 @@ jobs: - stable - beta - nightly - - "1.87.0" + - "1.88.0" target: - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 3e5f56458c..a026eac2c6 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -41,7 +41,7 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # generate Docker tags based on the following events/attributes tags: | - type=raw,value=20250807-0 + type=raw,value=20250815-0 type=sha - name: Build and push diff --git a/.github/workflows/preview-riscv64.yaml b/.github/workflows/preview-riscv64.yaml index 02f796b590..767d9779a8 100644 --- a/.github/workflows/preview-riscv64.yaml +++ b/.github/workflows/preview-riscv64.yaml @@ -24,7 +24,7 @@ jobs: fetch-depth: 0 - name: Install Rust toolchain - run: /opt/scripts/exec-in-qemu.sh rustup default 1.87.0 + run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0 - name: Build ${{ matrix.module }} Module (kvm) run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 44842475d1..4876fd5d98 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -45,7 +45,7 @@ jobs: target: ${{ matrix.platform.target }} args: ${{ matrix.platform.args }} strip: true - toolchain: "1.87.0" + toolchain: "1.88.0" - name: Copy Release Binaries if: github.event_name == 'create' && github.event.ref_type == 'tag' shell: bash diff --git a/Cargo.toml b/Cargo.toml index c621c256d4..9714ab4f87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ version = "47.0.0" # 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. -rust-version = "1.87.0" +rust-version = "1.88.0" [profile.release] codegen-units = 1 diff --git a/resources/Dockerfile b/resources/Dockerfile index a650c48211..704e8602f3 100644 --- a/resources/Dockerfile +++ b/resources/Dockerfile @@ -8,7 +8,7 @@ FROM ubuntu:24.04 AS dev ARG TARGETARCH -ARG RUST_TOOLCHAIN="1.87.0" +ARG RUST_TOOLCHAIN="1.88.0" ARG CLH_SRC_DIR="/cloud-hypervisor" ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build" ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry" @@ -123,7 +123,7 @@ RUN echo 'source $CARGO_HOME/env' >> "$HOME"/.bashrc \ && mkdir "$HOME"/.cargo \ && ln -s $CARGO_HOME/env "$HOME"/.cargo/env -# Allow pip to install packages system wide +# Allow pip to install packages system wide # hadolint ignore=DL3003,SC2046 RUN rm /usr/lib/python3.12/EXTERNALLY-MANAGED \ && git clone https://github.com/spdk/spdk \ diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 3349afa9d8..257889ae5b 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -9,7 +9,7 @@ CLI_NAME="Cloud Hypervisor" CTR_IMAGE_TAG="ghcr.io/cloud-hypervisor/cloud-hypervisor" # Needs to match explicit version in docker-image.yaml workflow -CTR_IMAGE_VERSION="20250807-0" +CTR_IMAGE_VERSION="20250815-0" : "${CTR_IMAGE:=${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}}" DOCKER_RUNTIME="docker" From dac0638fe8aaa4125024c7cbf4993a36bf06cc79 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 15 Aug 2025 10:31:06 +0200 Subject: [PATCH 0109/1893] misc: fix clippy Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- arch/src/riscv64/fdt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/src/riscv64/fdt.rs b/arch/src/riscv64/fdt.rs index 580aaa7d3f..ee453eb2fc 100644 --- a/arch/src/riscv64/fdt.rs +++ b/arch/src/riscv64/fdt.rs @@ -119,7 +119,7 @@ fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> { fdt.property_u32("timebase-frequency", timebase_frequency)?; for cpu_index in 0..num_cpus { - let cpu = fdt.begin_node(&format!("cpu@{:x}", cpu_index))?; + let cpu = fdt.begin_node(&format!("cpu@{cpu_index:x}"))?; fdt.property_string("device_type", "cpu")?; fdt.property_string("compatible", "riscv")?; fdt.property_string("mmu-type", "sv48")?; @@ -184,7 +184,7 @@ fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWr } let ram_start = super::layout::RAM_START.raw_value(); - let memory_node_name = format!("memory@{:x}", ram_start); + let memory_node_name = format!("memory@{ram_start:x}"); let memory_node = fdt.begin_node(&memory_node_name)?; fdt.property_string("device_type", "memory")?; fdt.property_array_u64("reg", &mem_reg_property)?; From dd8687aebbae67e1fcf9a4c2b1063c2ecbd60d27 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 25 Jun 2025 13:49:05 +0200 Subject: [PATCH 0110/1893] vmm: add enum PayloadConfigError validation to improve error reporting Currently, the following scenarios are supported by Cloud Hypervisor to bootstrap a VM: 1. provide firmware 2. provide kernel 3. provide kernel + cmdline 4. provide kernel + initrd 5. provide kernel + cmdline + initrd As the difference between `--firmware` and `--kernel` is not very clear currently, especially as both use/support a Xen PVH entry, adding this helps to identify the cause of misconfiguration. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- README.md | 14 +++++++++++++- vmm/src/config.rs | 17 +++++++++++------ vmm/src/vm.rs | 17 ++++++----------- vmm/src/vm_config.rs | 45 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4609903d0f..fdb18255f0 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ interface will be enabled as per `network-config` details. $ sudo setcap cap_net_admin+ep ./cloud-hypervisor $ ./create-cloud-init.sh $ ./cloud-hypervisor \ - --kernel ./hypervisor-fw \ + --firmware ./hypervisor-fw \ --disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \ --cpus boot=4 \ --memory size=1024M \ @@ -175,6 +175,18 @@ $ ./cloud-hypervisor \ --console off ``` +## Booting: `--firmware` vs `--kernel` + +The following scenarios are supported by Cloud Hypervisor to bootstrap a VM, i.e., +to load a payload/bootitem(s): + +- Provide firmware +- Provide kernel \[+ cmdline\]\ [+ initrd\] + +Please note that our Cloud Hypervisor firmware (`hypervisor-fw`) has a Xen PVH +boot entry, therefore it can also be booted via the `--kernel` parameter, as +seen in some examples. + ### Custom Kernel and Disk Image #### Building your Kernel diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 1977c8728c..06523761ad 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -181,9 +181,6 @@ pub enum Error { #[derive(Debug, PartialEq, Eq, Error)] pub enum ValidationError { - /// No kernel specified - #[error("No kernel specified")] - KernelMissing, /// Missing file value for console #[error("Path missing when using file console mode")] ConsoleFileMissing, @@ -356,6 +353,8 @@ pub enum ValidationError { /// Invalid Ivshmem backend file path #[error("Invalid ivshmem backend file path")] InvalidIvshmemPath, + #[error("Payload configuration is not bootable")] + PayloadError(#[from] PayloadConfigError), } type ValidationResult = std::result::Result; @@ -2507,9 +2506,13 @@ impl VmConfig { pub fn validate(&mut self) -> ValidationResult> { let mut id_list = BTreeSet::new(); + // Is the payload configuration bootable? self.payload - .as_ref() - .ok_or(ValidationError::KernelMissing)?; + .as_mut() + .ok_or(ValidationError::PayloadError( + PayloadConfigError::MissingBootitem, + ))? + .validate()?; #[cfg(feature = "tdx")] { @@ -4216,7 +4219,9 @@ mod tests { invalid_config.payload = None; assert_eq!( invalid_config.validate(), - Err(ValidationError::KernelMissing) + Err(ValidationError::PayloadError( + PayloadConfigError::MissingBootitem + )) ); let mut invalid_config = valid_config.clone(); diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 7e8851194d..347b39dc31 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -317,9 +317,6 @@ pub enum Error { #[error("Error joining kernel loading thread")] KernelLoadThreadJoin(std::boxed::Box), - #[error("Payload configuration is not bootable")] - InvalidPayload, - #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] #[error("Error coredumping VM")] Coredump(#[source] GuestDebuggableError), @@ -1217,7 +1214,7 @@ impl Vm { Self::load_firmware(&firmware, memory_manager)?; arch::layout::UEFI_START } - _ => return Err(Error::InvalidPayload), + _ => unreachable!("Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'"), }; Ok(EntryPoint { entry_addr }) @@ -1267,7 +1264,7 @@ impl Vm { Self::load_firmware(&firmware, memory_manager)?; arch::layout::UEFI_START } - _ => return Err(Error::InvalidPayload), + _ => unreachable!("Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'"), }; Ok(EntryPoint { entry_addr }) @@ -1385,19 +1382,17 @@ impl Vm { match ( &payload.firmware, &payload.kernel, - &payload.initramfs, - &payload.cmdline, ) { - (Some(firmware), None, None, None) => { + (Some(firmware), None) => { let firmware = File::open(firmware).map_err(Error::FirmwareFile)?; Self::load_kernel(firmware, None, memory_manager) } - (None, Some(kernel), _, _) => { + (None, Some(kernel)) => { let kernel = File::open(kernel).map_err(Error::KernelFile)?; let cmdline = Self::generate_cmdline(payload)?; Self::load_kernel(kernel, Some(cmdline), memory_manager) } - _ => Err(Error::InvalidPayload), + _ => unreachable!("Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'"), } } @@ -1415,7 +1410,7 @@ impl Vm { let kernel = File::open(kernel).map_err(Error::KernelFile)?; Self::load_kernel(None, Some(kernel), memory_manager) } - _ => Err(Error::InvalidPayload), + _ => unreachable!("Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'"), } } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 50841eeed8..cf1f61e05c 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -10,6 +10,7 @@ use std::{fs, result}; use net_util::MacAddr; use serde::{Deserialize, Serialize}; +use thiserror::Error; use virtio_devices::RateLimiterConfig; use crate::landlock::LandlockError; @@ -705,6 +706,21 @@ pub struct NumaConfig { pub pci_segments: Option>, } +/// Errors describing a misconfigured payload, i.e., a configuration that +/// can't be booted by Cloud Hypervisor. +/// +/// This typically is the case for invalid combinations of cmdline, kernel, +/// firmware, and initrd. +#[derive(Debug, Error, PartialEq, Eq)] +pub enum PayloadConfigError { + /// Specifying a kernel is not supported when a firmware is provided. + #[error("Specifying a kernel is not supported when a firmware is provided")] + FirmwarePlusOtherPayloads, + /// No bootitem provided: neither firmware nor kernel. + #[error("No bootitem provided: neither firmware nor kernel")] + MissingBootitem, +} + #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct PayloadConfig { #[serde(default)] @@ -796,6 +812,35 @@ impl FromStr for FwCfgItemList { } } +impl PayloadConfig { + /// Validates the payload config. + /// + /// Succeeds if Cloud Hypervisor will be able to boot the configuration. + /// Further, warns for some odd configurations. + pub fn validate(&mut self) -> Result<(), PayloadConfigError> { + match (&self.firmware, &self.kernel) { + (Some(_firmware), Some(_kernel)) => Err(PayloadConfigError::FirmwarePlusOtherPayloads), + (Some(_firmware), None) => { + if self.cmdline.is_some() { + log::warn!("Ignoring cmdline parameter as firmware is provided as the payload"); + self.cmdline = None; + } + if self.initramfs.is_some() { + log::warn!( + "Ignoring initramfs parameter as firmware is provided as the payload" + ); + self.initramfs = None; + } + Ok(()) + } + (None, Some(_kernel)) => Ok(()), + (None, None) => Err(PayloadConfigError::MissingBootitem), + }?; + + Ok(()) + } +} + impl ApplyLandlock for PayloadConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { // Payload only needs read access From 2c7d6be3f42ff0c108b8b33b0af88f400e063625 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Fri, 15 Aug 2025 01:50:42 +0000 Subject: [PATCH 0111/1893] arch: don't construct mptable on x86_64 if too many CPUs MP table is a legacy device that is incompatible with x2apic CPU IDs exceeding 254. The Linux kernel is perfectly happy without MP table in these cases. Signed-off-by: Barret Rhoden Signed-off-by: Neel Natu Signed-off-by: Ofir Weisse Signed-off-by: Peter Oskolkov --- arch/src/x86_64/mod.rs | 6 +++++- arch/src/x86_64/mptable.rs | 31 +++++++++++++------------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index a4c0b88bf1..27e1375d8a 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -33,6 +33,10 @@ use std::arch::x86_64; #[cfg(feature = "tdx")] pub mod tdx; +// While modern architectures support more than 255 CPUs via x2APIC, +// legacy devices such as mptable support at most 254 CPUs. +pub(crate) const MAX_SUPPORTED_CPUS_LEGACY: u32 = 254; + // CPUID feature bits #[cfg(feature = "kvm")] const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit. @@ -915,7 +919,7 @@ pub fn configure_vcpu( // 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) >= 255; + 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)?; } diff --git a/arch/src/x86_64/mptable.rs b/arch/src/x86_64/mptable.rs index d709a0043c..d688e41374 100644 --- a/arch/src/x86_64/mptable.rs +++ b/arch/src/x86_64/mptable.rs @@ -11,6 +11,7 @@ use libc::c_uchar; use thiserror::Error; use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError}; +use super::MAX_SUPPORTED_CPUS_LEGACY; use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START}; use crate::x86_64::{get_x2apic_id, mpspec}; use crate::GuestMemoryMmap; @@ -61,9 +62,6 @@ pub enum Error { /// Failure while zeroing out the memory for the MP table. #[error("Failure while zeroing out the memory for the MP table")] Clear(#[source] GuestMemoryError), - /// Number of CPUs exceeds the maximum supported CPUs - #[error("Number of CPUs exceeds the maximum supported CPUs")] - TooManyCpus, /// Failure to write the MP floating pointer. #[error("Failure to write the MP floating pointer")] WriteMpfIntel(#[source] GuestMemoryError), @@ -89,11 +87,6 @@ pub enum Error { pub type Result = result::Result; -// With APIC/xAPIC, there are only 255 APIC IDs available. And IOAPIC occupies -// one APIC ID, so only 254 CPUs at maximum may be supported. Actually it's -// a large number for FC usecases. -pub const MAX_SUPPORTED_CPUS: u32 = 254; - // Most of these variables are sourced from the Intel MP Spec 1.4. const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_"; const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP"; @@ -141,8 +134,9 @@ pub fn setup_mptable( if num_cpus > 0 { let cpu_id_max = num_cpus - 1; let x2apic_id_max = get_x2apic_id(cpu_id_max, topology); - if x2apic_id_max >= MAX_SUPPORTED_CPUS { - return Err(Error::TooManyCpus); + if x2apic_id_max >= MAX_SUPPORTED_CPUS_LEGACY { + info!("Skipping mptable creation due to too many CPUs"); + return Ok(()); } } @@ -157,7 +151,7 @@ pub fn setup_mptable( } let mut checksum: u8 = 0; - let ioapicid: u8 = MAX_SUPPORTED_CPUS as u8 + 1; + let ioapicid: u8 = MAX_SUPPORTED_CPUS_LEGACY as u8 + 1; // The checked_add here ensures the all of the following base_mp.unchecked_add's will be without // overflow. @@ -392,11 +386,13 @@ mod tests { #[test] fn cpu_entry_count() { - let mem = - GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(MAX_SUPPORTED_CPUS))]) - .unwrap(); + let mem = GuestMemoryMmap::from_ranges(&[( + MPTABLE_START, + compute_mp_size(MAX_SUPPORTED_CPUS_LEGACY), + )]) + .unwrap(); - for i in 0..MAX_SUPPORTED_CPUS { + for i in 0..MAX_SUPPORTED_CPUS_LEGACY { setup_mptable(MPTABLE_START, &mem, i, None).unwrap(); let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap(); @@ -426,10 +422,9 @@ mod tests { #[test] fn cpu_entry_count_max() { - let cpus = MAX_SUPPORTED_CPUS + 1; + let cpus = MAX_SUPPORTED_CPUS_LEGACY + 1; let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus))]).unwrap(); - let result = setup_mptable(MPTABLE_START, &mem, cpus, None); - result.unwrap_err(); + setup_mptable(MPTABLE_START, &mem, cpus, None).unwrap(); } } From a13200102144a9d7642e25a8a9301ad238db4e23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 23:28:16 +0000 Subject: [PATCH 0112/1893] build: Bump smallvec from 1.13.2 to 1.15.1 Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.13.2 to 1.15.1. - [Release notes](https://github.com/servo/rust-smallvec/releases) - [Commits](https://github.com/servo/rust-smallvec/compare/v1.13.2...v1.15.1) --- updated-dependencies: - dependency-name: smallvec dependency-version: 1.15.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- block/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6da3de4dc6..346f40fada 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1983,9 +1983,9 @@ checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "spin" diff --git a/block/Cargo.toml b/block/Cargo.toml index f1e102e922..aac824a004 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -16,7 +16,7 @@ libc = { workspace = true } log = { workspace = true } remain = "0.2.15" serde = { workspace = true, features = ["derive"] } -smallvec = "1.13.2" +smallvec = "1.15.1" thiserror = { workspace = true } uuid = { workspace = true, features = ["v4"] } virtio-bindings = { workspace = true } From c34baac6497a29809dde5af0c1ca26fa963d3db0 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 18 Aug 2025 11:34:22 +0200 Subject: [PATCH 0113/1893] build: fix warnings from latest nightly (1.91) The underlying problem currently causes unrelated PRs to fail. This commit fixes that. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- pci/src/msix.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pci/src/msix.rs b/pci/src/msix.rs index f323a69179..718c4f83ee 100644 --- a/pci/src/msix.rs +++ b/pci/src/msix.rs @@ -211,7 +211,7 @@ impl MsixConfig { } pub fn read_table(&self, offset: u64, data: &mut [u8]) { - assert!((data.len() == 4 || data.len() == 8)); + assert!(data.len() == 4 || data.len() == 8); let index: usize = (offset / MSIX_TABLE_ENTRIES_MODULO) as usize; let modulo_offset = offset % MSIX_TABLE_ENTRIES_MODULO; @@ -264,7 +264,7 @@ impl MsixConfig { } pub fn write_table(&mut self, offset: u64, data: &[u8]) { - assert!((data.len() == 4 || data.len() == 8)); + assert!(data.len() == 4 || data.len() == 8); let index: usize = (offset / MSIX_TABLE_ENTRIES_MODULO) as usize; let modulo_offset = offset % MSIX_TABLE_ENTRIES_MODULO; @@ -360,7 +360,7 @@ impl MsixConfig { } pub fn read_pba(&mut self, offset: u64, data: &mut [u8]) { - assert!((data.len() == 4 || data.len() == 8)); + assert!(data.len() == 4 || data.len() == 8); let index: usize = (offset / MSIX_PBA_ENTRIES_MODULO) as usize; let modulo_offset = offset % MSIX_PBA_ENTRIES_MODULO; From 09cf8fba5b2ecca48347a789e24b66496d0678fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 09:42:49 +0000 Subject: [PATCH 0114/1893] build: Bump bitflags from 2.9.0 to 2.9.2 Bumps [bitflags](https://github.com/bitflags/bitflags) from 2.9.0 to 2.9.2. - [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.9.0...2.9.2) --- updated-dependencies: - dependency-name: bitflags dependency-version: 2.9.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 28 ++++++++++++++-------------- Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 346f40fada..167390122b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,9 +299,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" [[package]] name = "block" @@ -527,7 +527,7 @@ dependencies = [ "anyhow", "arch", "bitfield-struct", - "bitflags 2.9.0", + "bitflags 2.9.2", "byteorder", "event_monitor", "hypervisor", @@ -640,7 +640,7 @@ version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", "libc", ] @@ -829,7 +829,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d66e32caf5dd59f561be0143e413e01d651bd8498eb9aa0be8c482c81c8d31" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", "cfg-if", "log", "managed", @@ -1077,7 +1077,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b702df98508cb63ad89dd9beb9f6409761b30edca10d48e57941d3f11513a006" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", "kvm-bindings", "libc", "vmm-sys-util", @@ -1112,7 +1112,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", "libc", ] @@ -1293,7 +1293,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", "cfg-if", "cfg_aliases", "libc", @@ -1834,7 +1834,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1847,7 +1847,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", "errno", "libc", "linux-raw-sys 0.9.4", @@ -2259,7 +2259,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed81c5ed8224d468a322e923777ed0615cad433fe61177126098af995f89cecf" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", "libc", "log", "serde", @@ -2277,7 +2277,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", "libc", "uuid", "vm-memory", @@ -2450,7 +2450,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.9.0", + "bitflags 2.9.2", "block", "blocking", "cfg-if", @@ -2852,7 +2852,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9714ab4f87..21e9cf1348 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,7 +136,7 @@ serde_with = { version = "3.14.0", default-features = false } # other crates anyhow = "1.0.98" -bitflags = "2.9.0" +bitflags = "2.9.2" byteorder = "1.5.0" cfg-if = "1.0.0" clap = "4.5.13" From e454bb629a7073316876ef3b3869cf047c1728ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 11:48:51 +0000 Subject: [PATCH 0115/1893] build: Bump mintex from 0.1.3 to 0.1.4 Bumps [mintex](https://github.com/garypen/mintex) from 0.1.3 to 0.1.4. - [Commits](https://github.com/garypen/mintex/compare/v0.1.3...v0.1.4) --- updated-dependencies: - dependency-name: mintex dependency-version: 0.1.4 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 167390122b..6f6256c9fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1219,9 +1219,9 @@ dependencies = [ [[package]] name = "mintex" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bec4598fddb13cc7b528819e697852653252b760f1228b7642679bf2ff2cd07" +checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mshv-bindings" From 91d15c331d534bb2a1337283ce6302f74a528fd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:18:19 +0000 Subject: [PATCH 0116/1893] build: Bump signal-hook-registry from 1.4.2 to 1.4.6 Bumps [signal-hook-registry](https://github.com/vorner/signal-hook) from 1.4.2 to 1.4.6. - [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md) - [Commits](https://github.com/vorner/signal-hook/compare/registry-v1.4.2...registry-v1.4.6) --- updated-dependencies: - dependency-name: signal-hook-registry dependency-version: 1.4.6 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f6256c9fa..a18feab93b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1968,9 +1968,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] From 3bff69734e9940e166b9387ba210c0b444b66e20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 02:06:49 +0000 Subject: [PATCH 0117/1893] build: Bump crate-ci/typos from 1.35.4 to 1.35.5 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.4 to 1.35.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.35.4...v1.35.5) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.35.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index bdd407b765..317fa84150 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.35.4 + - uses: crate-ci/typos@v1.35.5 From 34c5a081aa234a2e7963c365d051074471644162 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Tue, 19 Aug 2025 20:25:55 +0000 Subject: [PATCH 0118/1893] ci: Enable kvm build test on RISC-V Enable kvm build test and clippy test on RISC-V 64-bit platform to ensure whole projects builds properly. Signed-off-by: Ruoqing He --- .github/workflows/preview-riscv64-build.yaml | 30 +++++++++++++++++++ ...cv64.yaml => preview-riscv64-modules.yaml} | 0 2 files changed, 30 insertions(+) create mode 100644 .github/workflows/preview-riscv64-build.yaml rename .github/workflows/{preview-riscv64.yaml => preview-riscv64-modules.yaml} (100%) diff --git a/.github/workflows/preview-riscv64-build.yaml b/.github/workflows/preview-riscv64-build.yaml new file mode 100644 index 0000000000..929a60147a --- /dev/null +++ b/.github/workflows/preview-riscv64-build.yaml @@ -0,0 +1,30 @@ +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@v5 + with: + fetch-depth: 0 + + - name: Install Rust toolchain + run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0 + + - name: Build test (kvm) + run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked --no-default-features --features "kvm" + + - name: Clippy test (kvm) + run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm" + + - name: Check no files were modified + run: test -z "$(git status --porcelain)" diff --git a/.github/workflows/preview-riscv64.yaml b/.github/workflows/preview-riscv64-modules.yaml similarity index 100% rename from .github/workflows/preview-riscv64.yaml rename to .github/workflows/preview-riscv64-modules.yaml From c1f4df600fc79d5b2f807472218b90f63bc34e40 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 20 Aug 2025 11:40:48 +0200 Subject: [PATCH 0119/1893] vmm: cpu_manager: massively accelerate .pause() With 254 vCPUs, pausing now takes ~4ms instead of >254ms. This improvement is visible when running `ch-remote pause` and is particularly important for live migration, where every millisecond of downtime matters. For the wait logic, it is fine to stick to the approach of sleeping 1ms on the first missed ACK as: 1) we have to wait anyway 2) we give time to the OS, enabling it to schedule a vCPU thread next Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/cpu.rs | 56 +++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 629b0152b6..0f4ac18cc8 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -639,13 +639,25 @@ impl VcpuState { self.handle.is_some() } + /// Sends a signal to the underlying thread. + /// + /// Please call [`Self::wait_until_signal_acknowledged`] afterward to block + /// until the vCPU thread has acknowledged the signal. fn signal_thread(&self) { if let Some(handle) = self.handle.as_ref() { + // SAFETY: FFI call with correct arguments + unsafe { + libc::pthread_kill(handle.as_pthread_t() as _, SIGRTMIN()); + } + } + } + + /// Blocks until the vCPU thread has acknowledged the signal. + /// + /// This is the counterpart of [`Self::signal_thread`]. + fn wait_until_signal_acknowledged(&self) { + if let Some(_handle) = self.handle.as_ref() { loop { - // SAFETY: FFI call with correct arguments - unsafe { - libc::pthread_kill(handle.as_pthread_t() as _, SIGRTMIN()); - } if self.vcpu_run_interrupted.load(Ordering::SeqCst) { break; } else { @@ -1299,6 +1311,7 @@ impl CpuManager { let state = &mut self.vcpu_states[usize::try_from(cpu_id).unwrap()]; state.kill.store(true, Ordering::SeqCst); state.signal_thread(); + state.wait_until_signal_acknowledged(); state.join_thread()?; state.handle = None; @@ -1366,6 +1379,21 @@ impl CpuManager { } } + /// Signal to the spawned threads (vCPUs and console signal handler). + /// + /// For the vCPU threads this will interrupt the KVM_RUN ioctl() allowing + /// the loop to check the shared state booleans. + fn signal_vcpus(&self) { + // Splitting this into two loops reduced the time to pause many vCPUs + // massively. Example: 254 vCPUs. >254ms -> ~4ms. + for state in self.vcpu_states.iter() { + state.signal_thread(); + } + for state in self.vcpu_states.iter() { + state.wait_until_signal_acknowledged(); + } + } + pub fn shutdown(&mut self) -> Result<()> { // Tell the vCPUs to stop themselves next time they go through the loop self.vcpus_kill_signalled.store(true, Ordering::SeqCst); @@ -1378,12 +1406,7 @@ impl CpuManager { state.unpark_thread(); } - // Signal to the spawned threads (vCPUs and console signal handler). For the vCPU threads - // this will interrupt the KVM_RUN ioctl() allowing the loop to check the boolean set - // above. - for state in self.vcpu_states.iter() { - state.signal_thread(); - } + self.signal_vcpus(); // Wait for all the threads to finish. This removes the state from the vector. for mut state in self.vcpu_states.drain(..) { @@ -1934,11 +1957,7 @@ impl CpuManager { pub(crate) fn nmi(&self) -> Result<()> { self.vcpus_kick_signalled.store(true, Ordering::SeqCst); - - for state in self.vcpu_states.iter() { - state.signal_thread(); - } - + self.signal_vcpus(); self.vcpus_kick_signalled.store(false, Ordering::SeqCst); Ok(()) @@ -2300,12 +2319,7 @@ impl Pausable for CpuManager { // Tell the vCPUs to pause themselves next time they exit self.vcpus_pause_signalled.store(true, Ordering::SeqCst); - // Signal to the spawned threads (vCPUs and console signal handler). For the vCPU threads - // this will interrupt the KVM_RUN ioctl() allowing the loop to check the boolean set - // above. - for state in self.vcpu_states.iter() { - state.signal_thread(); - } + self.signal_vcpus(); for vcpu in self.vcpus.iter() { let mut vcpu = vcpu.lock().unwrap(); From 1a63b4b2ffaa21ae5c42739ccf790478b807e8a1 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Mon, 18 Aug 2025 19:13:52 +0000 Subject: [PATCH 0120/1893] vmm: Consolidate 'load_firmware/kernel' for aarch64 and riscv Both functions are defined separately for the two architecture with minor differences. * `load_firmware()`: call `arch::uefi::load_uefi` which are available on both architecture; * `load_kernel()`: manually align to `arch::layout::KERNEL_START` 2MB for both architecture (e.g. no-op for `aarch64`); Signed-off-by: Bo Chen --- arch/src/lib.rs | 2 +- vmm/src/vm.rs | 57 +++++-------------------------------------------- 2 files changed, 6 insertions(+), 53 deletions(-) diff --git a/arch/src/lib.rs b/arch/src/lib.rs index cbeb37f51d..aff58ffe31 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -97,7 +97,7 @@ pub mod riscv64; pub use riscv64::{ arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, - layout::IRQ_BASE, EntryPoint, _NSIG, + layout::IRQ_BASE, uefi, EntryPoint, _NSIG, }; #[cfg(target_arch = "x86_64")] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 347b39dc31..938bec8752 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1172,64 +1172,16 @@ impl Vm { Ok(cmdline) } - #[cfg(target_arch = "aarch64")] - fn load_firmware(mut firmware: &File, memory_manager: Arc>) -> Result<()> { - let uefi_flash = memory_manager.lock().as_ref().unwrap().uefi_flash(); - let mem = uefi_flash.memory(); - arch::aarch64::uefi::load_uefi(mem.deref(), arch::layout::UEFI_START, &mut firmware) - .map_err(Error::UefiLoad)?; - Ok(()) - } - - #[cfg(target_arch = "aarch64")] - fn load_kernel( - firmware: Option, - kernel: Option, - memory_manager: Arc>, - ) -> Result { - let guest_memory = memory_manager.lock().as_ref().unwrap().guest_memory(); - let mem = guest_memory.memory(); - let entry_addr = match (firmware, kernel) { - (None, Some(mut kernel)) => { - match linux_loader::loader::pe::PE::load( - mem.deref(), - Some(arch::layout::KERNEL_START), - &mut kernel, - None, - ) { - Ok(entry_addr) => entry_addr.kernel_load, - // Try to load the binary as kernel PE file at first. - // If failed, retry to load it as UEFI binary. - // As the UEFI binary is formatless, it must be the last option to try. - Err(linux_loader::loader::Error::Pe(InvalidImageMagicNumber)) => { - Self::load_firmware(&kernel, memory_manager)?; - arch::layout::UEFI_START - } - Err(e) => { - return Err(Error::KernelLoad(e)); - } - } - } - (Some(firmware), None) => { - Self::load_firmware(&firmware, memory_manager)?; - arch::layout::UEFI_START - } - _ => unreachable!("Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'"), - }; - - Ok(EntryPoint { entry_addr }) - } - - #[cfg(target_arch = "riscv64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_firmware(mut firmware: &File, memory_manager: Arc>) -> Result<()> { let uefi_flash = memory_manager.lock().as_ref().unwrap().uefi_flash(); let mem = uefi_flash.memory(); - arch::riscv64::uefi::load_uefi(mem.deref(), arch::layout::UEFI_START, &mut firmware) + arch::uefi::load_uefi(mem.deref(), arch::layout::UEFI_START, &mut firmware) .map_err(Error::UefiLoad)?; Ok(()) } - #[cfg(target_arch = "riscv64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_kernel( firmware: Option, kernel: Option, @@ -1238,7 +1190,8 @@ impl Vm { let guest_memory = memory_manager.lock().as_ref().unwrap().guest_memory(); let mem = guest_memory.memory(); let alignment = 0x20_0000; - let aligned_kernel_addr = arch::layout::KERNEL_START.0 + (alignment - 1) & !(alignment - 1); + let aligned_kernel_addr = + (arch::layout::KERNEL_START.0 + (alignment - 1)) & !(alignment - 1); let entry_addr = match (firmware, kernel) { (None, Some(mut kernel)) => { match linux_loader::loader::pe::PE::load( From 2c6426460efdacb2ce60b2aba13a6463e097d9ae Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 12 Aug 2025 12:39:38 +0200 Subject: [PATCH 0121/1893] vmm: harmonize bootpath across architectures On aarch64 and RISC-V, calling load_firmware() through load_kernel() provides no benefit and only duplicates checks already performed in load_payload(). load_payload() now directly invokes load_firmware() or load_kernel(), removing unnecessary indirection and redundancy. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/vm.rs | 60 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 938bec8752..af1ddaaa46 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1173,18 +1173,22 @@ impl Vm { } #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] - fn load_firmware(mut firmware: &File, memory_manager: Arc>) -> Result<()> { + fn load_firmware( + mut firmware: &File, + memory_manager: Arc>, + ) -> Result { let uefi_flash = memory_manager.lock().as_ref().unwrap().uefi_flash(); let mem = uefi_flash.memory(); arch::uefi::load_uefi(mem.deref(), arch::layout::UEFI_START, &mut firmware) .map_err(Error::UefiLoad)?; - Ok(()) + Ok(EntryPoint { + entry_addr: arch::layout::UEFI_START, + }) } #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_kernel( - firmware: Option, - kernel: Option, + mut kernel: File, memory_manager: Arc>, ) -> Result { let guest_memory = memory_manager.lock().as_ref().unwrap().guest_memory(); @@ -1192,32 +1196,25 @@ impl Vm { let alignment = 0x20_0000; let aligned_kernel_addr = (arch::layout::KERNEL_START.0 + (alignment - 1)) & !(alignment - 1); - let entry_addr = match (firmware, kernel) { - (None, Some(mut kernel)) => { - match linux_loader::loader::pe::PE::load( - mem.deref(), - Some(GuestAddress(aligned_kernel_addr)), - &mut kernel, - None, - ) { - Ok(entry_addr) => entry_addr.kernel_load, - // Try to load the binary as kernel PE file at first. - // If failed, retry to load it as UEFI binary. - // As the UEFI binary is formatless, it must be the last option to try. - Err(linux_loader::loader::Error::Pe(InvalidImageMagicNumber)) => { - Self::load_firmware(&kernel, memory_manager)?; - arch::layout::UEFI_START - } - Err(e) => { - return Err(Error::KernelLoad(e)); - } + let entry_addr = { + match linux_loader::loader::pe::PE::load( + mem.deref(), + Some(GuestAddress(aligned_kernel_addr)), + &mut kernel, + None, + ) { + Ok(entry_addr) => entry_addr.kernel_load, + // Try to load the binary as kernel PE file at first. + // If failed, retry to load it as UEFI binary. + // As the UEFI binary is formatless, it must be the last option to try. + Err(linux_loader::loader::Error::Pe(InvalidImageMagicNumber)) => { + Self::load_firmware(&kernel, memory_manager)?; + arch::layout::UEFI_START + } + Err(e) => { + return Err(Error::KernelLoad(e)); } } - (Some(firmware), None) => { - Self::load_firmware(&firmware, memory_manager)?; - arch::layout::UEFI_START - } - _ => unreachable!("Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'"), }; Ok(EntryPoint { entry_addr }) @@ -1254,6 +1251,9 @@ impl Vm { Ok(entry_point) } + /// Loads the kernel or a firmware file. + /// + /// For x86_64, the boot path is the same. #[cfg(target_arch = "x86_64")] fn load_kernel( mut kernel: File, @@ -1357,11 +1357,11 @@ impl Vm { match (&payload.firmware, &payload.kernel) { (Some(firmware), None) => { let firmware = File::open(firmware).map_err(Error::FirmwareFile)?; - Self::load_kernel(Some(firmware), None, memory_manager) + Self::load_firmware(&firmware, memory_manager) } (None, Some(kernel)) => { let kernel = File::open(kernel).map_err(Error::KernelFile)?; - Self::load_kernel(None, Some(kernel), memory_manager) + Self::load_kernel(kernel, memory_manager) } _ => unreachable!("Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'"), } From 3864230430a44d9981ea22275b5ae47f1561e1f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:36:19 +0000 Subject: [PATCH 0122/1893] build: Bump autocfg from 1.4.0 to 1.5.0 Bumps [autocfg](https://github.com/cuviper/autocfg) from 1.4.0 to 1.5.0. - [Commits](https://github.com/cuviper/autocfg/compare/1.4.0...1.5.0) --- updated-dependencies: - dependency-name: autocfg dependency-version: 1.5.0 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a18feab93b..3ac9fdb4dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,9 +261,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" From b8be33dff7694f983ac3d1eafc75c1065eb691c6 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 17 Jun 2025 12:37:13 +0200 Subject: [PATCH 0123/1893] net_util: tap: remove needless copy One can call `to_vec()` anyway if one needs an owned copy. This change further helps to prevent needless copies in upcoming changes. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- net_util/src/open_tap.rs | 2 +- net_util/src/tap.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/net_util/src/open_tap.rs b/net_util/src/open_tap.rs index e711529ca7..e02e3b5e8f 100644 --- a/net_util/src/open_tap.rs +++ b/net_util/src/open_tap.rs @@ -135,7 +135,7 @@ pub fn open_tap( // same device. tap = open_tap_rx_q_0(if_name, ip_addr, netmask, host_mac, mtu, num_rx_q, flags)?; // Set the name of the tap device we open in subsequent iterations. - ifname = String::from_utf8(tap.get_if_name()).unwrap(); + ifname = String::from_utf8(tap.get_if_name().to_vec()).unwrap(); } else { tap = Tap::open_named(ifname.as_str(), num_rx_q, flags).map_err(Error::TapOpen)?; diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 1dc0b7f486..bc841de119 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -481,8 +481,8 @@ impl Tap { ifreq } - pub fn get_if_name(&self) -> Vec { - self.if_name.clone() + pub fn get_if_name(&self) -> &[u8] { + &self.if_name } #[cfg(fuzzing)] From a51998605a3d003a59366e0203b1735d533b5521 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 27 Jun 2025 14:09:15 +0200 Subject: [PATCH 0124/1893] net_util: add Tap::if_name_as_str Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- net_util/src/open_tap.rs | 2 +- net_util/src/tap.rs | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/net_util/src/open_tap.rs b/net_util/src/open_tap.rs index e02e3b5e8f..21c48f8392 100644 --- a/net_util/src/open_tap.rs +++ b/net_util/src/open_tap.rs @@ -135,7 +135,7 @@ pub fn open_tap( // same device. tap = open_tap_rx_q_0(if_name, ip_addr, netmask, host_mac, mtu, num_rx_q, flags)?; // Set the name of the tap device we open in subsequent iterations. - ifname = String::from_utf8(tap.get_if_name().to_vec()).unwrap(); + ifname = tap.if_name_as_str().to_string(); } else { tap = Tap::open_named(ifname.as_str(), num_rx_q, flags).map_err(Error::TapOpen)?; diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index bc841de119..6d90293b87 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -481,10 +481,33 @@ impl Tap { ifreq } - pub fn get_if_name(&self) -> &[u8] { + /// Returns the raw bytes of the interface name, which may or may not be + /// valid UTF-8. + pub fn if_name_as_bytes(&self) -> &[u8] { &self.if_name } + /// Returns the interface name as a string, truncated at the first NUL byte + /// if present. + /// + /// # Panic + /// Panics if the interface name is not encoded as valid UTF-8. This can + /// only be caused by unrecoverable internal errors as users and management + /// software are only allowed to specify interfaces names as Rust strings, + /// thus valid UTF-8. Also, self-generated interface names form CHV are + /// also always created from Rust strings, thus valid UTF-8. + pub fn if_name_as_str(&self) -> &str { + // All bytes until first NUL. + let nul_terminated = self + .if_name_as_bytes() + .split(|&b| b == 0) + .next() + .unwrap_or(&[]); + + // Panicking here is fine, see function documentation. + std::str::from_utf8(nul_terminated).expect("Tap interface name should be valid UTF-8") + } + #[cfg(fuzzing)] pub fn new_for_fuzzing(tap_file: File, if_name: Vec) -> Self { Tap { tap_file, if_name } From 6e002defe2c9baf1df08179c91db73eedaa49889 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Sat, 23 Aug 2025 14:52:10 +0000 Subject: [PATCH 0125/1893] tests: remove redundant arch check in bzimage test test_direct_kernel_boot_bzimage runs only on x86, so the cfg!() branch for selecting grep_cmd is unnecessary. Remove it for clarity. Signed-off-by: Anirudh Rayabharam --- tests/integration.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 13e612485b..4e74cf0fc8 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3342,11 +3342,7 @@ mod common_parallel { assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); - let grep_cmd = if cfg!(target_arch = "x86_64") { - "grep -c PCI-MSI /proc/interrupts" - } else { - "grep -c ITS-PCI-MSIX /proc/interrupts" - }; + let grep_cmd = "grep -c PCI-MSI /proc/interrupts"; assert_eq!( guest .ssh_command(grep_cmd) From 6e6cf099ef1124561a0004dec23a30e4bcd45781 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Mon, 25 Aug 2025 22:28:33 +0000 Subject: [PATCH 0126/1893] hypervisor: enable x2apic API in kvm on x86_64 Also tweak extended interrupt handlings, as needed. Most credit should go to Neel Natu, who figured out the magic bits needed to make things work and provided detailed comments. This patch is still NOOP, as VM config allows only up to 254 vCPUs on x86_64. Note: changes in this and related previous patches/PRs have only been tested on Linux hosts running on Intel x86_64 hardware. Signed-off-by: Barret Rhoden Signed-off-by: Neel Natu Signed-off-by: Ofir Weisse Signed-off-by: Peter Oskolkov --- hypervisor/src/kvm/mod.rs | 85 ++++++++++++++++++++++++++++++++++++--- hypervisor/src/vm.rs | 10 +++++ 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index f92720779e..4204d4f83d 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -55,7 +55,8 @@ pub mod x86_64; #[cfg(target_arch = "x86_64")] use kvm_bindings::{ kvm_enable_cap, kvm_msr_entry, MsrList, KVM_CAP_HYPERV_SYNIC, KVM_CAP_SPLIT_IRQCHIP, - KVM_GUESTDBG_USE_HW_BP, + KVM_CAP_X2APIC_API, KVM_GUESTDBG_USE_HW_BP, KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK, + KVM_X2APIC_API_USE_32BIT_IDS, }; #[cfg(target_arch = "x86_64")] use x86_64::check_required_kvm_extensions; @@ -492,6 +493,50 @@ impl KvmVm { pub fn check_extension(&self, c: Cap) -> bool { self.fd.check_extension(c) } + + #[cfg(target_arch = "x86_64")] + /// Translates the MSI extended destination ID bits according to the logic + /// found in the Linux kernel's KVM MSI handling in kvm_msi_to_lapic_irq()/x86_msi_msg_get_destid(): + /// https://github.com/torvalds/linux/blob/3957a5720157264dcc41415fbec7c51c4000fc2d/arch/x86/kvm/irq.c#L266 + /// https://github.com/torvalds/linux/blob/3957a5720157264dcc41415fbec7c51c4000fc2d/arch/x86/kernel/apic/apic.c#L2306 + /// + /// This function moves bits [11, 5] from `address_lo` to bits [46, 40] in the combined 64-bit + /// address, but only if the Remappable Format (RF) bit (bit 4) in `address_lo` is + /// not set and `address_hi` is zero. + /// + /// The function is roughly equivalent to `uint64_t kvm_swizzle_msi_ext_dest_id(uint64_t address)` in + /// qemu/target/i386/kvm/kvm.c: + /// https://github.com/qemu/qemu/blob/88f72048d2f5835a1b9eaba690c7861393aef283/target/i386/kvm/kvm.c#L6258 + fn translate_msi_ext_dest_id(mut address_lo: u32, mut address_hi: u32) -> (u32, u32) { + // Mask for extracting the RF (Remappable Format) bit from address_lo. + // In the MSI specification, this is bit 4. See + // VT-d spec section "Interrupt Requests in Remappable Format" + const REMAPPABLE_FORMAT_BIT_MASK: u32 = 0x10; + let remappable_format_bit_is_set = (address_lo & REMAPPABLE_FORMAT_BIT_MASK) != 0; + + // Only perform the bit swizzling if the RF bit is unset and the upper + // 32 bits of the address are all zero. This identifies the legacy format. + if address_hi == 0 && !remappable_format_bit_is_set { + // "Move" the bits [11,5] to bits [46,40]. This is a shift of 35 bits, but + // since address is already split up into lo and hi, it's only a shift of + // 3 (35 - 32) within hi. + // "Move" via getting the bits via mask, zeroing out that range, and then + // ORing them back in at the correct location. The destination was already + // checked to be all zeroes. + const EXT_ID_MASK: u32 = 0xfe0; + const EXT_ID_SHIFT: u32 = 3; + let ext_id = address_lo & EXT_ID_MASK; + address_lo &= !EXT_ID_MASK; + address_hi |= ext_id << EXT_ID_SHIFT; + } + + (address_lo, address_hi) + } + + #[cfg(not(target_arch = "x86_64"))] + fn translate_msi_ext_dest_id(address_lo: u32, address_hi: u32) -> (u32, u32) { + (address_lo, address_hi) + } } /// Implementation of Vm trait for KVM @@ -647,8 +692,12 @@ impl vm::Vm for KvmVm { ..Default::default() }; - kvm_route.u.msi.address_lo = cfg.low_addr; - kvm_route.u.msi.address_hi = cfg.high_addr; + let (address_lo, address_hi) = + Self::translate_msi_ext_dest_id(cfg.low_addr, cfg.high_addr); + + kvm_route.u.msi.address_lo = address_lo; + kvm_route.u.msi.address_hi = address_hi; + kvm_route.u.msi.data = cfg.data; if self.check_extension(crate::kvm::Cap::MsiDevid) { @@ -822,6 +871,28 @@ impl vm::Vm for KvmVm { Ok(()) } + #[cfg(target_arch = "x86_64")] + fn enable_x2apic_api(&self) -> vm::Result<()> { + // From https://docs.kernel.org/virt/kvm/api.html: + // On x86, kvm_msi::address_hi is ignored unless the KVM_X2APIC_API_USE_32BIT_IDS feature of + // KVM_CAP_X2APIC_API capability is enabled. If it is enabled, address_hi bits 31-8 + // provide bits 31-8 of the destination id. Bits 7-0 of address_hi must be zero. + + // Thus KVM_X2APIC_API_USE_32BIT_IDS in combination with KVM_FEATURE_MSI_EXT_DEST_ID allows + // the guest to target interrupts to cpus with APIC IDs > 254. + + let mut cap = kvm_enable_cap { + cap: KVM_CAP_X2APIC_API, + ..Default::default() + }; + cap.args[0] = + (KVM_X2APIC_API_USE_32BIT_IDS | KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK) as u64; + self.fd + .enable_cap(&cap) + .map_err(|e| vm::HypervisorVmError::EnableX2ApicApi(e.into()))?; + Ok(()) + } + #[cfg(target_arch = "x86_64")] fn enable_sgx_attribute(&self, file: File) -> vm::Result<()> { let mut cap = kvm_enable_cap { @@ -1359,7 +1430,7 @@ impl cpu::Vcpu for KvmVcpu { let mut state = kvm_regs::default(); let mut off = offset_of!(user_pt_regs, regs); // There are 31 user_pt_regs: - // https://elixir.free-electrons.com/linux/v4.14.174/source/arch/arm64/include/uapi/asm/ptrace.h#L72 + // https://elixir.bootlin.com/linux/v4.14.174/source/arch/arm64/include/uapi/asm/ptrace.h#L72 // These actually are the general-purpose registers of the Armv8-a // architecture (i.e x0-x30 if used as a 64bit register or w0-30 when used as a 32bit register). for i in 0..31 { @@ -1439,7 +1510,7 @@ impl cpu::Vcpu for KvmVcpu { } // Now moving on to floating point registers which are stored in the user_fpsimd_state in the kernel: - // https://elixir.free-electrons.com/linux/v4.9.62/source/arch/arm64/include/uapi/asm/kvm.h#L53 + // https://elixir.bootlin.com/linux/v4.9.62/source/arch/arm64/include/uapi/asm/kvm.h#L53 let mut off = offset_of!(kvm_regs, fp_regs.vregs); for i in 0..32 { let mut bytes = [0_u8; 16]; @@ -2350,8 +2421,10 @@ impl cpu::Vcpu for KvmVcpu { .map_err(|e| cpu::HypervisorCpuError::SetRiscvCoreRegister(e.into()))?; // Last mandatory thing to set -> the address pointing to the FDT (also called DTB). + // + // In an earlier version of https://www.kernel.org/doc/Documentation/arch/riscv/boot.rst: // "The device tree blob (dtb) must be placed on an 8-byte boundary and must - // not exceed 64 kilobytes in size." -> https://www.kernel.org/doc/Documentation/arch/riscv/boot.txt. + // not exceed 64 kilobytes in size." let a1 = offset_of!(kvm_riscv_core, regs.a1); self.fd .lock() diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 25a80666ae..113c2001fe 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -126,6 +126,11 @@ pub enum HypervisorVmError { #[error("Failed to enable split Irq")] EnableSplitIrq(#[source] anyhow::Error), /// + /// Enable x2apic API error + /// + #[error("Failed to enable x2apic API")] + EnableX2ApicApi(#[source] anyhow::Error), + /// /// Enable SGX attribute error /// #[error("Failed to enable SGX attribute")] @@ -440,6 +445,11 @@ pub trait Vm: Send + Sync + Any { fn gain_page_access(&self, _gpa: u64, _size: u32) -> Result<()> { Ok(()) } + + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + fn enable_x2apic_api(&self) -> Result<()> { + unimplemented!("x2Apic is only supported on KVM/Linux hosts") + } } pub trait VmOps: Send + Sync { From 4a1eb0c4856832f12b4da29afa7a762608478304 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:56:36 +0000 Subject: [PATCH 0127/1893] build: Bump cc from 1.2.27 to 1.2.34 Bumps [cc](https://github.com/rust-lang/cc-rs) from 1.2.27 to 1.2.34. - [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.27...cc-v1.2.34) --- updated-dependencies: - dependency-name: cc dependency-version: 1.2.34 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ac9fdb4dc..af8984205d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,9 +351,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "shlex", ] From 92370e8ff18b44f42cd155eec326f9e0a729a507 Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Mon, 25 Aug 2025 14:59:04 +0800 Subject: [PATCH 0128/1893] block: Using feature bits to check the read-only flag This patch changes the read-only check using acked features bit, which will help to check more features. Signed-off-by: Songqian Li --- block/src/lib.rs | 3 +++ virtio-devices/src/block.rs | 34 ++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 4e10771c4d..d988a13992 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -139,6 +139,8 @@ pub enum ExecuteError { Read(#[source] GuestMemoryError), #[error("Failed to read_exact")] ReadExact(#[source] io::Error), + #[error("Can't execute an operation other than `read` on a read-only device")] + ReadOnly, #[error("Failed to seek")] Seek(#[source] io::Error), #[error("Failed to write")] @@ -168,6 +170,7 @@ impl ExecuteError { ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR, ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR, ExecuteError::ReadExact(_) => VIRTIO_BLK_S_IOERR, + ExecuteError::ReadOnly => VIRTIO_BLK_S_IOERR, ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR, ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR, ExecuteError::WriteAll(_) => VIRTIO_BLK_S_IOERR, diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index ff28f3ba76..7e337ad5bf 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -20,7 +20,7 @@ use std::{io, result}; use anyhow::anyhow; use block::async_io::{AsyncIo, AsyncIoError, DiskFile}; use block::fcntl::{get_lock_state, LockError, LockType}; -use block::{build_serial, fcntl, Request, RequestType, VirtioBlockConfig}; +use block::{build_serial, fcntl, ExecuteError, Request, RequestType, VirtioBlockConfig}; use rate_limiter::group::{RateLimiterGroup, RateLimiterGroupHandle}; use rate_limiter::TokenType; use seccompiler::SeccompAction; @@ -144,11 +144,25 @@ struct BlockEpollHandler { inflight_requests: VecDeque<(u16, Request)>, rate_limiter: Option, access_platform: Option>, - read_only: bool, host_cpus: Option>, + acked_features: u64, +} + +fn has_feature(features: u64, feature_flag: u64) -> bool { + (features & (1u64 << feature_flag)) != 0 } impl BlockEpollHandler { + fn check_request(features: u64, request_type: RequestType) -> result::Result<(), ExecuteError> { + if has_feature(features, VIRTIO_BLK_F_RO.into()) && request_type != RequestType::In { + // For virtio spec compliance + // "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request + // if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data." + return Err(ExecuteError::ReadOnly); + } + Ok(()) + } + fn process_queue_submit(&mut self) -> Result<()> { let queue = &mut self.queue; @@ -159,10 +173,8 @@ impl BlockEpollHandler { // For virtio spec compliance // "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request // if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data." - if self.read_only - && (request.request_type == RequestType::Out - || request.request_type == RequestType::Flush) - { + if let Err(e) = Self::check_request(self.acked_features, request.request_type) { + warn!("Request check failed: {:x?} {:?}", request, e); desc_chain .memory() .write_obj(VIRTIO_BLK_S_IOERR, request.status_addr) @@ -583,7 +595,6 @@ pub struct Block { seccomp_action: SeccompAction, rate_limiter: Option>, exit_evt: EventFd, - read_only: bool, serial: Vec, queue_affinity: BTreeMap>, } @@ -715,15 +726,18 @@ impl Block { seccomp_action, rate_limiter, exit_evt, - read_only, serial, queue_affinity, }) } + fn read_only(&self) -> bool { + has_feature(self.features(), VIRTIO_BLK_F_RO.into()) + } + /// Tries to set an advisory lock for the corresponding disk image. pub fn try_lock_image(&mut self) -> Result<()> { - let lock_type = match self.read_only { + let lock_type = match self.read_only() { true => LockType::Read, false => LockType::Write, }; @@ -904,8 +918,8 @@ impl VirtioDevice for Block { .transpose() .unwrap(), access_platform: self.common.access_platform.clone(), - read_only: self.read_only, host_cpus: self.queue_affinity.get(&queue_idx).cloned(), + acked_features: self.common.acked_features, }; let paused = self.common.paused.clone(); From bec975a7fc45f488f6c58143ea915735d1bc8cb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 17:21:55 +0000 Subject: [PATCH 0129/1893] build: Bump wait-timeout from 0.2.0 to 0.2.1 Bumps [wait-timeout](https://github.com/alexcrichton/wait-timeout) from 0.2.0 to 0.2.1. - [Commits](https://github.com/alexcrichton/wait-timeout/compare/0.2.0...0.2.1) --- updated-dependencies: - dependency-name: wait-timeout dependency-version: 0.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af8984205d..23bf435375 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2514,9 +2514,9 @@ dependencies = [ [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 21e9cf1348..25a142bb2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,5 +150,5 @@ log = "0.4.22" signal-hook = "0.3.18" thiserror = "2.0.12" uuid = { version = "1.17.0" } -wait-timeout = "0.2.0" +wait-timeout = "0.2.1" zerocopy = { version = "0.8.26", default-features = false } From 5357761c37080386cf02c19acdddb829ba1473f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:46:32 +0000 Subject: [PATCH 0130/1893] build: Bump async-io from 2.4.1 to 2.5.0 Bumps [async-io](https://github.com/smol-rs/async-io) from 2.4.1 to 2.5.0. - [Release notes](https://github.com/smol-rs/async-io/releases) - [Changelog](https://github.com/smol-rs/async-io/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/async-io/compare/v2.4.1...v2.5.0) --- updated-dependencies: - dependency-name: async-io dependency-version: 2.5.0 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23bf435375..a0ccdabcec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" dependencies = [ "async-lock", "cfg-if", @@ -174,8 +174,7 @@ dependencies = [ "polling", "rustix 1.0.7", "slab", - "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] From 9c85fbb1afefad55ebd3dfa37c8e09261a969b39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 07:06:48 +0000 Subject: [PATCH 0131/1893] build: Bump proc-macro2 from 1.0.95 to 1.0.101 Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.95 to 1.0.101. - [Release notes](https://github.com/dtolnay/proc-macro2/releases) - [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.95...1.0.101) --- updated-dependencies: - dependency-name: proc-macro2 dependency-version: 1.0.101 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0ccdabcec..5ca831622a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1678,9 +1678,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] From 41930d11f7a6a5c098a09795857e2598fbfb9093 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 23:02:04 +0000 Subject: [PATCH 0132/1893] build: Bump syn from 2.0.104 to 2.0.106 Bumps [syn](https://github.com/dtolnay/syn) from 2.0.104 to 2.0.106. - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.104...2.0.106) --- updated-dependencies: - dependency-name: syn dependency-version: 2.0.106 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ca831622a..0f88e6fdcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2021,9 +2021,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", From 8b2af1a2c841812a22be2d5eaffc37262f998a9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 23:20:08 +0000 Subject: [PATCH 0133/1893] build: Bump crate-ci/typos from 1.35.5 to 1.35.6 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.5 to 1.35.6. - [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.35.5...v1.35.6) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.35.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 317fa84150..a37eefa240 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.35.5 + - uses: crate-ci/typos@v1.35.6 From 67ab81874a5d35203580c9088719a91aa1d8c2bf Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 13 Aug 2025 00:21:50 +0000 Subject: [PATCH 0134/1893] block: virtio-devices: block: Clarify the return of execute_async() Instead of returning boolean return an struct of completion status so that it can be cached for batch submission. Signed-off-by: Bo Chen Signed-off-by: Muminul Islam --- block/src/lib.rs | 15 ++++++++++++--- virtio-devices/src/block.rs | 10 ++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index d988a13992..560010e266 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -232,6 +232,11 @@ pub struct AlignedOperation { layout: Layout, } +pub struct ExecuteAsync { + // `true` if the execution will complete asynchronously + pub async_complete: bool, +} + #[derive(Debug)] pub struct Request { pub request_type: RequestType, @@ -397,7 +402,7 @@ impl Request { disk_image: &mut dyn AsyncIo, serial: &[u8], user_data: u64, - ) -> result::Result { + ) -> result::Result { let sector = self.sector; let request_type = self.request_type; let offset = (sector << SECTOR_SHIFT) as libc::off_t; @@ -473,6 +478,9 @@ impl Request { iovecs.push(iovec); } + let mut ret = ExecuteAsync { + async_complete: true, + }; // Queue operations expected to be submitted. match request_type { RequestType::In => { @@ -507,12 +515,13 @@ impl Request { } mem.write_slice(serial, data_addr) .map_err(ExecuteError::Write)?; - return Ok(false); + ret.async_complete = false; + return Ok(ret); } RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), } - Ok(true) + Ok(ret) } pub fn complete_async(&mut self) -> result::Result<(), Error> { diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 7e337ad5bf..f301294b4f 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -20,7 +20,9 @@ use std::{io, result}; use anyhow::anyhow; use block::async_io::{AsyncIo, AsyncIoError, DiskFile}; use block::fcntl::{get_lock_state, LockError, LockType}; -use block::{build_serial, fcntl, ExecuteError, Request, RequestType, VirtioBlockConfig}; +use block::{ + build_serial, fcntl, ExecuteAsync, ExecuteError, Request, RequestType, VirtioBlockConfig, +}; use rate_limiter::group::{RateLimiterGroup, RateLimiterGroupHandle}; use rate_limiter::TokenType; use seccompiler::SeccompAction; @@ -232,7 +234,11 @@ impl BlockEpollHandler { desc_chain.head_index() as u64, ); - if let Ok(true) = result { + if let Ok(ExecuteAsync { + async_complete: true, + .. + }) = result + { self.inflight_requests .push_back((desc_chain.head_index(), request)); } else { From 245bce23fa4d2ce7890ec3c51693bc097b8963ed Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 13 Aug 2025 00:43:36 +0000 Subject: [PATCH 0135/1893] block, virtio-devices: Support request submission in batch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cache and batch IO requests after parsing all items in the queue, improving performance—especially for small block sizes—by reducing per-request overhead. Introduced two methods in the AsyncIo trait for batch submission, with implementation in the raw disk backend. This method should be called during/after parsing all block IO requests in the available queue. If the batch submission is not enabled, by default it does the old way of submitting requests. Signed-off-by: Bo Chen Signed-off-by: Muminul Islam --- block/src/async_io.rs | 8 ++++++- block/src/lib.rs | 40 ++++++++++++++++++++++++++++------ virtio-devices/src/block.rs | 43 ++++++++++++++++++++++++++++++++++--- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index 3f37bd6e34..2a4891d140 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -8,7 +8,7 @@ use std::os::fd::{AsRawFd, OwnedFd, RawFd}; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; -use crate::DiskTopology; +use crate::{BatchRequest, DiskTopology}; #[derive(Error, Debug)] pub enum DiskFileError { @@ -99,4 +99,10 @@ pub trait AsyncIo: Send { ) -> AsyncIoResult<()>; fn fsync(&mut self, user_data: Option) -> 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(()) + } } diff --git a/block/src/lib.rs b/block/src/lib.rs index 560010e266..3cca348b3b 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -232,9 +232,18 @@ pub struct AlignedOperation { layout: Layout, } +pub struct BatchRequest { + pub offset: libc::off_t, + pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>, + pub user_data: u64, + pub request_type: RequestType, +} + pub struct ExecuteAsync { // `true` if the execution will complete asynchronously pub async_complete: bool, + // request need to be batched for submission if any + pub batch_request: Option, } #[derive(Debug)] @@ -480,6 +489,7 @@ impl Request { let mut ret = ExecuteAsync { async_complete: true, + batch_request: None, }; // Queue operations expected to be submitted. match request_type { @@ -490,14 +500,32 @@ impl Request { .bitmap() .mark_dirty(0, *data_len as usize); } - disk_image - .read_vectored(offset, &iovecs, user_data) - .map_err(ExecuteError::AsyncRead)?; + if disk_image.batch_requests_enabled() { + ret.batch_request = Some(BatchRequest { + offset, + iovecs, + user_data, + request_type, + }); + } else { + disk_image + .read_vectored(offset, &iovecs, user_data) + .map_err(ExecuteError::AsyncRead)?; + } } RequestType::Out => { - disk_image - .write_vectored(offset, &iovecs, user_data) - .map_err(ExecuteError::AsyncWrite)?; + if disk_image.batch_requests_enabled() { + ret.batch_request = Some(BatchRequest { + offset, + iovecs, + user_data, + request_type, + }); + } else { + disk_image + .write_vectored(offset, &iovecs, user_data) + .map_err(ExecuteError::AsyncWrite)?; + } } RequestType::Flush => { disk_image diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index f301294b4f..415ae3f365 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -167,6 +167,8 @@ impl BlockEpollHandler { fn process_queue_submit(&mut self) -> Result<()> { let queue = &mut self.queue; + let mut batch_requests = Vec::new(); + let mut batch_inflight_requests = Vec::new(); while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) { let mut request = Request::parse(&mut desc_chain, self.access_platform.as_ref()) @@ -236,11 +238,21 @@ impl BlockEpollHandler { if let Ok(ExecuteAsync { async_complete: true, - .. + batch_request, }) = result { - self.inflight_requests - .push_back((desc_chain.head_index(), request)); + if let Some(batch_request) = batch_request { + match batch_request.request_type { + RequestType::In | RequestType::Out => batch_requests.push(batch_request), + _ => { + unreachable!( + "Unexpected batch request type: {:?}", + request.request_type + ) + } + } + } + batch_inflight_requests.push((desc_chain.head_index(), request)); } else { let status = match result { Ok(_) => VIRTIO_BLK_S_OK, @@ -266,6 +278,31 @@ impl BlockEpollHandler { } } + match self.disk_image.submit_batch_requests(&batch_requests) { + Ok(()) => { + self.inflight_requests.extend(batch_inflight_requests); + } + Err(e) => { + // If batch submission fails, report VIRTIO_BLK_S_IOERR for all requests. + for (user_data, request) in batch_inflight_requests { + warn!( + "Request failed with batch submission: {:x?} {:?}", + request, e + ); + let desc_index = user_data; + let mem = self.mem.memory(); + mem.write_obj(VIRTIO_BLK_S_IOERR as u8, request.status_addr) + .map_err(Error::RequestStatus)?; + queue + .add_used(mem.deref(), desc_index, 0) + .map_err(Error::QueueAddUsed)?; + queue + .enable_notification(mem.deref()) + .map_err(Error::QueueEnableNotification)?; + } + } + } + Ok(()) } From c4bab33020557e644713c1cc6a8e29de78bcb4ea Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 13 Aug 2025 01:17:00 +0000 Subject: [PATCH 0136/1893] block: Enable request submission in batch when using io_uring Implement the batch submission function for raw disk, default it is enabled. After parsing the requests this method is called for better IO latency and bandwidth. Signed-off-by: Bo Chen Signed-off-by: Muminul Islam --- block/src/async_io.rs | 3 ++ block/src/raw_async.rs | 75 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index 2a4891d140..aa31c54367 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -79,6 +79,9 @@ pub enum AsyncIoError { /// Failed synchronizing file. #[error("Failed synchronizing file")] Fsync(#[source] std::io::Error), + /// Failed submitting batch requests. + #[error("Failed submitting batch requests: {0}")] + SubmitBatchRequests(#[source] std::io::Error), } pub type AsyncIoResult = std::result::Result; diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 496445c6ad..b3c9882fbb 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -12,7 +12,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::DiskTopology; +use crate::{BatchRequest, DiskTopology, RequestType}; pub struct RawFileDisk { file: File, @@ -168,4 +168,77 @@ impl AsyncIo for RawFileAsync { .next() .map(|entry| (entry.user_data(), entry.result())) } + + fn batch_requests_enabled(&self) -> bool { + true + } + + fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> { + if !self.batch_requests_enabled() { + return Ok(()); + } + + let (submitter, mut sq, _) = self.io_uring.split(); + let mut submitted = false; + + for req in batch_request { + match req.request_type { + RequestType::In => { + // SAFETY: we know the file descriptor is valid and we + // relied on vm-memory to provide the buffer address. + unsafe { + sq.push( + &opcode::Readv::new( + types::Fd(self.fd), + req.iovecs.as_ptr(), + req.iovecs.len() as u32, + ) + .offset(req.offset as u64) + .build() + .user_data(req.user_data), + ) + .map_err(|_| { + AsyncIoError::ReadVectored(Error::other("Submission queue is full")) + })? + }; + submitted = true; + } + RequestType::Out => { + // SAFETY: we know the file descriptor is valid and we + // relied on vm-memory to provide the buffer address. + unsafe { + sq.push( + &opcode::Writev::new( + types::Fd(self.fd), + req.iovecs.as_ptr(), + req.iovecs.len() as u32, + ) + .offset(req.offset as u64) + .build() + .user_data(req.user_data), + ) + .map_err(|_| { + AsyncIoError::WriteVectored(Error::other("Submission queue is full")) + })? + }; + submitted = true; + } + _ => { + unreachable!("Unexpected batch request type: {:?}", req.request_type) + } + } + } + + // Only submit if we actually queued something + if submitted { + // Update the submission queue and submit new operations to the + // io_uring instance. + sq.sync(); + submitter + .submit() + .map_err(AsyncIoError::SubmitBatchRequests)?; + } + + Ok(()) + } } From a9d680752278670a2db134b56c6cc8c2ef8d8c24 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 8 Aug 2025 16:02:48 -0700 Subject: [PATCH 0137/1893] block: batch submit requests for fixed VHD Updated VHD async implementation to call the batch submit method via the raw async IO layer. Signed-off-by: Muminul Islam --- block/src/fixed_vhd_async.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index 6b51d070f8..ac02e21bf3 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -12,7 +12,7 @@ use crate::async_io::{ }; use crate::fixed_vhd::FixedVhd; use crate::raw_async::RawFileAsync; -use crate::BlockBackend; +use crate::{BatchRequest, BlockBackend}; pub struct FixedVhdDiskAsync(FixedVhd); @@ -106,4 +106,12 @@ impl AsyncIo for FixedVhdAsync { 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) + } } From c3a809696a1fe97308785a562fb62b1c10e9b45c Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 26 Aug 2025 08:07:04 +0200 Subject: [PATCH 0138/1893] docs: add Safety section to unsafe functions This step was done manually by searching for "unsafe fn" in the code base and adding corresponding Safety sections. `clippy::missing_safety_doc` only works for public functions but none of the corresponding functions is public. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- net_util/src/tap.rs | 9 +++++++++ vmm/src/clone3.rs | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 6d90293b87..533d6a45b2 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -129,6 +129,9 @@ fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result { } impl Tap { + /// # Safety + /// The caller should ensure to pass a valid file descriptor and valid + /// arguments for the `ioctl()` syscall. unsafe fn ioctl_with_mut_ref(fd: &F, req: c_ulong, arg: &mut T) -> Result<()> { let ret = ioctl_with_mut_ref(fd, req, arg); if ret < 0 { @@ -138,6 +141,9 @@ impl Tap { Ok(()) } + /// # Safety + /// The caller should ensure to pass a valid file descriptor and valid + /// arguments for the `ioctl()` syscall. unsafe fn ioctl_with_ref(fd: &F, req: c_ulong, arg: &T) -> Result<()> { let ret = ioctl_with_ref(fd, req, arg); if ret < 0 { @@ -147,6 +153,9 @@ impl Tap { Ok(()) } + /// # Safety + /// The caller should ensure to pass a valid file descriptor and valid + /// arguments for the `ioctl()` syscall. unsafe fn ioctl_with_val(fd: &F, req: c_ulong, arg: c_ulong) -> Result<()> { let ret = ioctl_with_val(fd, req, arg); if ret < 0 { diff --git a/vmm/src/clone3.rs b/vmm/src/clone3.rs index f08e5ad31c..0ab08126e1 100644 --- a/vmm/src/clone3.rs +++ b/vmm/src/clone3.rs @@ -22,6 +22,16 @@ pub struct clone_args { pub cgroup: u64, } +/// # Safety +/// `size` must have the proper size to match `args`. +/// Further, the caller needs to check the return value. +/// +/// # Return +/// - On success: +/// - Parent: child PID (`c_long`) +/// - Child: `0` +/// - On error: `-1` and `errno` is set +#[must_use] pub unsafe fn clone3(args: &mut clone_args, size: size_t) -> c_long { syscall(SYS_clone3, args, size) } From f68880600ca9c96525e367a55409e1889256e304 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 23:19:16 +0000 Subject: [PATCH 0139/1893] build: Bump crate-ci/typos from 1.35.6 to 1.35.7 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.6 to 1.35.7. - [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.35.6...v1.35.7) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.35.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index a37eefa240..08922bdaf2 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.35.6 + - uses: crate-ci/typos@v1.35.7 From 456f9b3871f4f2e0b79b50022a678be4c2171268 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 23:01:37 +0000 Subject: [PATCH 0140/1893] build: Bump bitflags from 2.9.2 to 2.9.3 Bumps [bitflags](https://github.com/bitflags/bitflags) from 2.9.2 to 2.9.3. - [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.9.2...2.9.3) --- updated-dependencies: - dependency-name: bitflags dependency-version: 2.9.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 28 ++++++++++++++-------------- Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f88e6fdcd..e4efc129cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,9 +298,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "block" @@ -526,7 +526,7 @@ dependencies = [ "anyhow", "arch", "bitfield-struct", - "bitflags 2.9.2", + "bitflags 2.9.3", "byteorder", "event_monitor", "hypervisor", @@ -639,7 +639,7 @@ version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "libc", ] @@ -828,7 +828,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d66e32caf5dd59f561be0143e413e01d651bd8498eb9aa0be8c482c81c8d31" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg-if", "log", "managed", @@ -1076,7 +1076,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b702df98508cb63ad89dd9beb9f6409761b30edca10d48e57941d3f11513a006" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "kvm-bindings", "libc", "vmm-sys-util", @@ -1111,7 +1111,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "libc", ] @@ -1292,7 +1292,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg-if", "cfg_aliases", "libc", @@ -1833,7 +1833,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1846,7 +1846,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys 0.9.4", @@ -2258,7 +2258,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed81c5ed8224d468a322e923777ed0615cad433fe61177126098af995f89cecf" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "libc", "log", "serde", @@ -2276,7 +2276,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "libc", "uuid", "vm-memory", @@ -2449,7 +2449,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.9.2", + "bitflags 2.9.3", "block", "blocking", "cfg-if", @@ -2851,7 +2851,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 25a142bb2d..8f49a0da80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,7 +136,7 @@ serde_with = { version = "3.14.0", default-features = false } # other crates anyhow = "1.0.98" -bitflags = "2.9.2" +bitflags = "2.9.3" byteorder = "1.5.0" cfg-if = "1.0.0" clap = "4.5.13" From c38596d6d3fee8dabacbffac3eb742eb5780bc70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 22:43:03 +0000 Subject: [PATCH 0141/1893] build: Bump serde_json from 1.0.120 to 1.0.143 Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.120 to 1.0.143. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.120...v1.0.143) --- updated-dependencies: - dependency-name: serde_json dependency-version: 1.0.143 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4efc129cf..ef5cb9ddcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1902,11 +1902,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 8f49a0da80..767d8ff6c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,7 +131,7 @@ igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates serde = "1.0.208" -serde_json = "1.0.120" +serde_json = "1.0.143" serde_with = { version = "3.14.0", default-features = false } # other crates From 1ca6c159ef4cca6ffa94f24daa75e7971e8dbd16 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 2 Sep 2025 15:43:03 -0700 Subject: [PATCH 0142/1893] tests: option to override default migratable version This patch gives user an option to override the default migratable version to any later release. This option makes MSHV specific tests suitable for tests since MSHV is stable after some breaking changes. This patch is also necessary for MSHV CI. Signed-off-by: Muminul Islam --- scripts/dev_cli.sh | 1 + scripts/run_integration_tests_live_migration.sh | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 257889ae5b..6dea0d7d22 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -538,6 +538,7 @@ cmd_tests() { --env TARGET_CC="$target_cc" \ --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ + --env MIGRATABLE_VERSION="$MIGRATABLE_VERSION" \ "$CTR_IMAGE" \ ./scripts/run_integration_tests_live_migration.sh "$@" || fix_dir_perms $? || exit $? fi diff --git a/scripts/run_integration_tests_live_migration.sh b/scripts/run_integration_tests_live_migration.sh index 0df9c01c91..fa0b3dcf45 100755 --- a/scripts/run_integration_tests_live_migration.sh +++ b/scripts/run_integration_tests_live_migration.sh @@ -11,6 +11,7 @@ mkdir -p "$WORKLOADS_DIR" process_common_args "$@" +migratable_version=v39.0 # For now these values are default for kvm test_features="" @@ -18,6 +19,15 @@ if [ "$hypervisor" = "mshv" ]; then test_features="--features mshv" fi +# if migratable version is set to override the default +if [ -n "${MIGRATABLE_VERSION}" ]; then + # validate the version if matched with vxx.0 + if ! [[ "${MIGRATABLE_VERSION}" =~ ^v[0-9]{2,}\.[0-9]$ ]]; then + echo "MIGRATABLE_VERSION should be in format vxx.0, e.g. v47.0" + exit 1 + fi + migratable_version=${MIGRATABLE_VERSION} +fi cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR" FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2" @@ -45,8 +55,7 @@ fi popd || exit # Download Cloud Hypervisor binary from its last stable release -LAST_RELEASE_VERSION="v39.0" -CH_RELEASE_URL="https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/$LAST_RELEASE_VERSION/cloud-hypervisor-static" +CH_RELEASE_URL="https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/${migratable_version}/cloud-hypervisor-static" CH_RELEASE_NAME="cloud-hypervisor-static" pushd "$WORKLOADS_DIR" || exit time wget --quiet $CH_RELEASE_URL -O "$CH_RELEASE_NAME" || exit 1 From 92325fc073a7cac44cd431fcbd58a693fbe64e52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 17:58:02 +0000 Subject: [PATCH 0143/1893] build: Bump zvariant from 5.6.0 to 5.7.0 Bumps [zvariant](https://github.com/dbus2/zbus) from 5.6.0 to 5.7.0. - [Release notes](https://github.com/dbus2/zbus/releases) - [Commits](https://github.com/dbus2/zbus/compare/zvariant-5.6.0...zvariant-5.7.0) --- updated-dependencies: - dependency-name: zvariant dependency-version: 5.7.0 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef5cb9ddcd..06680355e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2958,9 +2958,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" dependencies = [ "endi", "enumflags2", @@ -2972,9 +2972,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" dependencies = [ "proc-macro-crate", "proc-macro2", From bd2219004353aa1d45440abce3a8adcc617036bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 07:21:02 +0000 Subject: [PATCH 0144/1893] build: Bump uuid from 1.17.0 to 1.18.1 Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.17.0 to 1.18.1. - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/v1.17.0...v1.18.1) --- updated-dependencies: - dependency-name: uuid dependency-version: 1.18.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06680355e8..0f9793b81b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2209,9 +2209,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 767d8ff6c3..74cdbbeb17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,6 +149,6 @@ libc = "0.2.167" log = "0.4.22" signal-hook = "0.3.18" thiserror = "2.0.12" -uuid = { version = "1.17.0" } +uuid = { version = "1.18.1" } wait-timeout = "0.2.1" zerocopy = { version = "0.8.26", default-features = false } From f7e72456b968592b27224ddbbe58b7a9c2aed9c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:18:23 +0000 Subject: [PATCH 0145/1893] build: Bump anstyle-parse from 0.2.6 to 0.2.7 Bumps [anstyle-parse](https://github.com/rust-cli/anstyle) from 0.2.6 to 0.2.7. - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-parse-v0.2.6...anstyle-parse-v0.2.7) --- updated-dependencies: - dependency-name: anstyle-parse dependency-version: 0.2.7 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f9793b81b..3e6a50bce8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,9 +57,9 @@ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] From 676fb93c1a1ad16773b6f43d01e55ae89b836f79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:29:24 +0000 Subject: [PATCH 0146/1893] build: Bump crate-ci/typos from 1.35.7 to 1.36.1 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.7 to 1.36.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.35.7...v1.36.1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.36.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 08922bdaf2..24ee35990c 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.35.7 + - uses: crate-ci/typos@v1.36.1 From 1cc193ea3ca0438d44b2b6a1593770917a9819be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:24:08 +0000 Subject: [PATCH 0147/1893] build: Bump anstyle from 1.0.8 to 1.0.11 Bumps [anstyle](https://github.com/rust-cli/anstyle) from 1.0.8 to 1.0.11. - [Commits](https://github.com/rust-cli/anstyle/compare/v1.0.8...v1.0.11) --- updated-dependencies: - dependency-name: anstyle dependency-version: 1.0.11 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e6a50bce8..440c50b5ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" From f7f79642ab79999c514e8cd36bd9315855ea6d78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 14:54:21 +0000 Subject: [PATCH 0148/1893] build: Bump io-uring from 0.6.4 to 0.7.10 Bumps [io-uring](https://github.com/tokio-rs/io-uring) from 0.6.4 to 0.7.10. - [Commits](https://github.com/tokio-rs/io-uring/commits) --- updated-dependencies: - dependency-name: io-uring dependency-version: 0.7.10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 7 ++++--- block/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 440c50b5ee..cffc146c9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -996,11 +996,12 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.6.4" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595a0399f411a508feb2ec1e970a4a30c249351e30208960d58298de8660b0e5" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.3", + "cfg-if", "libc", ] diff --git a/block/Cargo.toml b/block/Cargo.toml index aac824a004..9e0505921e 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -11,7 +11,7 @@ io_uring = ["dep:io-uring"] [dependencies] byteorder = { workspace = true } crc-any = "2.5.0" -io-uring = { version = "0.6.4", optional = true } +io-uring = { version = "0.7.10", optional = true } libc = { workspace = true } log = { workspace = true } remain = "0.2.15" From 9bf4696b09f815a1de8594c3b042d54c7f76d642 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:01:56 +0000 Subject: [PATCH 0149/1893] build: Bump libssh2-sys from 0.3.0 to 0.3.1 Bumps [libssh2-sys](https://github.com/alexcrichton/ssh2-rs) from 0.3.0 to 0.3.1. - [Commits](https://github.com/alexcrichton/ssh2-rs/compare/libssh2-sys-0.3.0...libssh2-sys-0.3.1) --- updated-dependencies: - dependency-name: libssh2-sys dependency-version: 0.3.1 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cffc146c9a..f985a18180 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,9 +1118,9 @@ dependencies = [ [[package]] name = "libssh2-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" +checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" dependencies = [ "cc", "libc", From 7281459bf946260b9401d405d2e752e0aa1a1c01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:17:01 +0000 Subject: [PATCH 0150/1893] build: Bump actions/setup-python from 5 to 6 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/dco.yaml | 2 +- .github/workflows/gitlint.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dco.yaml b/.github/workflows/dco.yaml index 4c83547872..daf21315e0 100644 --- a/.github/workflows/dco.yaml +++ b/.github/workflows/dco.yaml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python 3.x - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.x' - name: Check DCO diff --git a/.github/workflows/gitlint.yaml b/.github/workflows/gitlint.yaml index c31fee202d..7c3c4f7e45 100644 --- a/.github/workflows/gitlint.yaml +++ b/.github/workflows/gitlint.yaml @@ -13,7 +13,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - name: Set up Python 3.10 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" - name: Install dependencies From 2d9e2431638dfdbc341625a1ab67f3814dbd77cf Mon Sep 17 00:00:00 2001 From: Shubham Chakrawar Date: Fri, 15 Aug 2025 13:18:14 -0700 Subject: [PATCH 0151/1893] misc: Remove SGX support from Cloud Hypervisor This commit removes the SGX support from cloud hypervisor. SGX support was deprecated in May as part of #7090. Signed-off-by: Shubham Chakrawar --- .lychee.toml | 2 - arch/src/lib.rs | 5 - arch/src/x86_64/mod.rs | 154 +---------------- docs/intel_sgx.md | 54 ------ docs/memory.md | 23 +-- docs/snapshot_restore.md | 2 +- fuzz/fuzz_targets/http_api.rs | 2 - hypervisor/src/kvm/mod.rs | 26 +-- hypervisor/src/mshv/mod.rs | 7 - hypervisor/src/vm.rs | 10 -- scripts/dev_cli.sh | 26 --- scripts/run_integration_tests_sgx.sh | 51 ------ src/main.rs | 10 -- test_infra/src/lib.rs | 18 -- tests/integration.rs | 44 ----- vmm/src/acpi.rs | 10 -- vmm/src/api/openapi/cloud-hypervisor.yaml | 23 --- vmm/src/config.rs | 85 +--------- vmm/src/cpu.rs | 11 -- vmm/src/lib.rs | 8 +- vmm/src/memory_manager.rs | 197 +--------------------- vmm/src/vm.rs | 33 ---- vmm/src/vm_config.rs | 15 -- 23 files changed, 11 insertions(+), 805 deletions(-) delete mode 100644 docs/intel_sgx.md delete mode 100755 scripts/run_integration_tests_sgx.sh diff --git a/.lychee.toml b/.lychee.toml index 875a861826..44517a7819 100644 --- a/.lychee.toml +++ b/.lychee.toml @@ -2,8 +2,6 @@ verbose = "info" exclude = [ # Availability of links below should be manually verified. - # Page for intel SGX support, returns 403 while querying. - '^https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html', # 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. diff --git a/arch/src/lib.rs b/arch/src/lib.rs index aff58ffe31..2413fe2235 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -18,9 +18,6 @@ use std::{fmt, result}; use serde::{Deserialize, Serialize}; use thiserror::Error; -#[cfg(target_arch = "x86_64")] -use crate::x86_64::SgxEpcSection; - type GuestMemoryMmap = vm_memory::GuestMemoryMmap; type GuestRegionMmap = vm_memory::GuestRegionMmap; @@ -127,8 +124,6 @@ pub struct NumaNode { pub pci_segments: Vec, pub distances: BTreeMap, pub memory_zones: Vec, - #[cfg(target_arch = "x86_64")] - pub sgx_epc_sections: Vec, } pub type NumaNodes = BTreeMap; diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 27e1375d8a..22d1a1cfea 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -12,7 +12,6 @@ pub mod layout; mod mpspec; mod mptable; pub mod regs; -use std::collections::BTreeMap; use std::mem; use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX}; @@ -24,7 +23,7 @@ use linux_loader::loader::elf::start_info::{ use thiserror::Error; use vm_memory::{ Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, - GuestMemoryRegion, GuestUsize, + GuestMemoryRegion, }; use crate::{GuestMemoryMmap, InitramfsConfig, RegionType}; @@ -79,55 +78,7 @@ pub struct EntryPoint { const E820_RAM: u32 = 1; const E820_RESERVED: u32 = 2; -#[derive(Clone)] -pub struct SgxEpcSection { - start: GuestAddress, - size: GuestUsize, -} - -impl SgxEpcSection { - pub fn new(start: GuestAddress, size: GuestUsize) -> Self { - SgxEpcSection { start, size } - } - pub fn start(&self) -> GuestAddress { - self.start - } - pub fn size(&self) -> GuestUsize { - self.size - } -} - -#[derive(Clone)] -pub struct SgxEpcRegion { - start: GuestAddress, - size: GuestUsize, - epc_sections: BTreeMap, -} - -impl SgxEpcRegion { - pub fn new(start: GuestAddress, size: GuestUsize) -> Self { - SgxEpcRegion { - start, - size, - epc_sections: BTreeMap::new(), - } - } - pub fn start(&self) -> GuestAddress { - self.start - } - pub fn size(&self) -> GuestUsize { - self.size - } - pub fn epc_sections(&self) -> &BTreeMap { - &self.epc_sections - } - pub fn insert(&mut self, id: String, epc_section: SgxEpcSection) { - self.epc_sections.insert(id, epc_section); - } -} - pub struct CpuidConfig { - pub sgx_epc_sections: Option>, pub phys_bits: u8, pub kvm_hyperv: bool, #[cfg(feature = "tdx")] @@ -169,18 +120,6 @@ pub enum Error { #[error("Error setting up SMBIOS table")] SmbiosSetup(#[source] smbios::Error), - /// Could not find any SGX EPC section - #[error("Could not find any SGX EPC section")] - NoSgxEpcSection, - - /// Missing SGX CPU feature - #[error("Missing SGX CPU feature")] - MissingSgxFeature, - - /// Missing SGX_LC CPU feature - #[error("Missing SGX_LC CPU feature")] - MissingSgxLaunchControlFeature, - /// Error getting supported CPUID through the hypervisor (kvm/mshv) API #[error("Error getting supported CPUID through the hypervisor API")] CpuidGetSupported(#[source] HypervisorError), @@ -467,7 +406,7 @@ impl CpuidFeatureEntry { feature_reg: CpuidReg::EDX, compatible_check: CpuidCompatibleCheck::BitwiseSubset, }, - // KVM CPUID bits: https://www.kernel.org/doc/html/latest/virt/kvm/cpuid.html + // KVM CPUID bits: https://www.kernel.org/doc/html/latest/virt/kvm/x86/cpuid.html // Leaf 0x4000_0000, EAX/EBX/ECX/EDX, KVM CPUID SIGNATURE CpuidFeatureEntry { function: 0x4000_0000, @@ -675,10 +614,6 @@ pub fn generate_common_cpuid( CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches); - if let Some(sgx_epc_sections) = &config.sgx_epc_sections { - update_cpuid_sgx(&mut cpuid, sgx_epc_sections)?; - } - #[cfg(feature = "tdx")] let tdx_capabilities = if config.tdx { let caps = hypervisor @@ -974,7 +909,6 @@ pub fn configure_system( _num_cpus: u32, setup_header: Option, rsdp_addr: Option, - sgx_epc_region: Option, serial_number: Option<&str>, uuid: Option<&str>, oem_strings: Option<&[&str]>, @@ -1008,15 +942,8 @@ pub fn configure_system( initramfs, hdr, rsdp_addr, - sgx_epc_region, - ), - None => configure_pvh( - guest_mem, - cmdline_addr, - initramfs, - rsdp_addr, - sgx_epc_region, ), + None => configure_pvh(guest_mem, cmdline_addr, initramfs, rsdp_addr), } } @@ -1108,7 +1035,6 @@ fn configure_pvh( cmdline_addr: GuestAddress, initramfs: &Option, rsdp_addr: Option, - sgx_epc_region: Option, ) -> super::Result<()> { const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578; @@ -1174,15 +1100,6 @@ fn configure_pvh( E820_RESERVED, ); - if let Some(sgx_epc_region) = sgx_epc_region { - add_memmap_entry( - &mut memmap, - sgx_epc_region.start().raw_value(), - sgx_epc_region.size(), - E820_RESERVED, - ); - } - start_info.memmap_entries = memmap.len() as u32; // Copy the vector with the memmap table to the MEMMAP_START address @@ -1229,7 +1146,6 @@ fn configure_32bit_entry( initramfs: &Option, setup_hdr: setup_header, rsdp_addr: Option, - sgx_epc_region: Option, ) -> super::Result<()> { const KERNEL_LOADER_OTHER: u8 = 0xff; @@ -1285,15 +1201,6 @@ fn configure_32bit_entry( E820_RESERVED, )?; - if let Some(sgx_epc_region) = sgx_epc_region { - add_e820_entry( - &mut params, - sgx_epc_region.start().raw_value(), - sgx_epc_region.size(), - E820_RESERVED, - )?; - } - if let Some(rsdp_addr) = rsdp_addr { params.acpi_rsdp_addr = rsdp_addr.0; } @@ -1527,57 +1434,6 @@ fn update_cpuid_topology( } } } - -// The goal is to update the CPUID sub-leaves to reflect the number of EPC -// sections exposed to the guest. -fn update_cpuid_sgx( - cpuid: &mut Vec, - epc_sections: &[SgxEpcSection], -) -> Result<(), Error> { - // Something's wrong if there's no EPC section. - if epc_sections.is_empty() { - return Err(Error::NoSgxEpcSection); - } - // We can't go further if the hypervisor does not support SGX feature. - if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::EBX, 2) { - return Err(Error::MissingSgxFeature); - } - // We can't go further if the hypervisor does not support SGX_LC feature. - if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::ECX, 30) { - return Err(Error::MissingSgxLaunchControlFeature); - } - - // Get host CPUID for leaf 0x12, subleaf 0x2. This is to retrieve EPC - // properties such as confidentiality and integrity. - // SAFETY: call cpuid with valid leaves - let leaf = unsafe { std::arch::x86_64::__cpuid_count(0x12, 0x2) }; - - for (i, epc_section) in epc_sections.iter().enumerate() { - let subleaf_idx = i + 2; - let start = epc_section.start().raw_value(); - let size = epc_section.size(); - let eax = (start & 0xffff_f000) as u32 | 0x1; - let ebx = (start >> 32) as u32; - let ecx = (size & 0xffff_f000) as u32 | (leaf.ecx & 0xf); - let edx = (size >> 32) as u32; - // CPU Topology leaf 0x12 - CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, eax); - CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, ebx); - CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, ecx); - CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, edx); - } - - // Add one NULL entry to terminate the dynamic list - let subleaf_idx = epc_sections.len() + 2; - // CPU Topology leaf 0x12 - CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, 0); - CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, 0); - CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, 0); - CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, 0); - - Ok(()) -} - #[cfg(test)] mod tests { use linux_loader::loader::bootparam::boot_e820_entry; @@ -1608,7 +1464,6 @@ mod tests { None, None, None, - None, ); config_err.unwrap_err(); @@ -1633,7 +1488,6 @@ mod tests { None, None, None, - None, ) .unwrap(); @@ -1663,7 +1517,6 @@ mod tests { None, None, None, - None, ) .unwrap(); @@ -1679,7 +1532,6 @@ mod tests { None, None, None, - None, ) .unwrap(); } diff --git a/docs/intel_sgx.md b/docs/intel_sgx.md deleted file mode 100644 index 9f2ca76bdc..0000000000 --- a/docs/intel_sgx.md +++ /dev/null @@ -1,54 +0,0 @@ -# Intel SGX - -Intel® Software Guard Extensions (Intel® SGX) is an Intel technology designed -to increase the security of application code and data. Cloud Hypervisor supports -SGX virtualization through KVM. Because SGX is built on hardware features that -cannot be emulated in software, virtualizing SGX requires support in KVM and in -the host kernel. The required Linux and KVM changes can be found in Linux 5.13+. - -Utilizing SGX in the guest requires a kernel/OS with SGX support, e.g. a kernel -since release 5.11, see -[here](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html). -Running Linux 5.13+ as the guest kernel allows nested virtualization of SGX. - -For more information about SGX, please refer to the [SGX Homepage](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html). - -For more information about SGX SDK and how to test SGX, please refer to the -following [instructions](https://github.com/intel/linux-sgx). - -## Cloud Hypervisor support - -Assuming the host exposes `/dev/sgx_vepc`, we can pass SGX enclaves through -the guest. - -In order to use SGX enclaves within a Cloud Hypervisor VM, we must define one -or several Enclave Page Cache (EPC) sections. Here is an example of a VM being -created with 2 EPC sections, the first one being 64MiB with pre-allocated -memory, the second one being 32MiB with no pre-allocated memory. - -```bash -./cloud-hypervisor \ - --cpus boot=1 \ - --memory size=1G \ - --disk path=focal-server-cloudimg-amd64.raw \ - --kernel vmlinux \ - --cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \ - --sgx-epc id=epc0,size=64M,prefault=on id=epc1,size=32M,prefault=off -``` - -Once booted, and assuming your guest kernel contains the patches from the -[KVM SGX Tree](https://github.com/intel/kvm-sgx), you can validate SGX devices -have been correctly created under `/dev/sgx`: - -```bash -ls /dev/sgx* -/dev/sgx_enclave /dev/sgx_provision /dev/sgx_vepc -``` - -From this point, it is possible to run any SGX application from the guest, as -it will access `/dev/sgx_enclave` device to create dedicated SGX enclaves. - -Note: There is only one contiguous SGX EPC region, which contains all SGX EPC -sections. This region is exposed through ACPI and marked as reserved through -the e820 table. It is treated as yet another device, which means it should -appear at the end of the guest address space. diff --git a/docs/memory.md b/docs/memory.md index 46569449c8..a429ff1b78 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -437,12 +437,11 @@ struct NumaConfig { cpus: Option>, distances: Option>, memory_zones: Option>, - sgx_epc_sections: Option>, } ``` ``` ---numa Settings related to a given NUMA node "guest_numa_id=,cpus=,distances=,memory_zones=,sgx_epc_sections=" +--numa Settings related to a given NUMA node "guest_numa_id=,cpus=,distances=,memory_zones= ``` ### `guest_numa_id` @@ -550,26 +549,6 @@ _Example_ --numa guest_numa_id=0,memory_zones=[mem0,mem2] guest_numa_id=1,memory_zones=mem1 ``` -### `sgx_epc_sections` - -List of SGX EPC sections attached to the guest NUMA node identified by the -`guest_numa_id` option. This allows for describing a list of SGX EPC sections -which must be seen by the guest as belonging to the NUMA node `guest_numa_id`. - -Multiple values can be provided to define the list. Each value is a string -referring to an existing SGX EPC section identifier. Values are separated from -each other with the `,` separator. - -As soon as one tries to describe a list of values, `[` and `]` must be used to -demarcate the list. - -_Example_ - -``` ---sgx-epc id=epc0,size=32M id=epc1,size=64M id=epc2,size=32M ---numa guest_numa_id=0,sgx_epc_sections=epc1 guest_numa_id=1,sgx_epc_sections=[epc0,epc2] -``` - ### PCI bus Cloud Hypervisor supports guests with one or more PCI segments. The default PCI segment always diff --git a/docs/snapshot_restore.md b/docs/snapshot_restore.md index 67f29ce6dc..df7248805e 100644 --- a/docs/snapshot_restore.md +++ b/docs/snapshot_restore.md @@ -110,4 +110,4 @@ from the restored VM. ## Limitations -VFIO devices and Intel SGX are out of scope. +VFIO devices is out of scope. diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index ee8fa52376..e9965ceddf 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -186,8 +186,6 @@ impl RequestHandler for StubApiRequestHandler { #[cfg(feature = "pvmemcontrol")] pvmemcontrol: None, iommu: false, - #[cfg(target_arch = "x86_64")] - sgx_epc: None, numa: None, watchdog: false, gdb: false, diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 4204d4f83d..9aaafd5b07 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -12,11 +12,9 @@ use std::any::Any; use std::collections::HashMap; -#[cfg(target_arch = "x86_64")] -use std::fs::File; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use std::mem::offset_of; -#[cfg(target_arch = "x86_64")] +#[cfg(feature = "tdx")] use std::os::unix::io::AsRawFd; #[cfg(feature = "tdx")] use std::os::unix::io::RawFd; @@ -108,6 +106,8 @@ use kvm_bindings::{kvm_run__bindgen_ty_1, KVMIO}; pub use kvm_ioctls::{Cap, Kvm, VcpuExit}; use thiserror::Error; use vfio_ioctls::VfioDeviceFd; +#[cfg(target_arch = "x86_64")] +use vmm_sys_util::ioctl_io_nr; #[cfg(feature = "tdx")] use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_iowr_nr}; pub use {kvm_bindings, kvm_ioctls}; @@ -116,13 +116,6 @@ pub use {kvm_bindings, kvm_ioctls}; use crate::arch::aarch64::regs; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use crate::RegList; - -#[cfg(target_arch = "x86_64")] -const KVM_CAP_SGX_ATTRIBUTE: u32 = 196; - -#[cfg(target_arch = "x86_64")] -use vmm_sys_util::ioctl_io_nr; - #[cfg(target_arch = "x86_64")] ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a); @@ -893,19 +886,6 @@ impl vm::Vm for KvmVm { Ok(()) } - #[cfg(target_arch = "x86_64")] - fn enable_sgx_attribute(&self, file: File) -> vm::Result<()> { - let mut cap = kvm_enable_cap { - cap: KVM_CAP_SGX_ATTRIBUTE, - ..Default::default() - }; - cap.args[0] = file.as_raw_fd() as u64; - self.fd - .enable_cap(&cap) - .map_err(|e| vm::HypervisorVmError::EnableSgxAttribute(e.into()))?; - Ok(()) - } - /// Retrieve guest clock. #[cfg(target_arch = "x86_64")] fn get_clock(&self) -> vm::Result { diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 8b331cf0a8..93634ada0f 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -41,8 +41,6 @@ pub mod x86_64; // aarch64 dependencies #[cfg(target_arch = "aarch64")] pub mod aarch64; -#[cfg(target_arch = "x86_64")] -use std::fs::File; use std::os::unix::io::AsRawFd; #[cfg(target_arch = "aarch64")] use std::sync::Mutex; @@ -1892,11 +1890,6 @@ impl vm::Vm for MshvVm { Ok(()) } - #[cfg(target_arch = "x86_64")] - fn enable_sgx_attribute(&self, _file: File) -> vm::Result<()> { - Ok(()) - } - fn register_ioevent( &self, fd: &EventFd, diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 113c2001fe..bd9c0e6746 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -11,8 +11,6 @@ // use std::any::Any; -#[cfg(target_arch = "x86_64")] -use std::fs::File; use std::sync::Arc; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use std::sync::Mutex; @@ -130,12 +128,6 @@ pub enum HypervisorVmError { /// #[error("Failed to enable x2apic API")] EnableX2ApicApi(#[source] anyhow::Error), - /// - /// Enable SGX attribute error - /// - #[error("Failed to enable SGX attribute")] - EnableSgxAttribute(#[source] anyhow::Error), - /// /// Get clock error /// #[error("Failed to get clock")] @@ -363,8 +355,6 @@ pub trait Vm: Send + Sync + Any { /// Enable split Irq capability #[cfg(target_arch = "x86_64")] fn enable_split_irq(&self) -> Result<()>; - #[cfg(target_arch = "x86_64")] - fn enable_sgx_attribute(&self, file: File) -> Result<()>; /// Retrieve guest clock. #[cfg(target_arch = "x86_64")] fn get_clock(&self) -> Result; diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 6dea0d7d22..519517dcdb 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -193,7 +193,6 @@ cmd_help() { echo " Run the Cloud Hypervisor tests." echo " --unit Run the unit tests." echo " --integration Run the integration tests." - echo " --integration-sgx Run the SGX integration tests." echo " --integration-vfio Run the VFIO integration tests." echo " --integration-windows Run the Windows guest integration tests." echo " --integration-live-migration Run the live-migration integration tests." @@ -327,7 +326,6 @@ cmd_clean() { cmd_tests() { unit=false integration=false - integration_sgx=false integration_vfio=false integration_windows=false integration_live_migration=false @@ -346,7 +344,6 @@ cmd_tests() { } ;; "--unit") { unit=true; } ;; "--integration") { integration=true; } ;; - "--integration-sgx") { integration_sgx=true; } ;; "--integration-vfio") { integration_vfio=true; } ;; "--integration-windows") { integration_windows=true; } ;; "--integration-live-migration") { integration_live_migration=true; } ;; @@ -449,29 +446,6 @@ cmd_tests() { dbus-run-session ./scripts/run_integration_tests_"$(uname -m)".sh "$@" || fix_dir_perms $? || exit $? fi - if [ "$integration_sgx" = true ]; then - say "Running SGX integration tests for $target..." - $DOCKER_RUNTIME run \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ - --privileged \ - --security-opt seccomp=unconfined \ - --ipc=host \ - --net="$CTR_CLH_NET" \ - --mount type=tmpfs,destination=/tmp \ - --volume /dev:/dev \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ - --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ - --env USER="root" \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ - --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ - "$CTR_IMAGE" \ - ./scripts/run_integration_tests_sgx.sh "$@" || fix_dir_perms $? || exit $? - fi - if [ "$integration_vfio" = true ]; then say "Running VFIO integration tests for $target..." $DOCKER_RUNTIME run \ diff --git a/scripts/run_integration_tests_sgx.sh b/scripts/run_integration_tests_sgx.sh deleted file mode 100755 index b6549b6288..0000000000 --- a/scripts/run_integration_tests_sgx.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -# shellcheck disable=SC2048,SC2086 -set -x - -# shellcheck source=/dev/null -source "$HOME"/.cargo/env -source "$(dirname "$0")"/test-util.sh - -process_common_args "$@" - -if [[ "$hypervisor" = "mshv" ]]; then - echo "Unsupported SGX test for MSHV" - exit 1 -fi - -WORKLOADS_DIR="$HOME/workloads" -mkdir -p "$WORKLOADS_DIR" - -download_hypervisor_fw - -JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2" -JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME" -JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME" -if [ ! -f "$JAMMY_OS_IMAGE" ]; then - pushd "$WORKLOADS_DIR" || exit - time wget --quiet $JAMMY_OS_IMAGE_URL || exit 1 - popd || exit -fi - -JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.raw" -JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME" -if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then - pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -p -f qcow2 -O raw $JAMMY_OS_IMAGE_NAME $JAMMY_OS_RAW_IMAGE_NAME || exit 1 - popd || exit -fi - -CFLAGS="" -if [[ "${BUILD_TARGET}" == "x86_64-unknown-linux-musl" ]]; then - # shellcheck disable=SC2034 - CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/" -fi - -cargo build --features mshv --all --release --target "$BUILD_TARGET" - -export RUST_BACKTRACE=1 - -time cargo test "sgx::$test_filter" -- ${test_binary_args[*]} -RES=$? - -exit $RES diff --git a/src/main.rs b/src/main.rs index 6daac338f7..8329100b6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,8 +31,6 @@ use vmm::vm_config; use vmm::vm_config::FwCfgConfig; #[cfg(feature = "ivshmem")] use vmm::vm_config::IvshmemConfig; -#[cfg(target_arch = "x86_64")] -use vmm::vm_config::SgxEpcConfig; use vmm::vm_config::{ BalloonConfig, DeviceConfig, DiskConfig, FsConfig, LandlockConfig, NetConfig, NumaConfig, PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, TpmConfig, UserDeviceConfig, VdpaConfig, @@ -429,12 +427,6 @@ fn get_cli_options_sorted( .help("Control serial port: off|null|pty|tty|file=|socket=") .default_value("null") .group("vm-config"), - #[cfg(target_arch = "x86_64")] - Arg::new("sgx-epc") - .long("sgx-epc") - .help(SgxEpcConfig::SYNTAX) - .num_args(1..) - .group("vm-config"), Arg::new("tpm") .long("tpm") .num_args(1) @@ -1030,8 +1022,6 @@ mod unit_tests { #[cfg(feature = "pvmemcontrol")] pvmemcontrol: None, iommu: false, - #[cfg(target_arch = "x86_64")] - sgx_epc: None, numa: None, watchdog: false, #[cfg(feature = "guest_debug")] diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 6875aa5b22..812a3a9339 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1061,24 +1061,6 @@ impl Guest { } } - #[cfg(target_arch = "x86_64")] - pub fn check_sgx_support(&self) -> Result<(), Error> { - self.ssh_command( - "cpuid -l 0x7 -s 0 | tr -s [:space:] | grep -q 'SGX: \ - Software Guard Extensions supported = true'", - )?; - self.ssh_command( - "cpuid -l 0x7 -s 0 | tr -s [:space:] | grep -q 'SGX_LC: \ - SGX launch config supported = true'", - )?; - self.ssh_command( - "cpuid -l 0x12 -s 0 | tr -s [:space:] | grep -q 'SGX1 \ - supported = true'", - )?; - - Ok(()) - } - pub fn get_pci_bridge_class(&self) -> Result { Ok(self .ssh_command("cat /sys/bus/pci/devices/0000:00:00.0/class")? diff --git a/tests/integration.rs b/tests/integration.rs index 4e74cf0fc8..94f79dac6d 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -9530,50 +9530,6 @@ mod windows { } } -#[cfg(target_arch = "x86_64")] -mod sgx { - use crate::*; - - #[test] - fn test_sgx() { - let jammy_image = JAMMY_IMAGE_NAME.to_string(); - let jammy = UbuntuDiskConfig::new(jammy_image); - let guest = Guest::new(Box::new(jammy)); - - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) - .args(["--memory", "size=512M"]) - .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) - .default_disks() - .default_net() - .args(["--sgx-epc", "id=epc0,size=64M"]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); - - // Check if SGX is correctly detected in the guest. - guest.check_sgx_support().unwrap(); - - // Validate the SGX EPC section is 64MiB. - assert_eq!( - guest - .ssh_command("cpuid -l 0x12 -s 2 | grep 'section size' | cut -d '=' -f 2") - .unwrap() - .trim(), - "0x0000000004000000" - ); - }); - - let _ = child.kill(); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - } -} - #[cfg(target_arch = "x86_64")] mod vfio { use crate::*; diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index a2299acd84..215deac912 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -314,16 +314,6 @@ fn create_srat_table( )) } - #[cfg(target_arch = "x86_64")] - for section in &node.sgx_epc_sections { - srat.append(MemoryAffinity::from_range( - section.start().raw_value(), - section.size(), - proximity_domain, - MemAffinityFlags::ENABLE, - )) - } - for cpu in &node.cpus { #[cfg(target_arch = "x86_64")] let x2apic_id = arch::x86_64::get_x2apic_id(*cpu, topology); diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 80a4fa2572..e4a76f6b74 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -607,10 +607,6 @@ components: $ref: "#/components/schemas/VdpaConfig" vsock: $ref: "#/components/schemas/VsockConfig" - sgx_epc: - type: array - items: - $ref: "#/components/schemas/SgxEpcConfig" numa: type: array items: @@ -1143,21 +1139,6 @@ components: id: type: string - SgxEpcConfig: - required: - - id - - size - type: object - properties: - id: - type: string - size: - type: integer - format: int64 - prefault: - type: boolean - default: false - NumaDistance: required: - destination @@ -1192,10 +1173,6 @@ components: type: array items: type: string - sgx_epc_sections: - type: array - items: - type: string pci_segments: type: array items: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 06523761ad..29a2644b74 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -109,14 +109,6 @@ pub enum Error { /// Failed parsing restore parameters #[error("Error parsing --restore")] ParseRestore(#[source] OptionParserError), - /// Failed parsing SGX EPC parameters - #[cfg(target_arch = "x86_64")] - #[error("Error parsing --sgx-epc")] - ParseSgxEpc(#[source] OptionParserError), - /// Missing 'id' from SGX EPC section - #[cfg(target_arch = "x86_64")] - #[error("Error parsing --sgx-epc: id missing")] - ParseSgxEpcIdMissing, /// Failed parsing NUMA parameters #[error("Error parsing --numa")] ParseNuma(#[source] OptionParserError), @@ -395,8 +387,6 @@ pub struct VmParams<'a> { #[cfg(feature = "pvmemcontrol")] pub pvmemcontrol: bool, pub pvpanic: bool, - #[cfg(target_arch = "x86_64")] - pub sgx_epc: Option>, pub numa: Option>, pub watchdog: bool, #[cfg(feature = "guest_debug")] @@ -462,10 +452,6 @@ impl<'a> VmParams<'a> { #[cfg(feature = "pvmemcontrol")] let pvmemcontrol = args.get_flag("pvmemcontrol"); let pvpanic = args.get_flag("pvpanic"); - #[cfg(target_arch = "x86_64")] - let sgx_epc: Option> = args - .get_many::("sgx-epc") - .map(|x| x.map(|y| y as &str).collect()); let numa: Option> = args .get_many::("numa") .map(|x| x.map(|y| y as &str).collect()); @@ -516,8 +502,6 @@ impl<'a> VmParams<'a> { #[cfg(feature = "pvmemcontrol")] pvmemcontrol, pvpanic, - #[cfg(target_arch = "x86_64")] - sgx_epc, numa, watchdog, #[cfg(feature = "guest_debug")] @@ -2139,36 +2123,10 @@ impl VsockConfig { } } -#[cfg(target_arch = "x86_64")] -impl SgxEpcConfig { - pub const SYNTAX: &'static str = "SGX EPC parameters \ - \"id=,size=,prefault=on|off\""; - - pub fn parse(sgx_epc: &str) -> Result { - let mut parser = OptionParser::new(); - parser.add("id").add("size").add("prefault"); - parser.parse(sgx_epc).map_err(Error::ParseSgxEpc)?; - - let id = parser.get("id").ok_or(Error::ParseSgxEpcIdMissing)?; - let size = parser - .convert::("size") - .map_err(Error::ParseSgxEpc)? - .unwrap_or(ByteSized(0)) - .0; - let prefault = parser - .convert::("prefault") - .map_err(Error::ParseSgxEpc)? - .unwrap_or(Toggle(false)) - .0; - - Ok(SgxEpcConfig { id, size, prefault }) - } -} - impl NumaConfig { pub const SYNTAX: &'static str = "Settings related to a given NUMA node \ \"guest_numa_id=,cpus=,distances=,\ - memory_zones=,sgx_epc_sections=,\ + memory_zones=,\ pci_segments=\""; pub fn parse(numa: &str) -> Result { @@ -2178,7 +2136,6 @@ impl NumaConfig { .add("cpus") .add("distances") .add("memory_zones") - .add("sgx_epc_sections") .add("pci_segments"); parser.parse(numa).map_err(Error::ParseNuma)?; @@ -2206,11 +2163,6 @@ impl NumaConfig { .convert::("memory_zones") .map_err(Error::ParseNuma)? .map(|v| v.0); - #[cfg(target_arch = "x86_64")] - let sgx_epc_sections = parser - .convert::("sgx_epc_sections") - .map_err(Error::ParseNuma)? - .map(|v| v.0); let pci_segments = parser .convert::("pci_segments") .map_err(Error::ParseNuma)? @@ -2220,8 +2172,6 @@ impl NumaConfig { cpus, distances, memory_zones, - #[cfg(target_arch = "x86_64")] - sgx_epc_sections, pci_segments, }) } @@ -2800,14 +2750,6 @@ impl VmConfig { } } - #[cfg(target_arch = "x86_64")] - if let Some(sgx_epcs) = &self.sgx_epc { - for sgx_epc in sgx_epcs.iter() { - let id = sgx_epc.id.clone(); - Self::validate_identifier(&mut id_list, &Some(id))?; - } - } - if let Some(pci_segments) = &self.pci_segments { for pci_segment in pci_segments { pci_segment.validate(self)?; @@ -2957,21 +2899,6 @@ impl VmConfig { let platform = vm_params.platform.map(PlatformConfig::parse).transpose()?; - #[cfg(target_arch = "x86_64")] - let mut sgx_epc: Option> = None; - #[cfg(target_arch = "x86_64")] - { - if let Some(sgx_epc_list) = &vm_params.sgx_epc { - warn!("SGX support is deprecated and will be removed in a future release."); - let mut sgx_epc_config_list = Vec::new(); - for item in sgx_epc_list.iter() { - let sgx_epc_config = SgxEpcConfig::parse(item)?; - sgx_epc_config_list.push(sgx_epc_config); - } - sgx_epc = Some(sgx_epc_config_list); - } - } - let mut numa: Option> = None; if let Some(numa_list) = &vm_params.numa { let mut numa_config_list = Vec::new(); @@ -3058,8 +2985,6 @@ impl VmConfig { pvmemcontrol, pvpanic: vm_params.pvpanic, iommu: false, // updated in VmConfig::validate() - #[cfg(target_arch = "x86_64")] - sgx_epc, numa, watchdog: vm_params.watchdog, #[cfg(feature = "guest_debug")] @@ -3189,8 +3114,6 @@ impl Clone for VmConfig { user_devices: self.user_devices.clone(), vdpa: self.vdpa.clone(), vsock: self.vsock.clone(), - #[cfg(target_arch = "x86_64")] - sgx_epc: self.sgx_epc.clone(), numa: self.numa.clone(), pci_segments: self.pci_segments.clone(), platform: self.platform.clone(), @@ -3976,8 +3899,6 @@ mod tests { pvmemcontrol: None, pvpanic: false, iommu: false, - #[cfg(target_arch = "x86_64")] - sgx_epc: None, numa: None, watchdog: false, #[cfg(feature = "guest_debug")] @@ -4119,8 +4040,6 @@ mod tests { cpus: None, distances: None, memory_zones: None, - #[cfg(target_arch = "x86_64")] - sgx_epc_sections: None, pci_segments: None, } } @@ -4192,8 +4111,6 @@ mod tests { pvmemcontrol: None, pvpanic: false, iommu: false, - #[cfg(target_arch = "x86_64")] - sgx_epc: None, numa: None, watchdog: false, #[cfg(feature = "guest_debug")] diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 0f4ac18cc8..7f542015ed 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -82,8 +82,6 @@ use crate::coredump::{ }; #[cfg(feature = "guest_debug")] use crate::gdb::{get_raw_tid, Debuggable, DebuggableError}; -#[cfg(target_arch = "x86_64")] -use crate::memory_manager::MemoryManager; use crate::seccomp_filters::{get_seccomp_filter, Thread}; #[cfg(target_arch = "x86_64")] use crate::vm::physical_bits; @@ -799,23 +797,14 @@ impl CpuManager { #[cfg(target_arch = "x86_64")] pub fn populate_cpuid( &mut self, - memory_manager: &Arc>, hypervisor: &Arc, #[cfg(feature = "tdx")] tdx: bool, ) -> Result<()> { - let sgx_epc_sections = memory_manager - .lock() - .unwrap() - .sgx_epc_region() - .as_ref() - .map(|sgx_epc_region| sgx_epc_region.epc_sections().values().cloned().collect()); - self.cpuid = { let phys_bits = physical_bits(hypervisor, self.config.max_phys_bits); arch::generate_common_cpuid( hypervisor, &arch::CpuidConfig { - sgx_epc_sections, phys_bits, kvm_hyperv: self.config.kvm_hyperv, #[cfg(feature = "tdx")] diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index dddfe9bd33..0378b7b18a 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -900,8 +900,6 @@ impl Vmm { false, Some(&vm_migration_config.memory_manager_data), existing_memory_files, - #[cfg(target_arch = "x86_64")] - None, ) .map_err(|e| { MigratableError::MigrateReceive(anyhow!( @@ -1135,7 +1133,6 @@ impl Vmm { arch::generate_common_cpuid( &hypervisor, &arch::CpuidConfig { - sgx_epc_sections: None, phys_bits, kvm_hyperv: vm_config.lock().unwrap().cpus.kvm_hyperv, #[cfg(feature = "tdx")] @@ -1266,7 +1263,7 @@ impl Vmm { }; // We check the `CPUID` compatibility of between the source vm and destination, which is - // mostly about feature compatibility and "topology/sgx" leaves are not relevant. + // mostly about feature compatibility. let dest_cpuid = &{ let vm_config = &src_vm_config.lock().unwrap(); @@ -1274,7 +1271,6 @@ impl Vmm { arch::generate_common_cpuid( &self.hypervisor.clone(), &arch::CpuidConfig { - sgx_epc_sections: None, phys_bits, kvm_hyperv: vm_config.cpus.kvm_hyperv, #[cfg(feature = "tdx")] @@ -2428,8 +2424,6 @@ mod unit_tests { pvmemcontrol: None, pvpanic: false, iommu: false, - #[cfg(target_arch = "x86_64")] - sgx_epc: None, numa: None, watchdog: false, #[cfg(feature = "guest_debug")] diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index cdc5810445..461eb24b9a 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -19,16 +19,12 @@ use std::{ffi, result, thread}; use acpi_tables::{aml, Aml}; use anyhow::anyhow; -#[cfg(target_arch = "x86_64")] -use arch::x86_64::{SgxEpcRegion, SgxEpcSection}; use arch::RegionType; #[cfg(target_arch = "x86_64")] use devices::ioapic; #[cfg(target_arch = "aarch64")] use hypervisor::HypervisorVmError; use libc::_SC_NPROCESSORS_ONLN; -#[cfg(target_arch = "x86_64")] -use libc::{MAP_NORESERVE, MAP_POPULATE, MAP_SHARED, PROT_READ, PROT_WRITE}; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracer::trace_scoped; @@ -54,8 +50,6 @@ use crate::coredump::{ CoredumpMemoryRegion, CoredumpMemoryRegions, DumpState, GuestDebuggableError, }; use crate::migration::url_to_path; -#[cfg(target_arch = "x86_64")] -use crate::vm_config::SgxEpcConfig; use crate::vm_config::{HotplugMethod, MemoryConfig, MemoryZoneConfig}; use crate::{GuestMemoryMmap, GuestRegionMmap, MEMORY_MANAGER_SNAPSHOT_ID}; @@ -68,9 +62,6 @@ const SNAPSHOT_FILENAME: &str = "memory-ranges"; #[cfg(target_arch = "x86_64")] const X86_64_IRQ_BASE: u32 = 5; -#[cfg(target_arch = "x86_64")] -const SGX_PAGE_SIZE: u64 = 1 << 12; - const HOTPLUG_COUNT: usize = 8; // Memory policy constants @@ -183,8 +174,6 @@ pub struct MemoryManager { hugepage_size: Option, prefault: bool, thp: bool, - #[cfg(target_arch = "x86_64")] - sgx_epc_region: Option, user_provided_zones: bool, snapshot_memory_ranges: MemoryRangeTable, memory_zones: MemoryZones, @@ -269,36 +258,6 @@ pub enum Error { #[error("Cannot create the system allocator")] CreateSystemAllocator, - /// Invalid SGX EPC section size - #[cfg(target_arch = "x86_64")] - #[error("Invalid SGX EPC section size")] - EpcSectionSizeInvalid, - - /// Failed allocating SGX EPC region - #[cfg(target_arch = "x86_64")] - #[error("Failed allocating SGX EPC region")] - SgxEpcRangeAllocation, - - /// Failed opening SGX virtual EPC device - #[cfg(target_arch = "x86_64")] - #[error("Failed opening SGX virtual EPC device")] - SgxVirtEpcOpen(#[source] io::Error), - - /// Failed setting the SGX virtual EPC section size - #[cfg(target_arch = "x86_64")] - #[error("Failed setting the SGX virtual EPC section size")] - SgxVirtEpcFileSetLen(#[source] io::Error), - - /// Failed opening SGX provisioning device - #[cfg(target_arch = "x86_64")] - #[error("Failed opening SGX provisioning device")] - SgxProvisionOpen(#[source] io::Error), - - /// Failed enabling SGX provisioning - #[cfg(target_arch = "x86_64")] - #[error("Failed enabling SGX provisioning")] - SgxEnableProvisioning(#[source] hypervisor::HypervisorVmError), - /// Failed creating a new MmapRegion instance. #[cfg(target_arch = "x86_64")] #[error("Failed creating a new MmapRegion instance")] @@ -1034,7 +993,6 @@ impl MemoryManager { #[cfg(feature = "tdx")] tdx_enabled: bool, restore_data: Option<&MemoryManagerSnapshotData>, existing_memory_files: Option>, - #[cfg(target_arch = "x86_64")] sgx_epc_config: Option>, ) -> Result>, Error> { trace_scoped!("MemoryManager::new"); @@ -1236,8 +1194,7 @@ impl MemoryManager { None }; - // If running on SGX the start of device area and RAM area may diverge but - // at this point they are next to each other. + // The start of device area and RAM area are placed next to each other. let end_of_ram_area = start_of_device_area.unchecked_sub(1); let ram_allocator = AddressAllocator::new(GuestAddress(0), start_of_device_area.0).unwrap(); @@ -1263,8 +1220,6 @@ impl MemoryManager { hugepages: config.hugepages, hugepage_size: config.hugepage_size, prefault: config.prefault, - #[cfg(target_arch = "x86_64")] - sgx_epc_region: None, user_provided_zones, snapshot_memory_ranges: MemoryRangeTable::default(), memory_zones, @@ -1279,11 +1234,6 @@ impl MemoryManager { thp: config.thp, }; - #[cfg(target_arch = "x86_64")] - if let Some(sgx_epc_config) = sgx_epc_config { - memory_manager.setup_sgx(sgx_epc_config)?; - } - Ok(Arc::new(Mutex::new(memory_manager))) } @@ -1311,8 +1261,6 @@ impl MemoryManager { false, Some(&mem_snapshot), None, - #[cfg(target_arch = "x86_64")] - None, )?; mm.lock() @@ -1976,121 +1924,6 @@ impl MemoryManager { self.virtio_mem_resize(id, virtio_mem_size) } - #[cfg(target_arch = "x86_64")] - pub fn setup_sgx(&mut self, sgx_epc_config: Vec) -> Result<(), Error> { - let file = OpenOptions::new() - .read(true) - .open("/dev/sgx_provision") - .map_err(Error::SgxProvisionOpen)?; - self.vm - .enable_sgx_attribute(file) - .map_err(Error::SgxEnableProvisioning)?; - - // Go over each EPC section and verify its size is a 4k multiple. At - // the same time, calculate the total size needed for the contiguous - // EPC region. - let mut epc_region_size = 0; - for epc_section in sgx_epc_config.iter() { - if epc_section.size == 0 { - return Err(Error::EpcSectionSizeInvalid); - } - if epc_section.size & (SGX_PAGE_SIZE - 1) != 0 { - return Err(Error::EpcSectionSizeInvalid); - } - - epc_region_size += epc_section.size; - } - - // Place the SGX EPC region on a 4k boundary between the RAM and the device area - let epc_region_start = - GuestAddress(self.start_of_device_area.0.div_ceil(SGX_PAGE_SIZE) * SGX_PAGE_SIZE); - - self.start_of_device_area = epc_region_start - .checked_add(epc_region_size) - .ok_or(Error::GuestAddressOverFlow)?; - - let mut sgx_epc_region = SgxEpcRegion::new(epc_region_start, epc_region_size as GuestUsize); - info!( - "SGX EPC region: 0x{:x} (0x{:x})", - epc_region_start.0, epc_region_size - ); - - // Each section can be memory mapped into the allocated region. - let mut epc_section_start = epc_region_start.raw_value(); - for epc_section in sgx_epc_config.iter() { - let file = OpenOptions::new() - .read(true) - .write(true) - .open("/dev/sgx_vepc") - .map_err(Error::SgxVirtEpcOpen)?; - - let prot = PROT_READ | PROT_WRITE; - let mut flags = MAP_NORESERVE | MAP_SHARED; - if epc_section.prefault { - flags |= MAP_POPULATE; - } - - // We can't use the vm-memory crate to perform the memory mapping - // here as it would try to ensure the size of the backing file is - // matching the size of the expected mapping. The /dev/sgx_vepc - // device does not work that way, it provides a file descriptor - // which is not matching the mapping size, as it's a just a way to - // let KVM know that an EPC section is being created for the guest. - // SAFETY: FFI call with correct arguments - let host_addr = unsafe { - libc::mmap( - std::ptr::null_mut(), - epc_section.size as usize, - prot, - flags, - file.as_raw_fd(), - 0, - ) - }; - - if host_addr == libc::MAP_FAILED { - error!( - "Could not add SGX EPC section (size 0x{:x})", - epc_section.size - ); - return Err(Error::SgxEpcRangeAllocation); - } - - info!( - "Adding SGX EPC section: 0x{:x} (0x{:x})", - epc_section_start, epc_section.size - ); - - let _mem_slot = self.create_userspace_mapping( - epc_section_start, - epc_section.size, - host_addr as u64, - false, - false, - false, - )?; - - sgx_epc_region.insert( - epc_section.id.clone(), - SgxEpcSection::new( - GuestAddress(epc_section_start), - epc_section.size as GuestUsize, - ), - ); - - epc_section_start += epc_section.size; - } - - self.sgx_epc_region = Some(sgx_epc_region); - - Ok(()) - } - - #[cfg(target_arch = "x86_64")] - pub fn sgx_epc_region(&self) -> &Option { - &self.sgx_epc_region - } - pub fn is_hardlink(f: &File) -> bool { let mut stat = std::mem::MaybeUninit::::uninit(); // SAFETY: FFI call with correct arguments @@ -2642,34 +2475,6 @@ impl Aml for MemoryManager { ) .to_aml_bytes(sink); } - - #[cfg(target_arch = "x86_64")] - { - if let Some(sgx_epc_region) = &self.sgx_epc_region { - let min = sgx_epc_region.start().raw_value(); - let max = min + sgx_epc_region.size() - 1; - // SGX EPC region - aml::Device::new( - "_SB_.EPC_".into(), - vec![ - &aml::Name::new("_HID".into(), &aml::EISAName::new("INT0E0C")), - // QWORD describing the EPC region start and size - &aml::Name::new( - "_CRS".into(), - &aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory( - aml::AddressSpaceCacheable::NotCacheable, - true, - min, - max, - None, - )]), - ), - &aml::Method::new("_STA".into(), 0, false, vec![&aml::Return::new(&0xfu8)]), - ], - ) - .to_aml_bytes(sink); - } - } } } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index af1ddaaa46..3092c32367 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -606,7 +606,6 @@ impl Vm { .lock() .unwrap() .populate_cpuid( - &memory_manager, &hypervisor, #[cfg(feature = "tdx")] tdx_enabled, @@ -971,24 +970,6 @@ impl Vm { } } - #[cfg(target_arch = "x86_64")] - if let Some(sgx_epc_sections) = &config.sgx_epc_sections { - if let Some(sgx_epc_region) = mm.sgx_epc_region() { - let mm_sections = sgx_epc_region.epc_sections(); - for sgx_epc_section in sgx_epc_sections.iter() { - if let Some(mm_section) = mm_sections.get(sgx_epc_section) { - node.sgx_epc_sections.push(mm_section.clone()); - } else { - error!("Unknown SGX EPC section '{}'", sgx_epc_section); - return Err(Error::InvalidNumaConfig); - } - } - } else { - error!("Missing SGX EPC region"); - return Err(Error::InvalidNumaConfig); - } - } - numa_nodes.insert(config.guest_numa_id, node); } } @@ -1056,9 +1037,6 @@ impl Vm { ) .map_err(Error::MemoryManager)? } else { - #[cfg(target_arch = "x86_64")] - let sgx_epc_config = vm_config.lock().unwrap().sgx_epc.clone(); - MemoryManager::new( vm.clone(), &vm_config.lock().unwrap().memory.clone(), @@ -1068,8 +1046,6 @@ impl Vm { tdx_enabled, None, None, - #[cfg(target_arch = "x86_64")] - sgx_epc_config, ) .map_err(Error::MemoryManager)? }; @@ -1420,13 +1396,6 @@ impl Vm { let boot_vcpus = self.cpu_manager.lock().unwrap().boot_vcpus(); let rsdp_addr = Some(rsdp_addr); - let sgx_epc_region = self - .memory_manager - .lock() - .unwrap() - .sgx_epc_region() - .as_ref() - .cloned(); let serial_number = self .config @@ -1466,7 +1435,6 @@ impl Vm { boot_vcpus, entry_addr.setup_header, rsdp_addr, - sgx_epc_region, serial_number.as_deref(), uuid.as_deref(), oem_strings.as_deref(), @@ -2917,7 +2885,6 @@ impl Snapshottable for Vm { arch::generate_common_cpuid( &self.hypervisor, &arch::CpuidConfig { - sgx_epc_sections: None, phys_bits, kvm_hyperv: self.config.lock().unwrap().cpus.kvm_hyperv, #[cfg(feature = "tdx")] diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index cf1f61e05c..9c149d05f1 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -671,16 +671,6 @@ impl Default for IvshmemConfig { } } -#[cfg(target_arch = "x86_64")] -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct SgxEpcConfig { - pub id: String, - #[serde(default)] - pub size: u64, - #[serde(default)] - pub prefault: bool, -} - #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct NumaDistance { #[serde(default)] @@ -699,9 +689,6 @@ pub struct NumaConfig { pub distances: Option>, #[serde(default)] pub memory_zones: Option>, - #[cfg(target_arch = "x86_64")] - #[serde(default)] - pub sgx_epc_sections: Option>, #[serde(default)] pub pci_segments: Option>, } @@ -941,8 +928,6 @@ pub struct VmConfig { pub pvpanic: bool, #[serde(default)] pub iommu: bool, - #[cfg(target_arch = "x86_64")] - pub sgx_epc: Option>, pub numa: Option>, #[serde(default)] pub watchdog: bool, From 706b56e97d0631816490d3cdd502dce7ccecb45a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 23:01:57 +0000 Subject: [PATCH 0152/1893] build: Bump async-trait from 0.1.88 to 0.1.89 Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.88 to 0.1.89. - [Release notes](https://github.com/dtolnay/async-trait/releases) - [Commits](https://github.com/dtolnay/async-trait/compare/0.1.88...0.1.89) --- updated-dependencies: - dependency-name: async-trait dependency-version: 0.1.89 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f985a18180..0b896638f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,9 +243,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", From 3f5c7197057d92560097b7b6c3e39523c189528c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 23:18:58 +0000 Subject: [PATCH 0153/1893] build: Bump crate-ci/typos from 1.36.1 to 1.36.2 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.36.1 to 1.36.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.36.1...v1.36.2) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.36.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 24ee35990c..f767909a27 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.36.1 + - uses: crate-ci/typos@v1.36.2 From 47cff7c37fe9ed1dbb232d749e64280b9c031029 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Sat, 6 Sep 2025 13:01:23 +0200 Subject: [PATCH 0154/1893] vmm: allow TCGETS2/TCSETS2 where TCGETS/TCSETS are These are now used by Cloud Hypervisor when linked with Glibc 2.42. These values should be correct for all currently supported Cloud Hypervisor platforms, although they are not for all Linux platforms. Closes: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7276 Signed-off-by: Alyssa Ross --- vmm/src/seccomp_filters.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 1f0a6a47e4..46e38f9c8e 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -42,7 +42,9 @@ macro_rules! or { // See include/uapi/asm-generic/ioctls.h in the kernel code. const TCGETS: u64 = 0x5401; +const TCGETS2: u64 = 0x802c_542a; const TCSETS: u64 = 0x5402; +const TCSETS2: u64 = 0x402c_542b; const TIOCSCTTY: u64 = 0x540E; const TIOCGPGRP: u64 = 0x540F; const TIOCSPGRP: u64 = 0x5410; @@ -311,7 +313,9 @@ fn create_vmm_ioctl_seccomp_rule_common( and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFMTU)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFNETMASK)?], and![Cond::new(1, ArgLen::Dword, Eq, TCSETS)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCSETS2)?], and![Cond::new(1, ArgLen::Dword, Eq, TCGETS)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCGETS2)?], and![Cond::new(1, ArgLen::Dword, Eq, TIOCGPGRP)?], and![Cond::new(1, ArgLen::Dword, Eq, TIOCGPTPEER)?], and![Cond::new(1, ArgLen::Dword, Eq, TIOCGWINSZ)?], @@ -487,7 +491,9 @@ fn create_api_ioctl_seccomp_rule() -> Result, BackendError> { fn create_signal_handler_ioctl_seccomp_rule() -> Result, BackendError> { Ok(or![ and![Cond::new(1, ArgLen::Dword, Eq, TCGETS)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCGETS2)?], and![Cond::new(1, ArgLen::Dword, Eq, TCSETS)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCSETS2)?], and![Cond::new(1, ArgLen::Dword, Eq, TIOCGWINSZ)?], ]) } From 57bc78da4f185efab0bb580f74c824de1855c46e Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Fri, 5 Sep 2025 20:32:36 +0000 Subject: [PATCH 0155/1893] arch: x86_64: make MAX_SUPPORTED_CPUS_LEGACY public Signed-off-by: Peter Oskolkov --- arch/src/x86_64/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 22d1a1cfea..83cb0876c0 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -34,7 +34,7 @@ pub mod tdx; // While modern architectures support more than 255 CPUs via x2APIC, // legacy devices such as mptable support at most 254 CPUs. -pub(crate) const MAX_SUPPORTED_CPUS_LEGACY: u32 = 254; +pub const MAX_SUPPORTED_CPUS_LEGACY: u32 = 254; // CPUID feature bits #[cfg(feature = "kvm")] From 05d222f0eb55c0a7ad417e96eb7f791a2989c0eb Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Fri, 5 Sep 2025 20:36:25 +0000 Subject: [PATCH 0156/1893] vmm: raise the (v)CPU limit on kvm/x86_64 Raise the max number of supported (v)CPUs on kvm x86_64 hosts to 8192 (the max allowed value of CONFIG_NR_CPUS in the Linux kernel). Other platfroms keep their existing CPU limits pending further development and testing. The change has been tested on Intel and AMD hosts. Signed-off-by: Barret Rhoden Signed-off-by: Neel Natu Signed-off-by: Ofir Weisse Signed-off-by: Peter Oskolkov --- vmm/src/config.rs | 30 +++++++++++++++++---- vmm/src/cpu.rs | 56 +++++++++++++++++++++------------------ vmm/src/device_manager.rs | 2 +- vmm/src/lib.rs | 9 ++++++- vmm/src/vm.rs | 13 ++++++--- vmm/src/vm_config.rs | 32 +++++++++++++++------- 6 files changed, 97 insertions(+), 45 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 29a2644b74..b66c71b8c6 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -27,6 +27,11 @@ use crate::vm_config::*; const MAX_NUM_PCI_SEGMENTS: u16 = 96; const MAX_IOMMU_ADDRESS_WIDTH_BITS: u8 = 64; +#[cfg(all(feature = "kvm", target_arch = "x86_64"))] +const MAX_SUPPORTED_CPUS: u32 = 8192; +#[cfg(not(all(feature = "kvm", target_arch = "x86_64")))] +const MAX_SUPPORTED_CPUS: u32 = 255; + /// Errors associated with VM configuration parameters. #[derive(Debug, Error)] pub enum Error { @@ -182,6 +187,9 @@ pub enum ValidationError { /// Max is less than boot #[error("Max CPUs lower than boot CPUs")] CpusMaxLowerThanBoot, + /// Too many CPUs. + #[error("Too many CPUs: specified {0} but {MAX_SUPPORTED_CPUS} is the limit")] + TooManyCpus(u32 /* specified CPUs */), /// Missing file value for debug-console #[cfg(target_arch = "x86_64")] #[error("Path missing when using file mode for debug console")] @@ -586,11 +594,11 @@ impl CpusConfig { .add("features"); parser.parse(cpus).map_err(Error::ParseCpus)?; - let boot_vcpus: u8 = parser + let boot_vcpus: u32 = parser .convert("boot") .map_err(Error::ParseCpus)? .unwrap_or(DEFAULT_VCPUS); - let max_vcpus: u8 = parser + let max_vcpus: u32 = parser .convert("max") .map_err(Error::ParseCpus)? .unwrap_or(boot_vcpus); @@ -605,7 +613,7 @@ impl CpusConfig { .map_err(Error::ParseCpus)? .unwrap_or(DEFAULT_MAX_PHYS_BITS); let affinity = parser - .convert::>>("affinity") + .convert::>>("affinity") .map_err(Error::ParseCpus)? .map(|v| { v.0.iter() @@ -2147,7 +2155,7 @@ impl NumaConfig { let cpus = parser .convert::("cpus") .map_err(Error::ParseNuma)? - .map(|v| v.0.iter().map(|e| *e as u8).collect()); + .map(|v| v.0.iter().map(|e| *e as u32).collect()); let distances = parser .convert::>("distances") .map_err(Error::ParseNuma)? @@ -2523,6 +2531,15 @@ impl VmConfig { return Err(ValidationError::CpusMaxLowerThanBoot); } + if self.cpus.max_vcpus > MAX_SUPPORTED_CPUS { + // Note: historically, Cloud Hypervisor did not support more than 255(254 on x64) + // vCPUs: self.cpus.max_vcpus was of type u8, so 255 was the maximum; + // on x86_64, the legacy mptable/apic was limited to 254 CPUs. + // + // Now the limit is lifted on x86_64 targets. Other targests/archs: TBD. + return Err(ValidationError::TooManyCpus(self.cpus.max_vcpus)); + } + if let Some(rate_limit_groups) = &self.rate_limit_groups { for rate_limit_group in rate_limit_groups { rate_limit_group.validate(self)?; @@ -2614,7 +2631,10 @@ impl VmConfig { return Err(ValidationError::CpuTopologyDiesPerPackage); } - let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages; + let total: u32 = (t.threads_per_core as u32) + * (t.cores_per_die as u32) + * (t.dies_per_package as u32) + * (t.packages as u32); if total != self.cpus.max_vcpus { return Err(ValidationError::CpuTopologyCount); } diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 7f542015ed..00d2468f39 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -196,8 +196,8 @@ pub enum Error { #[error("Error setting up AMX")] AmxEnable(#[source] anyhow::Error), - #[error("Maximum number of vCPUs exceeds host limit")] - MaximumVcpusExceeded, + #[error("Maximum number of vCPUs {0} exceeds host limit {1}")] + MaximumVcpusExceeded(u32, u32), #[cfg(feature = "sev_snp")] #[error("Failed to set sev control register")] @@ -698,12 +698,16 @@ impl CpuManager { numa_nodes: &NumaNodes, #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, ) -> Result>> { - if u32::from(config.max_vcpus) > hypervisor.get_max_vcpus() { - return Err(Error::MaximumVcpusExceeded); + if config.max_vcpus > hypervisor.get_max_vcpus() { + return Err(Error::MaximumVcpusExceeded( + config.max_vcpus, + hypervisor.get_max_vcpus(), + )); } - let mut vcpu_states = Vec::with_capacity(usize::from(config.max_vcpus)); - vcpu_states.resize_with(usize::from(config.max_vcpus), VcpuState::default); + let max_vcpus = usize::try_from(config.max_vcpus).unwrap(); + let mut vcpu_states = Vec::with_capacity(max_vcpus); + vcpu_states.resize_with(max_vcpus, VcpuState::default); let hypervisor_type = hypervisor.hypervisor_type(); #[cfg(target_arch = "x86_64")] let cpu_vendor = hypervisor.get_cpu_vendor(); @@ -755,7 +759,7 @@ impl CpuManager { let affinity = if let Some(cpu_affinity) = config.affinity.as_ref() { cpu_affinity .iter() - .map(|a| (a.vcpu as u32, a.host_cpus.clone())) + .map(|a| (a.vcpu, a.host_cpus.clone())) .collect() } else { BTreeMap::new() @@ -781,7 +785,7 @@ impl CpuManager { #[cfg(feature = "guest_debug")] vm_debug_evt, selected_cpu: 0, - vcpus: Vec::with_capacity(usize::from(config.max_vcpus)), + vcpus: Vec::with_capacity(max_vcpus), seccomp_action, vm_ops, acpi_address: None, @@ -895,10 +899,10 @@ impl CpuManager { }, |t| { ( - t.threads_per_core.into(), - t.cores_per_die.into(), - t.dies_per_package.into(), - t.packages.into(), + t.threads_per_core, + t.cores_per_die, + t.dies_per_package, + t.packages, ) }, ); @@ -934,7 +938,7 @@ impl CpuManager { self.present_vcpus() ); - if desired_vcpus > self.config.max_vcpus as u32 { + if desired_vcpus > self.config.max_vcpus { return Err(Error::DesiredVCpuCountExceedsMax); } @@ -1245,7 +1249,7 @@ impl CpuManager { inserting: bool, paused: Option, ) -> Result<()> { - if desired_vcpus > self.config.max_vcpus as u32 { + if desired_vcpus > self.config.max_vcpus { return Err(Error::DesiredVCpuCountExceedsMax); } @@ -1418,11 +1422,11 @@ impl CpuManager { } pub fn boot_vcpus(&self) -> u32 { - self.config.boot_vcpus as u32 + self.config.boot_vcpus } pub fn max_vcpus(&self) -> u32 { - self.config.max_vcpus as u32 + self.config.max_vcpus } #[cfg(target_arch = "x86_64")] @@ -1456,10 +1460,10 @@ impl CpuManager { pub fn get_vcpu_topology(&self) -> Option<(u16, u16, u16, u16)> { self.config.topology.clone().map(|t| { ( - t.threads_per_core.into(), - t.cores_per_die.into(), - t.dies_per_package.into(), - t.packages.into(), + t.threads_per_core, + t.cores_per_die, + t.dies_per_package, + t.packages, ) }) } @@ -1475,7 +1479,7 @@ impl CpuManager { { madt.write(36, arch::layout::APIC_START.0); - for cpu in 0..self.config.max_vcpus as u32 { + for cpu in 0..self.config.max_vcpus { let x2apic_id = get_x2apic_id(cpu, self.get_vcpu_topology()); let lapic = LocalX2Apic { @@ -1483,7 +1487,7 @@ impl CpuManager { length: 16, processor_id: cpu, apic_id: x2apic_id, - flags: if cpu < self.config.boot_vcpus as u32 { + flags: if cpu < self.config.boot_vcpus { 1 << MADT_CPU_ENABLE_FLAG } else { 0 @@ -1535,8 +1539,8 @@ impl CpuManager { r#type: acpi::ACPI_APIC_GENERIC_CPU_INTERFACE, length: 80, reserved0: 0, - cpu_interface_number: cpu as u32, - uid: cpu as u32, + cpu_interface_number: cpu, + uid: cpu, flags: 1, parking_version: 0, performance_interrupt: 0, @@ -2274,7 +2278,7 @@ impl Aml for CpuManager { let uid = aml::Name::new("_CID".into(), &aml::EISAName::new("PNP0A05")); // Bundle methods together under a common object let methods = CpuMethods { - max_vcpus: self.config.max_vcpus as u32, + max_vcpus: self.config.max_vcpus, dynamic: self.dynamic, }; let mut cpu_data_inner: Vec<&dyn Aml> = vec![&hid, &uid, &methods]; @@ -2282,7 +2286,7 @@ impl Aml for CpuManager { #[cfg(target_arch = "x86_64")] let topology = self.get_vcpu_topology(); let mut cpu_devices = Vec::new(); - for cpu_id in 0..(self.config.max_vcpus as u32) { + for cpu_id in 0..self.config.max_vcpus { let proximity_domain = *self.proximity_domain_per_cpu.get(&cpu_id).unwrap_or(&0); let cpu_device = Cpu { cpu_id, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index c097a25974..0ad2ab7ca4 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1694,7 +1694,7 @@ impl DeviceManager { ) -> DeviceManagerResult>> { let interrupt_controller: Arc> = Arc::new(Mutex::new( gic::Gic::new( - self.config.lock().unwrap().cpus.boot_vcpus as u32, + self.config.lock().unwrap().cpus.boot_vcpus, Arc::clone(&self.msi_interrupt_manager), self.address_manager.vm.clone(), ) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 0378b7b18a..cb430728b8 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -27,6 +27,8 @@ use anyhow::anyhow; #[cfg(feature = "dbus_api")] use api::dbus::{DBusApiOptions, DBusApiShutdownChannels}; use api::http::HttpApiHandle; +#[cfg(all(feature = "kvm", target_arch = "x86_64"))] +use arch::x86_64::MAX_SUPPORTED_CPUS_LEGACY; use console_devices::{pre_create_console_devices, ConsoleInfo}; use landlock::LandlockError; use libc::{tcsetattr, termios, EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW}; @@ -888,6 +890,11 @@ impl Vmm { )) })?; + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + if config.lock().unwrap().max_apic_id() > MAX_SUPPORTED_CPUS_LEGACY { + vm.enable_x2apic_api().unwrap(); + } + let phys_bits = vm::physical_bits(&self.hypervisor, config.lock().unwrap().cpus.max_phys_bits); @@ -1822,7 +1829,7 @@ impl RequestHandler for Vmm { } else { let mut config = self.vm_config.as_ref().unwrap().lock().unwrap(); if let Some(desired_vcpus) = desired_vcpus { - config.cpus.boot_vcpus = desired_vcpus.try_into().unwrap(); + config.cpus.boot_vcpus = desired_vcpus; } if let Some(desired_ram) = desired_ram { config.memory.size = desired_ram; diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 3092c32367..d5e1e808d6 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -29,6 +29,8 @@ use anyhow::anyhow; use arch::layout::{KVM_IDENTITY_MAP_START, KVM_TSS_START}; #[cfg(feature = "tdx")] use arch::x86_64::tdx::TdvfSection; +#[cfg(all(feature = "kvm", target_arch = "x86_64"))] +use arch::x86_64::MAX_SUPPORTED_CPUS_LEGACY; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use arch::PciSpaceInfo; use arch::{get_host_cpu_phys_bits, EntryPoint, NumaNode, NumaNodes}; @@ -944,7 +946,7 @@ impl Vm { } if let Some(cpus) = &config.cpus { - node.cpus.extend(cpus.iter().map(|cpu| *cpu as u32)); + node.cpus.extend(cpus); } if let Some(pci_segments) = &config.pci_segments { @@ -1022,6 +1024,11 @@ impl Vm { vm_config.lock().unwrap().memory.total_size(), )?; + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + if vm_config.lock().unwrap().max_apic_id() > MAX_SUPPORTED_CPUS_LEGACY { + vm.enable_x2apic_api().unwrap(); + } + let phys_bits = physical_bits(&hypervisor, vm_config.lock().unwrap().cpus.max_phys_bits); let memory_manager = if let Some(snapshot) = @@ -1655,7 +1662,7 @@ impl Vm { .notify_hotplug(AcpiNotificationFlags::CPU_DEVICES_CHANGED) .map_err(Error::DeviceManager)?; } - self.config.lock().unwrap().cpus.boot_vcpus = desired_vcpus.try_into().unwrap(); + self.config.lock().unwrap().cpus.boot_vcpus = desired_vcpus; } if let Some(desired_memory) = desired_memory { @@ -2709,7 +2716,7 @@ impl Vm { &mut self, destination_url: &str, ) -> std::result::Result { - let nr_cpus = self.config.lock().unwrap().cpus.boot_vcpus as u32; + let nr_cpus = self.config.lock().unwrap().cpus.boot_vcpus; let elf_note_size = self.get_note_size(NoteDescType::ElfAndVmm, nr_cpus) as isize; let mut elf_phdr_num = 1; let elf_sh_info = 0; diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 9c149d05f1..d07da3c44f 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -26,7 +26,7 @@ pub(crate) trait ApplyLandlock { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CpuAffinity { - pub vcpu: u8, + pub vcpu: u32, pub host_cpus: Vec, } @@ -39,10 +39,10 @@ pub struct CpuFeatures { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CpuTopology { - pub threads_per_core: u8, - pub cores_per_die: u8, - pub dies_per_package: u8, - pub packages: u8, + pub threads_per_core: u16, + pub cores_per_die: u16, + pub dies_per_package: u16, + pub packages: u16, } // When booting with PVH boot the maximum physical addressable size @@ -56,8 +56,8 @@ pub fn default_cpuconfig_max_phys_bits() -> u8 { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CpusConfig { - pub boot_vcpus: u8, - pub max_vcpus: u8, + pub boot_vcpus: u32, + pub max_vcpus: u32, #[serde(default)] pub topology: Option, #[serde(default)] @@ -70,7 +70,7 @@ pub struct CpusConfig { pub features: CpuFeatures, } -pub const DEFAULT_VCPUS: u8 = 1; +pub const DEFAULT_VCPUS: u32 = 1; impl Default for CpusConfig { fn default() -> Self { @@ -684,7 +684,7 @@ pub struct NumaConfig { #[serde(default)] pub guest_numa_id: u32, #[serde(default)] - pub cpus: Option>, + pub cpus: Option>, #[serde(default)] pub distances: Option>, #[serde(default)] @@ -1035,4 +1035,18 @@ impl VmConfig { Ok(()) } + + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + pub(crate) fn max_apic_id(&self) -> u32 { + if let Some(topology) = &self.cpus.topology { + arch::x86_64::get_max_x2apic_id(( + topology.threads_per_core, + topology.cores_per_die, + topology.dies_per_package, + topology.packages, + )) + } else { + self.cpus.max_vcpus + } + } } From 3259234e58e509e5c632527086e316973205e85c Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Mon, 8 Sep 2025 16:07:47 +0200 Subject: [PATCH 0157/1893] vmm: drop unnecessary copies path_beneath_rules() just needs the paths given to it to be AsRef, so there's no need to create new PathBufs for it. Signed-off-by: Alyssa Ross --- vmm/src/landlock.rs | 8 ++++---- vmm/src/vm_config.rs | 45 ++++++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/vmm/src/landlock.rs b/vmm/src/landlock.rs index 3defeaefbd..5f139e5c1c 100644 --- a/vmm/src/landlock.rs +++ b/vmm/src/landlock.rs @@ -4,7 +4,7 @@ use std::convert::TryFrom; use std::io::Error as IoError; -use std::path::PathBuf; +use std::path::Path; #[cfg(test)] use landlock::make_bitflags; @@ -87,13 +87,13 @@ impl Landlock { pub(crate) fn add_rule( &mut self, - path: PathBuf, + path: &Path, access: BitFlags, ) -> Result<(), LandlockError> { // path_beneath_rules in landlock crate handles file and directory access rules. // Incoming path/s are passed to path_beneath_rules, so that we don't // have to worry about the type of the path. - let paths = vec![path.clone()]; + let paths = vec![&path]; let path_beneath_rules = path_beneath_rules(paths, access); self.ruleset .as_mut() @@ -104,7 +104,7 @@ impl Landlock { pub(crate) fn add_rule_with_access( &mut self, - path: PathBuf, + path: &Path, access: &str, ) -> Result<(), LandlockError> { self.add_rule(path, LandlockAccess::try_from(access)?.access)?; diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index d07da3c44f..38c0a0ba40 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // use std::net::{IpAddr, Ipv4Addr}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[cfg(feature = "fw_cfg")] use std::str::FromStr; use std::{fs, result}; @@ -159,7 +159,7 @@ pub struct MemoryZoneConfig { impl ApplyLandlock for MemoryZoneConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { if let Some(file) = &self.file { - landlock.add_rule_with_access(file.to_path_buf(), "rw")?; + landlock.add_rule_with_access(file, "rw")?; } Ok(()) } @@ -281,7 +281,7 @@ pub struct DiskConfig { impl ApplyLandlock for DiskConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { if let Some(path) = &self.path { - landlock.add_rule_with_access(path.to_path_buf(), "rw")?; + landlock.add_rule_with_access(path, "rw")?; } Ok(()) } @@ -425,7 +425,7 @@ impl Default for RngConfig { impl ApplyLandlock for RngConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { // Rng Path only need read access - landlock.add_rule_with_access(self.src.to_path_buf(), "r")?; + landlock.add_rule_with_access(&self.src, "r")?; Ok(()) } } @@ -469,7 +469,7 @@ pub fn default_fsconfig_queue_size() -> u16 { impl ApplyLandlock for FsConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; + landlock.add_rule_with_access(&self.socket, "rw")?; Ok(()) } } @@ -492,7 +492,7 @@ pub struct PmemConfig { impl ApplyLandlock for PmemConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { let access = if self.discard_writes { "r" } else { "rw" }; - landlock.add_rule_with_access(self.file.to_path_buf(), access)?; + landlock.add_rule_with_access(&self.file, access)?; Ok(()) } } @@ -524,10 +524,10 @@ pub fn default_consoleconfig_file() -> Option { impl ApplyLandlock for ConsoleConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { if let Some(file) = &self.file { - landlock.add_rule_with_access(file.to_path_buf(), "rw")?; + landlock.add_rule_with_access(file, "rw")?; } if let Some(socket) = &self.socket { - landlock.add_rule_with_access(socket.to_path_buf(), "rw")?; + landlock.add_rule_with_access(socket, "rw")?; } Ok(()) } @@ -557,7 +557,7 @@ impl Default for DebugConsoleConfig { impl ApplyLandlock for DebugConsoleConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { if let Some(file) = &self.file { - landlock.add_rule_with_access(file.to_path_buf(), "rw")?; + landlock.add_rule_with_access(file, "rw")?; } Ok(()) } @@ -585,8 +585,9 @@ impl ApplyLandlock for DeviceConfig { .to_str() .ok_or(LandlockError::InvalidPath)?; - let vfio_group_path = "/dev/vfio/".to_owned() + iommu_group_str; - landlock.add_rule_with_access(vfio_group_path.into(), "rw")?; + let mut vfio_group_path = PathBuf::from("/dev/vfio"); + vfio_group_path.push(iommu_group_str); + landlock.add_rule_with_access(&vfio_group_path, "rw")?; Ok(()) } @@ -603,7 +604,7 @@ pub struct UserDeviceConfig { impl ApplyLandlock for UserDeviceConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; + landlock.add_rule_with_access(&self.socket, "rw")?; Ok(()) } } @@ -627,7 +628,7 @@ pub fn default_vdpaconfig_num_queues() -> usize { impl ApplyLandlock for VdpaConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.path.to_path_buf(), "rw")?; + landlock.add_rule_with_access(&self.path, "rw")?; Ok(()) } } @@ -646,7 +647,7 @@ pub struct VsockConfig { impl ApplyLandlock for VsockConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; + landlock.add_rule_with_access(&self.socket, "rw")?; Ok(()) } } @@ -832,20 +833,20 @@ impl ApplyLandlock for PayloadConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { // Payload only needs read access if let Some(firmware) = &self.firmware { - landlock.add_rule_with_access(firmware.to_path_buf(), "r")?; + landlock.add_rule_with_access(firmware, "r")?; } if let Some(kernel) = &self.kernel { - landlock.add_rule_with_access(kernel.to_path_buf(), "r")?; + landlock.add_rule_with_access(kernel, "r")?; } if let Some(initramfs) = &self.initramfs { - landlock.add_rule_with_access(initramfs.to_path_buf(), "r")?; + landlock.add_rule_with_access(initramfs, "r")?; } #[cfg(feature = "igvm")] if let Some(igvm) = &self.igvm { - landlock.add_rule_with_access(igvm.to_path_buf(), "r")?; + landlock.add_rule_with_access(igvm, "r")?; } Ok(()) @@ -877,7 +878,7 @@ pub struct TpmConfig { impl ApplyLandlock for TpmConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; + landlock.add_rule_with_access(&self.socket, "rw")?; Ok(()) } } @@ -890,7 +891,7 @@ pub struct LandlockConfig { impl ApplyLandlock for LandlockConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.path.to_path_buf(), self.access.clone().as_str())?; + landlock.add_rule_with_access(&self.path, self.access.clone().as_str())?; Ok(()) } } @@ -990,7 +991,7 @@ impl VmConfig { } if let Some(devices) = &self.devices { - landlock.add_rule_with_access("/dev/vfio/vfio".into(), "rw")?; + landlock.add_rule_with_access(Path::new("/dev/vfio/vfio"), "rw")?; for device in devices.iter() { device.apply_landlock(&mut landlock)?; @@ -1022,7 +1023,7 @@ impl VmConfig { } if self.net.is_some() { - landlock.add_rule_with_access("/dev/net/tun".into(), "rw")?; + landlock.add_rule_with_access(Path::new("/dev/net/tun"), "rw")?; } if let Some(landlock_rules) = &self.landlock_rules { From 08b197bbc1e2ae2ac06c312dfbe9ddc5c704f6d5 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 9 Sep 2025 10:21:38 +0200 Subject: [PATCH 0158/1893] vmm: fix vsock with landlock Without write access to the directory the socket will go in, it's not possible to create the socket. I've tested outgoing connections from the VM, and they don't seem to need read permissions on that directory to connect to a socket on the host. Fixes: b3e5738b4 ("vmm: Introduce ApplyLandlock trait") Signed-off-by: Alyssa Ross --- vmm/src/vm_config.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 38c0a0ba40..aff0d95f35 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -647,7 +647,12 @@ pub struct VsockConfig { impl ApplyLandlock for VsockConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + if let Some(parent) = self.socket.parent() { + landlock.add_rule_with_access(parent, "w")?; + } + landlock.add_rule_with_access(&self.socket, "rw")?; + Ok(()) } } From e7e850bbdd4b995f65876b588f617204c6ea952b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Mon, 8 Sep 2025 16:04:40 -0700 Subject: [PATCH 0159/1893] tests: enable more test cases for MSHV MSHV now supports movable pages i.e VA backed guest. Also with more features and stability in the MSHV Kernel drives MSHV now supports more test scenario. This patch enables more integration test cases. Signed-off-by: Muminul Islam --- tests/integration.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 94f79dac6d..45662c786c 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2849,7 +2849,6 @@ mod common_parallel { } #[test] - #[cfg(not(feature = "mshv"))] fn test_user_defined_memory_regions() { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(focal)); @@ -2917,7 +2916,6 @@ mod common_parallel { } #[test] - #[cfg(not(feature = "mshv"))] fn test_guest_numa_nodes() { _test_guest_numa_nodes(false); } @@ -3914,13 +3912,11 @@ mod common_parallel { } #[test] - #[cfg(not(feature = "mshv"))] fn test_virtio_fs_multi_segment_hotplug() { _test_virtio_fs(&prepare_virtiofsd, true, Some(15)) } #[test] - #[cfg(not(feature = "mshv"))] fn test_virtio_fs_multi_segment() { _test_virtio_fs(&prepare_virtiofsd, false, Some(15)) } @@ -5107,7 +5103,6 @@ mod common_parallel { } #[test] - #[cfg(not(feature = "mshv"))] fn test_virtio_mem() { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(focal)); @@ -5181,7 +5176,6 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] - #[cfg(not(feature = "mshv"))] // Test both vCPU and memory resizing together fn test_resize() { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); @@ -5809,7 +5803,6 @@ mod common_parallel { } #[test] - #[cfg(not(feature = "mshv"))] fn test_virtio_balloon_free_page_reporting() { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(focal)); @@ -7887,7 +7880,6 @@ mod common_sequential { // through each ssh command. There's no need to perform a dedicated test to // verify the migration went well for virtio-net. #[test] - #[cfg(not(feature = "mshv"))] fn test_snapshot_restore_hotplug_virtiomem() { _test_snapshot_restore(true); } @@ -11164,25 +11156,21 @@ mod live_migration { } #[test] - #[cfg(not(feature = "mshv"))] fn test_live_migration_numa() { _test_live_migration_numa(false, false) } #[test] - #[cfg(not(feature = "mshv"))] fn test_live_migration_numa_local() { _test_live_migration_numa(false, true) } #[test] - #[cfg(not(feature = "mshv"))] fn test_live_upgrade_numa() { _test_live_migration_numa(true, false) } #[test] - #[cfg(not(feature = "mshv"))] fn test_live_upgrade_numa_local() { _test_live_migration_numa(true, true) } From c7eac0f65a83b9cd4fdc9d3edb918f9efcbe0b34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 23:01:53 +0000 Subject: [PATCH 0160/1893] build: Bump clap from 4.5.13 to 4.5.47 Bumps [clap](https://github.com/clap-rs/clap) from 4.5.13 to 4.5.47. - [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.13...clap_complete-v4.5.47) --- updated-dependencies: - dependency-name: clap dependency-version: 4.5.47 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 25 ++++++++----------------- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b896638f4..61bc88a524 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -371,18 +371,18 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.13" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" dependencies = [ "anstream", "anstyle", @@ -2047,12 +2047,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 0.38.44", - "windows-sys 0.48.0", + "rustix 1.0.7", + "windows-sys 0.60.2", ] [[package]] @@ -2617,15 +2617,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 74cdbbeb17..d216a795ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,7 +139,7 @@ anyhow = "1.0.98" bitflags = "2.9.3" byteorder = "1.5.0" cfg-if = "1.0.0" -clap = "4.5.13" +clap = "4.5.47" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" From 533d3a85d13af3c8d86398b81526266db070f062 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Mon, 8 Sep 2025 17:41:11 +0200 Subject: [PATCH 0161/1893] vmm: fix landlock on aarch64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit arch::aarch64::fdt::create_cpu_nodes will always look at this if it exists. (If it doesn't exist, this is a no-op — add_rule_with_access() won't add rules for paths that don't exist.) Fixes: b3e5738b4 ("vmm: Introduce ApplyLandlock trait") Signed-off-by: Alyssa Ross --- vmm/src/vm_config.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index aff0d95f35..96269bfb27 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -960,6 +960,11 @@ impl VmConfig { pub(crate) fn apply_landlock(&self) -> LandlockResult<()> { let mut landlock = Landlock::new()?; + #[cfg(target_arch = "aarch64")] + { + landlock.add_rule_with_access(Path::new("/sys/devices/system/cpu/cpu0/cache"), "r")?; + } + if let Some(mem_zones) = &self.memory.zones { for zone in mem_zones.iter() { zone.apply_landlock(&mut landlock)?; From 6cac99bd76dc048493b0085c85695639c31cff35 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 9 Sep 2025 10:53:10 +0200 Subject: [PATCH 0162/1893] vmm: error if landlock can't be enabled Since the user has to explicitly ask for Landlock to be enabled in Cloud Hypervisor, it's surprising that, even when they've done that, Landlock will silently not be enabled if the kernel doesn't support it. To prevent accidental absence of a desired security protection, exit with an error if Landlock, or the one feature of it we use in Cloud Hypervisor (file access) is not supported. Signed-off-by: Alyssa Ross --- vmm/src/landlock.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vmm/src/landlock.rs b/vmm/src/landlock.rs index 5f139e5c1c..b7fbf6edb7 100644 --- a/vmm/src/landlock.rs +++ b/vmm/src/landlock.rs @@ -9,8 +9,8 @@ use std::path::Path; #[cfg(test)] use landlock::make_bitflags; use landlock::{ - path_beneath_rules, Access, AccessFs, BitFlags, Ruleset, RulesetAttr, RulesetCreated, - RulesetCreatedAttr, RulesetError, ABI, + path_beneath_rules, Access, AccessFs, BitFlags, Compatible, Ruleset, RulesetAttr, + RulesetCreated, RulesetCreatedAttr, RulesetError, ABI, }; use thiserror::Error; @@ -75,8 +75,10 @@ impl Landlock { let file_access = AccessFs::from_all(ABI); let def_ruleset = Ruleset::default() + .set_compatibility(landlock::CompatLevel::HardRequirement) .handle_access(file_access) - .map_err(LandlockError::ManageRuleset)?; + .map_err(LandlockError::ManageRuleset)? + .set_compatibility(landlock::CompatLevel::HardRequirement); // By default, rulesets are created in `BestEffort` mode. This lets Landlock // to enable all the supported rules and silently ignore the unsupported ones. From 305bec056fa7d2bcb9632d63021cc7a102b0de0c Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 9 Sep 2025 16:22:28 +0200 Subject: [PATCH 0163/1893] arch: aarch64: fdt: replace broken link The previously linked file was converted to ReStructuredText, then later removed from the kernel, with part of the justification being "Most of what booting-without-of.rst contains is now in the DT specification", so point to that instead. Signed-off-by: Alyssa Ross --- arch/src/aarch64/fdt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index aefecf8b2d..1669f980ba 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -231,8 +231,8 @@ pub fn create_fdt Date: Tue, 9 Sep 2025 15:52:40 +0200 Subject: [PATCH 0164/1893] seccomp: avoid hardcoding ioctl numbers These can differ between platforms, so it's better to use centralized definitions of them. We can't currently do this for the KVM and VFIO ioctls, because the corresponding crates don't publicly expose the ioctl numbers. Signed-off-by: Alyssa Ross --- Cargo.lock | 1 + virtio-devices/src/seccomp_filters.rs | 14 +- vmm/Cargo.toml | 1 + vmm/src/seccomp_filters.rs | 210 ++++++++++++-------------- 4 files changed, 100 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61bc88a524..82933af3ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2489,6 +2489,7 @@ dependencies = [ "uuid", "vfio-ioctls", "vfio_user", + "vhost", "virtio-bindings", "virtio-devices", "vm-allocator", diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs index 2579e4f142..abad842bb1 100644 --- a/virtio-devices/src/seccomp_filters.rs +++ b/virtio-devices/src/seccomp_filters.rs @@ -4,6 +4,7 @@ // // SPDX-License-Identifier: Apache-2.0 +use libc::{FIONBIO, TIOCGWINSZ, TUNSETOFFLOAD}; use seccompiler::SeccompCmpOp::Eq; use seccompiler::{ BpfProgram, Error, SeccompAction, SeccompCmpArgLen as ArgLen, SeccompCondition as Cond, @@ -46,17 +47,10 @@ macro_rules! or { ($($x:expr),*) => (vec![$($x),*]) } -// See include/uapi/asm-generic/ioctls.h in the kernel code. -const TIOCGWINSZ: u64 = 0x5413; -const FIONBIO: u64 = 0x5421; - // See include/uapi/linux/vfio.h in the kernel code. const VFIO_IOMMU_MAP_DMA: u64 = 0x3b71; const VFIO_IOMMU_UNMAP_DMA: u64 = 0x3b72; -// See include/uapi/linux/if_tun.h in the kernel code. -const TUNSETOFFLOAD: u64 = 0x4004_54d0; - #[cfg(feature = "sev_snp")] fn mshv_sev_snp_ioctl_seccomp_rule() -> SeccompRule { and![Cond::new( @@ -75,7 +69,7 @@ fn create_mshv_sev_snp_ioctl_seccomp_rule() -> Vec { fn create_virtio_console_ioctl_seccomp_rule() -> Vec { or![ - and![Cond::new(1, ArgLen::Dword, Eq, TIOCGWINSZ).unwrap()], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCGWINSZ as _).unwrap()], #[cfg(feature = "sev_snp")] mshv_sev_snp_ioctl_seccomp_rule(), ] @@ -157,7 +151,7 @@ fn virtio_net_thread_rules() -> Vec<(i64, Vec)> { fn create_virtio_net_ctl_ioctl_seccomp_rule() -> Vec { or![ - and![Cond::new(1, ArgLen::Dword, Eq, TUNSETOFFLOAD).unwrap()], + and![Cond::new(1, ArgLen::Dword, Eq, TUNSETOFFLOAD as _).unwrap()], #[cfg(feature = "sev_snp")] mshv_sev_snp_ioctl_seccomp_rule(), ] @@ -231,7 +225,7 @@ fn virtio_vhost_block_thread_rules() -> Vec<(i64, Vec)> { fn create_vsock_ioctl_seccomp_rule() -> Vec { or![ - and![Cond::new(1, ArgLen::Dword, Eq, FIONBIO,).unwrap()], + and![Cond::new(1, ArgLen::Dword, Eq, FIONBIO as _).unwrap()], #[cfg(feature = "sev_snp")] mshv_sev_snp_ioctl_seccomp_rule(), ] diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 6ebabc01ae..7793357e2d 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -78,6 +78,7 @@ tracer = { path = "../tracer" } uuid = { workspace = true } vfio-ioctls = { workspace = true, default-features = false } vfio_user = { workspace = true } +vhost = { workspace = true } virtio-bindings = { workspace = true } virtio-devices = { path = "../virtio-devices" } vm-allocator = { path = "../vm-allocator" } diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 46e38f9c8e..3dacd30463 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -5,11 +5,25 @@ // SPDX-License-Identifier: Apache-2.0 use hypervisor::HypervisorType; +use libc::{ + BLKIOMIN, BLKIOOPT, BLKPBSZGET, BLKSSZGET, FIOCLEX, FIONBIO, SIOCGIFFLAGS, SIOCGIFHWADDR, + SIOCGIFINDEX, SIOCGIFMTU, SIOCSIFADDR, SIOCSIFFLAGS, SIOCSIFHWADDR, SIOCSIFMTU, SIOCSIFNETMASK, + TCGETS, TCGETS2, TCSETS, TCSETS2, TIOCGPGRP, TIOCGPTPEER, TIOCGWINSZ, TIOCSCTTY, TIOCSPGRP, + TIOCSPTLCK, TUNGETFEATURES, TUNGETIFF, TUNSETIFF, TUNSETOFFLOAD, TUNSETVNETHDRSZ, +}; use seccompiler::SeccompCmpOp::Eq; use seccompiler::{ BackendError, BpfProgram, Error, SeccompAction, SeccompCmpArgLen as ArgLen, SeccompCondition as Cond, SeccompFilter, SeccompRule, }; +use vhost::vhost_kern::vhost_binding::{ + VHOST_GET_BACKEND_FEATURES, VHOST_GET_FEATURES, VHOST_SET_BACKEND_FEATURES, VHOST_SET_FEATURES, + VHOST_SET_OWNER, VHOST_SET_VRING_ADDR, VHOST_SET_VRING_BASE, VHOST_SET_VRING_CALL, + VHOST_SET_VRING_KICK, VHOST_SET_VRING_NUM, VHOST_VDPA_GET_CONFIG, VHOST_VDPA_GET_CONFIG_SIZE, + VHOST_VDPA_GET_DEVICE_ID, VHOST_VDPA_GET_IOVA_RANGE, VHOST_VDPA_GET_STATUS, + VHOST_VDPA_GET_VRING_NUM, VHOST_VDPA_SET_CONFIG, VHOST_VDPA_SET_CONFIG_CALL, + VHOST_VDPA_SET_STATUS, VHOST_VDPA_SET_VRING_ENABLE, VHOST_VDPA_SUSPEND, +}; pub enum Thread { HttpApi, @@ -40,44 +54,6 @@ macro_rules! or { ($($x:expr),*) => (vec![$($x),*]) } -// See include/uapi/asm-generic/ioctls.h in the kernel code. -const TCGETS: u64 = 0x5401; -const TCGETS2: u64 = 0x802c_542a; -const TCSETS: u64 = 0x5402; -const TCSETS2: u64 = 0x402c_542b; -const TIOCSCTTY: u64 = 0x540E; -const TIOCGPGRP: u64 = 0x540F; -const TIOCSPGRP: u64 = 0x5410; -const TIOCGWINSZ: u64 = 0x5413; -const TIOCSPTLCK: u64 = 0x4004_5431; -const TIOCGPTPEER: u64 = 0x5441; -const FIOCLEX: u64 = 0x5451; -const FIONBIO: u64 = 0x5421; - -// See include/uapi/linux/fs.h in the kernel code. -const BLKSSZGET: u64 = 0x1268; -const BLKPBSZGET: u64 = 0x127b; -const BLKIOMIN: u64 = 0x1278; -const BLKIOOPT: u64 = 0x1279; - -// See include/uapi/linux/if_tun.h in the kernel code. -const TUNGETIFF: u64 = 0x8004_54d2; -const TUNSETIFF: u64 = 0x4004_54ca; -const TUNSETOFFLOAD: u64 = 0x4004_54d0; -const TUNSETVNETHDRSZ: u64 = 0x4004_54d8; -const TUNGETFEATURES: u64 = 0x8004_54cf; - -// See include/uapi/linux/sockios.h in the kernel code. -const SIOCGIFFLAGS: u64 = 0x8913; -const SIOCSIFFLAGS: u64 = 0x8914; -const SIOCSIFADDR: u64 = 0x8916; -const SIOCSIFNETMASK: u64 = 0x891c; -const SIOCGIFMTU: u64 = 0x8921; -const SIOCSIFMTU: u64 = 0x8922; -const SIOCSIFHWADDR: u64 = 0x8924; -const SIOCGIFHWADDR: u64 = 0x8927; -const SIOCGIFINDEX: u64 = 0x8933; - // See include/uapi/linux/vfio.h in the kernel code. const VFIO_GET_API_VERSION: u64 = 0x3b64; const VFIO_CHECK_EXTENSION: u64 = 0x3b65; @@ -95,29 +71,6 @@ const VFIO_IOMMU_MAP_DMA: u64 = 0x3b71; const VFIO_IOMMU_UNMAP_DMA: u64 = 0x3b72; const VFIO_DEVICE_IOEVENTFD: u64 = 0x3b74; -// See include/uapi/linux/vhost.h in the kernel code -const VHOST_GET_FEATURES: u64 = 0x8008af00; -const VHOST_SET_FEATURES: u64 = 0x4008af00; -const VHOST_SET_OWNER: u64 = 0xaf01; -const VHOST_SET_VRING_NUM: u64 = 0x4008af10; -const VHOST_SET_VRING_ADDR: u64 = 0x4028af11; -const VHOST_SET_VRING_BASE: u64 = 0x4008af12; -const VHOST_SET_VRING_KICK: u64 = 0x4008af20; -const VHOST_SET_VRING_CALL: u64 = 0x4008af21; -const VHOST_SET_BACKEND_FEATURES: u64 = 0x4008af25; -const VHOST_GET_BACKEND_FEATURES: u64 = 0x8008af26; -const VHOST_VDPA_GET_DEVICE_ID: u64 = 0x8004af70; -const VHOST_VDPA_GET_STATUS: u64 = 0x8001af71; -const VHOST_VDPA_SET_STATUS: u64 = 0x4001af72; -const VHOST_VDPA_GET_CONFIG: u64 = 0x8008af73; -const VHOST_VDPA_SET_CONFIG: u64 = 0x4008af74; -const VHOST_VDPA_SET_VRING_ENABLE: u64 = 0x4008af75; -const VHOST_VDPA_GET_VRING_NUM: u64 = 0x8002af76; -const VHOST_VDPA_SET_CONFIG_CALL: u64 = 0x4004af77; -const VHOST_VDPA_GET_IOVA_RANGE: u64 = 0x8010af78; -const VHOST_VDPA_GET_CONFIG_SIZE: u64 = 0x8004af79; -const VHOST_VDPA_SUSPEND: u64 = 0xaf7d; - // See include/uapi/linux/kvm.h in the kernel code. #[cfg(feature = "kvm")] mod kvm { @@ -297,12 +250,12 @@ fn create_vmm_ioctl_seccomp_rule_common( hypervisor_type: HypervisorType, ) -> Result, BackendError> { let mut common_rules = or![ - and![Cond::new(1, ArgLen::Dword, Eq, BLKSSZGET)?], - and![Cond::new(1, ArgLen::Dword, Eq, BLKPBSZGET)?], - and![Cond::new(1, ArgLen::Dword, Eq, BLKIOMIN)?], - and![Cond::new(1, ArgLen::Dword, Eq, BLKIOOPT)?], - and![Cond::new(1, ArgLen::Dword, Eq, FIOCLEX)?], - and![Cond::new(1, ArgLen::Dword, Eq, FIONBIO)?], + and![Cond::new(1, ArgLen::Dword, Eq, BLKSSZGET as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, BLKPBSZGET as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, BLKIOMIN as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, BLKIOOPT as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, FIOCLEX as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, FIONBIO as _)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFFLAGS)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFHWADDR)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFMTU)?], @@ -312,21 +265,21 @@ fn create_vmm_ioctl_seccomp_rule_common( and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFHWADDR)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFMTU)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFNETMASK)?], - and![Cond::new(1, ArgLen::Dword, Eq, TCSETS)?], - and![Cond::new(1, ArgLen::Dword, Eq, TCSETS2)?], - and![Cond::new(1, ArgLen::Dword, Eq, TCGETS)?], - and![Cond::new(1, ArgLen::Dword, Eq, TCGETS2)?], - and![Cond::new(1, ArgLen::Dword, Eq, TIOCGPGRP)?], - and![Cond::new(1, ArgLen::Dword, Eq, TIOCGPTPEER)?], - and![Cond::new(1, ArgLen::Dword, Eq, TIOCGWINSZ)?], - and![Cond::new(1, ArgLen::Dword, Eq, TIOCSCTTY)?], - and![Cond::new(1, ArgLen::Dword, Eq, TIOCSPGRP)?], - and![Cond::new(1, ArgLen::Dword, Eq, TIOCSPTLCK)?], - and![Cond::new(1, ArgLen::Dword, Eq, TUNGETFEATURES)?], - and![Cond::new(1, ArgLen::Dword, Eq, TUNGETIFF)?], - and![Cond::new(1, ArgLen::Dword, Eq, TUNSETIFF)?], - and![Cond::new(1, ArgLen::Dword, Eq, TUNSETOFFLOAD)?], - and![Cond::new(1, ArgLen::Dword, Eq, TUNSETVNETHDRSZ)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCSETS as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCSETS2 as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCGETS as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCGETS2 as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCGPGRP as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCGPTPEER as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCGWINSZ as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCSCTTY as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCSPGRP as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCSPTLCK as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TUNGETFEATURES as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TUNGETIFF as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TUNSETIFF as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TUNSETOFFLOAD as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TUNSETVNETHDRSZ as _)?], and![Cond::new(1, ArgLen::Dword, Eq, VFIO_GET_API_VERSION)?], and![Cond::new(1, ArgLen::Dword, Eq, VFIO_CHECK_EXTENSION)?], and![Cond::new(1, ArgLen::Dword, Eq, VFIO_SET_IOMMU)?], @@ -347,32 +300,57 @@ fn create_vmm_ioctl_seccomp_rule_common( and![Cond::new(1, ArgLen::Dword, Eq, VFIO_IOMMU_MAP_DMA)?], and![Cond::new(1, ArgLen::Dword, Eq, VFIO_IOMMU_UNMAP_DMA)?], and![Cond::new(1, ArgLen::Dword, Eq, VFIO_DEVICE_IOEVENTFD)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_GET_FEATURES)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_FEATURES)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_OWNER)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_NUM)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_ADDR)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_BASE)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_KICK)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_CALL)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_BACKEND_FEATURES)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_GET_BACKEND_FEATURES)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_DEVICE_ID)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_STATUS)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SET_STATUS)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_CONFIG)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SET_CONFIG)?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_GET_FEATURES())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_FEATURES())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_OWNER())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_NUM())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_ADDR())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_BASE())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_KICK())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_SET_VRING_CALL())?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + VHOST_SET_BACKEND_FEATURES() + )?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + VHOST_GET_BACKEND_FEATURES() + )?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_DEVICE_ID())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_STATUS())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SET_STATUS())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_CONFIG())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SET_CONFIG())?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + VHOST_VDPA_SET_VRING_ENABLE(), + )?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_VRING_NUM())?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + VHOST_VDPA_SET_CONFIG_CALL() + )?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + VHOST_VDPA_GET_IOVA_RANGE() + )?], and![Cond::new( 1, ArgLen::Dword, Eq, - VHOST_VDPA_SET_VRING_ENABLE + VHOST_VDPA_GET_CONFIG_SIZE() )?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_VRING_NUM)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SET_CONFIG_CALL)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_IOVA_RANGE)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_CONFIG_SIZE)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SUSPEND)?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SUSPEND())?], ]; let hypervisor_rules = create_vmm_ioctl_seccomp_rule_hypervisor(hypervisor_type)?; @@ -485,16 +463,16 @@ fn create_vmm_ioctl_seccomp_rule( } fn create_api_ioctl_seccomp_rule() -> Result, BackendError> { - Ok(or![and![Cond::new(1, ArgLen::Dword, Eq, FIONBIO)?]]) + Ok(or![and![Cond::new(1, ArgLen::Dword, Eq, FIONBIO as _)?]]) } fn create_signal_handler_ioctl_seccomp_rule() -> Result, BackendError> { Ok(or![ - and![Cond::new(1, ArgLen::Dword, Eq, TCGETS)?], - and![Cond::new(1, ArgLen::Dword, Eq, TCGETS2)?], - and![Cond::new(1, ArgLen::Dword, Eq, TCSETS)?], - and![Cond::new(1, ArgLen::Dword, Eq, TCSETS2)?], - and![Cond::new(1, ArgLen::Dword, Eq, TIOCGWINSZ)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCGETS as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCGETS2 as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCSETS as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TCSETS2 as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCGWINSZ as _)?], ]) } @@ -526,9 +504,9 @@ fn signal_handler_thread_rules() -> Result)>, Backend fn create_pty_foreground_ioctl_seccomp_rule() -> Result, BackendError> { Ok(or![ - and![Cond::new(1, ArgLen::Dword, Eq, TIOCGPGRP)?], - and![Cond::new(1, ArgLen::Dword, Eq, TIOCSCTTY)?], - and![Cond::new(1, ArgLen::Dword, Eq, TIOCSPGRP)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCGPGRP as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCSCTTY as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, TIOCSPGRP as _)?], ]) } @@ -773,14 +751,14 @@ fn create_vcpu_ioctl_seccomp_rule( and![Cond::new(1, ArgLen::Dword, Eq, VFIO_GROUP_UNSET_CONTAINER)?], and![Cond::new(1, ArgLen::Dword, Eq, VFIO_IOMMU_MAP_DMA)?], and![Cond::new(1, ArgLen::Dword, Eq, VFIO_IOMMU_UNMAP_DMA)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SET_STATUS)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_CONFIG)?], - and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SET_CONFIG)?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SET_STATUS())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_GET_CONFIG())?], + and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SET_CONFIG())?], and![Cond::new( 1, ArgLen::Dword, Eq, - VHOST_VDPA_SET_VRING_ENABLE + VHOST_VDPA_SET_VRING_ENABLE(), )?], ]; From e4af58b88eeb6cb8444d949a3ef61e9f4d5bf9b3 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 9 Sep 2025 16:01:56 +0200 Subject: [PATCH 0165/1893] arch: aarch64: fdt: reduce log verbosity This module warns for a lot of routine things that the user cannot do anything about. Closes: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7220 Signed-off-by: Alyssa Ross --- arch/src/aarch64/fdt.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index 1669f980ba..e6b9722270 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -110,11 +110,8 @@ pub fn get_cache_size(cache_level: CacheLevel) -> u32 { let file_path = Path::new(&file_directory); if !file_path.exists() { - warn!("File: {} does not exist.", file_directory); 0 } else { - info!("File: {} exist.", file_directory); - 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(); @@ -144,11 +141,8 @@ pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 { let file_path = Path::new(&file_directory); if !file_path.exists() { - warn!("File: {} does not exist.", file_directory); 0 } else { - info!("File: {} exist.", file_directory); - let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted."); src.trim().parse::().unwrap() } @@ -167,11 +161,8 @@ pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 { let file_path = Path::new(&file_directory); if !file_path.exists() { - warn!("File: {} does not exist.", file_directory); 0 } else { - info!("File: {} exist.", file_directory); - let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted."); src.trim().parse::().unwrap() } @@ -195,11 +186,8 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool { let file_path = Path::new(&file_directory); if !file_path.exists() { - warn!("File: {} does not exist.", file_directory); result = false; } else { - info!("File: {} exist.", file_directory); - let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted."); let src = src.trim(); if src.is_empty() { @@ -325,7 +313,6 @@ fn create_cpu_nodes( if !cache_exist { warn!("cache sysfs system does not exist."); } else { - info!("cache sysfs system exists."); // 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); @@ -426,9 +413,6 @@ fn create_cpu_nodes( fdt.end_node(l2_cache_node)?; } - if l2_cache_size != 0 && l2_cache_shared { - warn!("L2 cache shared with other cpus"); - } } fdt.end_node(cpu_node)?; From 5737e58f293a8c50046bf9f9efc38c95e487d28f Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 9 Sep 2025 18:24:01 +0000 Subject: [PATCH 0166/1893] tests: Use serial console in test_cpu_hotplug Virtio console is activated much later in boot. The output it spits out lacks the initial CPU configuration of the guest. Signed-off-by: Wei Liu --- tests/integration.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/integration.rs b/tests/integration.rs index 45662c786c..255c27adf1 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -4939,6 +4939,7 @@ mod common_parallel { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(focal)); let api_socket = temp_api_path(&guest.tmp_dir); + let console_str = "console=ttyS0"; let kernel_path = direct_kernel_boot_path(); @@ -4946,7 +4947,14 @@ mod common_parallel { .args(["--cpus", "boot=2,max=4"]) .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--cmdline", + DIRECT_KERNEL_BOOT_CMDLINE + .replace("console=hvc0 ", console_str) + .as_str(), + ]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) .default_disks() .default_net() .args(["--api-socket", &api_socket]) From 86c736fe76136f0ea97bf330796635a8d3bc6785 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Tue, 9 Sep 2025 17:35:27 +0000 Subject: [PATCH 0167/1893] build: bump mshv and vfio crates Bump mshv-ioctls and mshv-bindings to 0.6.0. Most notably, this version contains fixes and new bindings for arm64 guests. Bump the vfio crates too so that they point to the latest mshv crates. Signed-off-by: Anirudh Rayabharam --- Cargo.lock | 20 +++++++-------- Cargo.toml | 10 ++++---- fuzz/Cargo.lock | 68 ++++++++++++++++++++++++------------------------- fuzz/Cargo.toml | 2 +- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82933af3ec..0111343fa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1225,9 +1225,9 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mshv-bindings" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07f94f542c738f19317363222a7f415588c04cda964882479af41948ac3c3647" +checksum = "805cf329582f770f62cc612716a04c14815276ae266b6298375a672d3c5a5184" dependencies = [ "libc", "num_enum", @@ -1239,9 +1239,9 @@ dependencies = [ [[package]] name = "mshv-ioctls" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a6df0848f14eb69505a28673f94acdd830cf248fb57022b21f24e242b702e66" +checksum = "aefaab4c067cf5226a917227640d835327b25b71a8d465f815f74f490344e10a" dependencies = [ "libc", "mshv-bindings", @@ -2228,18 +2228,18 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vfio-bindings" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b565663f62e091ca47db9a674c8c95c9686a000e82970f391a3cacf6470ff060" +checksum = "698c66a4522a31ab407a410a59c9660da036178e4fe3f371825cd6aad7d46837" dependencies = [ "vmm-sys-util", ] [[package]] name = "vfio-ioctls" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61316b5e308faa8ed4a87c4130256f765e46de3442eb2e2e619840ef73456738" +checksum = "7af7e8d49719333e5eb52209417f26695c9ab2b117a82596a63a44947f97c5d6" dependencies = [ "byteorder", "kvm-bindings", @@ -2256,9 +2256,9 @@ dependencies = [ [[package]] name = "vfio_user" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed81c5ed8224d468a322e923777ed0615cad433fe61177126098af995f89cecf" +checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a" dependencies = [ "bitflags 2.9.3", "libc", diff --git a/Cargo.toml b/Cargo.toml index d216a795ba..5a0d56a439 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,12 +110,12 @@ kvm-bindings = "0.12.0" kvm-ioctls = "0.22.0" # TODO: update to 0.13.1+ linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" } -mshv-bindings = "0.5.2" -mshv-ioctls = "0.5.2" +mshv-bindings = "0.6.0" +mshv-ioctls = "0.6.0" seccompiler = "0.5.0" -vfio-bindings = { version = "0.5.0", default-features = false } -vfio-ioctls = { version = "0.5.0", default-features = false } -vfio_user = { version = "0.1.0", default-features = false } +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 } virtio-bindings = "0.2.6" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index f6692014dc..1750a54b58 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -121,9 +121,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "block" @@ -176,18 +176,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.13" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" dependencies = [ "anstream", "anstyle", @@ -197,9 +197,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cloud-hypervisor-fuzz" @@ -300,7 +300,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.9.0", + "bitflags 2.9.4", "byteorder", "event_monitor", "hypervisor", @@ -344,7 +344,7 @@ version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "libc", ] @@ -406,7 +406,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d66e32caf5dd59f561be0143e413e01d651bd8498eb9aa0be8c482c81c8d31" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "cfg-if", "log", "managed", @@ -416,9 +416,9 @@ dependencies = [ [[package]] name = "gdbstub_arch" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328a9e9425db13770d0d11de6332a608854266e44c53d12776be7b4aa427e3de" +checksum = "22dde0e1b68787036ccedd0b1ff6f953527a0e807e571fbe898975203027278f" dependencies = [ "gdbstub", "num-traits", @@ -555,7 +555,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b702df98508cb63ad89dd9beb9f6409761b30edca10d48e57941d3f11513a006" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "kvm-bindings", "libc", "vmm-sys-util", @@ -598,8 +598,7 @@ dependencies = [ [[package]] name = "linux-loader" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870c3814345f050991f99869417779f6062542bcf4ed81db7a1b926ad1306638" +source = "git+https://github.com/rust-vmm/linux-loader?branch=main#5fdaed87ddafc89d6abf0b50195a12d19133000d" dependencies = [ "vm-memory", ] @@ -643,9 +642,9 @@ dependencies = [ [[package]] name = "mshv-bindings" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07f94f542c738f19317363222a7f415588c04cda964882479af41948ac3c3647" +checksum = "805cf329582f770f62cc612716a04c14815276ae266b6298375a672d3c5a5184" dependencies = [ "libc", "num_enum", @@ -923,9 +922,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -987,9 +986,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "spin" @@ -1110,9 +1109,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -1122,18 +1121,18 @@ dependencies = [ [[package]] name = "vfio-bindings" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b565663f62e091ca47db9a674c8c95c9686a000e82970f391a3cacf6470ff060" +checksum = "698c66a4522a31ab407a410a59c9660da036178e4fe3f371825cd6aad7d46837" dependencies = [ "vmm-sys-util", ] [[package]] name = "vfio-ioctls" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61316b5e308faa8ed4a87c4130256f765e46de3442eb2e2e619840ef73456738" +checksum = "7af7e8d49719333e5eb52209417f26695c9ab2b117a82596a63a44947f97c5d6" dependencies = [ "byteorder", "kvm-bindings", @@ -1148,11 +1147,11 @@ dependencies = [ [[package]] name = "vfio_user" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed81c5ed8224d468a322e923777ed0615cad433fe61177126098af995f89cecf" +checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "libc", "log", "serde", @@ -1170,7 +1169,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "libc", "uuid", "vm-memory", @@ -1242,6 +1241,7 @@ dependencies = [ "serde", "thiserror 2.0.12", "vfio-ioctls", + "vm-memory", "vmm-sys-util", ] @@ -1288,7 +1288,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.9.0", + "bitflags 2.9.4", "block", "cfg-if", "clap", @@ -1524,7 +1524,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", ] [[package]] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index f8fa6fa4b7..ccdad23241 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -30,7 +30,7 @@ linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "mai "pe", ] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } -mshv-bindings = "0.5.2" +mshv-bindings = "0.6.0" net_util = { path = "../net_util" } seccompiler = "0.5.0" virtio-devices = { path = "../virtio-devices" } From 5790bcefee4419e597a6cd8c544d80cb0554f6a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 23:01:48 +0000 Subject: [PATCH 0168/1893] build: Bump miniz_oxide from 0.8.8 to 0.8.9 Bumps [miniz_oxide](https://github.com/Frommi/miniz_oxide) from 0.8.8 to 0.8.9. - [Changelog](https://github.com/Frommi/miniz_oxide/blob/master/CHANGELOG.md) - [Commits](https://github.com/Frommi/miniz_oxide/commits) --- updated-dependencies: - dependency-name: miniz_oxide dependency-version: 0.8.9 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0111343fa8..0ee00fa852 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1210,9 +1210,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] From 061351d82d15fe92ce84790c4881f85fbef9d094 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 15 Aug 2025 13:54:48 +0200 Subject: [PATCH 0169/1893] build: upgrade whole* workspace to Rust edition 2024 This upgrades the Cargo workspace to Rust edition 2024 to keep the code base clean and up to date. The commit only contains the adjustments to the Cargo.toml files and basic compiler error fixes. Also, this commit includes new SAFETY comments as discussed in [1]. The changes were not automatically fixed by `cargo fix --edition` but needed manual adjustments. Apart from that, all formatting and clippy adjustments follow in subsequent commits. * As only exception, workspace member net_gen sticks to edition 2021 for now as discussed in [0]. [0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7295#discussion_r2310851041 [1] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7256#issuecomment-3271888674 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 3 ++- api_client/Cargo.toml | 2 +- arch/Cargo.toml | 2 +- block/Cargo.toml | 2 +- devices/Cargo.toml | 2 +- devices/src/pvmemcontrol.rs | 3 ++- event_monitor/Cargo.toml | 2 +- hypervisor/Cargo.toml | 2 +- net_gen/Cargo.toml | 1 + net_util/Cargo.toml | 2 +- net_util/src/tap.rs | 9 ++++++--- option_parser/Cargo.toml | 2 +- pci/Cargo.toml | 2 +- pci/src/vfio.rs | 6 +++--- performance-metrics/Cargo.toml | 2 +- rate_limiter/Cargo.toml | 2 +- serial_buffer/Cargo.toml | 2 +- test_infra/Cargo.toml | 2 +- tracer/Cargo.toml | 2 +- vhost_user_block/Cargo.toml | 2 +- vhost_user_net/Cargo.toml | 2 +- virtio-devices/Cargo.toml | 2 +- virtio-devices/src/iommu.rs | 6 +++--- vm-allocator/Cargo.toml | 2 +- vm-device/Cargo.toml | 2 +- vm-migration/Cargo.toml | 2 +- vm-virtio/Cargo.toml | 2 +- vmm/Cargo.toml | 2 +- vmm/src/clone3.rs | 3 ++- vmm/src/device_manager.rs | 2 +- vmm/src/sigwinch_listener.rs | 18 ++++++++++++------ 31 files changed, 54 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a0d56a439..9f78b3f8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ authors = ["The Cloud Hypervisor Authors"] build = "build.rs" default-run = "cloud-hypervisor" description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV" -edition = "2021" +edition = "2024" homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" license = "Apache-2.0 AND BSD-3-Clause" name = "cloud-hypervisor" @@ -102,6 +102,7 @@ members = [ "vm-virtio", "vmm", ] +package.edition = "2024" [workspace.dependencies] # rust-vmm crates diff --git a/api_client/Cargo.toml b/api_client/Cargo.toml index 630f1b4c44..429ecbf927 100644 --- a/api_client/Cargo.toml +++ b/api_client/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "api_client" version = "0.1.0" diff --git a/arch/Cargo.toml b/arch/Cargo.toml index 4739c14f36..3bd32affb1 100644 --- a/arch/Cargo.toml +++ b/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Chromium OS Authors"] -edition = "2021" +edition.workspace = true name = "arch" version = "0.1.0" diff --git a/block/Cargo.toml b/block/Cargo.toml index 9e0505921e..db4ac9a6b3 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "block" version = "0.1.0" diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 2b985085f4..bf620eca90 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Chromium OS Authors"] -edition = "2021" +edition.workspace = true name = "devices" version = "0.1.0" diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index d119a21a1a..ff2190933c 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -137,7 +137,8 @@ impl PvmemcontrolTransport { } unsafe fn as_register(self) -> PvmemcontrolTransportRegister { - self.payload.register + // SAFETY: We access initialized data. + unsafe { self.payload.register } } } diff --git a/event_monitor/Cargo.toml b/event_monitor/Cargo.toml index af63335046..b2b7a4e48d 100644 --- a/event_monitor/Cargo.toml +++ b/event_monitor/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "event_monitor" version = "0.1.0" diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index bdfefdeefd..c756cf9fc1 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Microsoft Authors"] -edition = "2021" +edition.workspace = true license = "Apache-2.0 OR BSD-3-Clause" name = "hypervisor" version = "0.1.0" diff --git a/net_gen/Cargo.toml b/net_gen/Cargo.toml index c0edc11559..b1443c1f29 100644 --- a/net_gen/Cargo.toml +++ b/net_gen/Cargo.toml @@ -1,6 +1,7 @@ [package] authors = ["The Chromium OS Authors"] edition = "2021" +#edition.workspace = true name = "net_gen" version = "0.1.0" diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml index 74ad342085..8f5df72225 100644 --- a/net_util/Cargo.toml +++ b/net_util/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Chromium OS Authors"] -edition = "2021" +edition.workspace = true name = "net_util" version = "0.1.0" diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 533d6a45b2..591dbc4912 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -133,7 +133,8 @@ impl Tap { /// The caller should ensure to pass a valid file descriptor and valid /// arguments for the `ioctl()` syscall. unsafe fn ioctl_with_mut_ref(fd: &F, req: c_ulong, arg: &mut T) -> Result<()> { - let ret = ioctl_with_mut_ref(fd, req, arg); + // SAFETY: file descriptor is valid and return value is checked + let ret = unsafe { ioctl_with_mut_ref(fd, req, arg) }; if ret < 0 { return Err(Error::IoctlError(req, IoError::last_os_error())); } @@ -145,7 +146,8 @@ impl Tap { /// The caller should ensure to pass a valid file descriptor and valid /// arguments for the `ioctl()` syscall. unsafe fn ioctl_with_ref(fd: &F, req: c_ulong, arg: &T) -> Result<()> { - let ret = ioctl_with_ref(fd, req, arg); + // SAFETY: file descriptor is valid and return value is checked + let ret = unsafe { ioctl_with_ref(fd, req, arg) }; if ret < 0 { return Err(Error::IoctlError(req, IoError::last_os_error())); } @@ -157,7 +159,8 @@ impl Tap { /// The caller should ensure to pass a valid file descriptor and valid /// arguments for the `ioctl()` syscall. unsafe fn ioctl_with_val(fd: &F, req: c_ulong, arg: c_ulong) -> Result<()> { - let ret = ioctl_with_val(fd, req, arg); + // SAFETY: file descriptor is valid and return value is checked + let ret = unsafe { ioctl_with_val(fd, req, arg) }; if ret < 0 { return Err(Error::IoctlError(req, IoError::last_os_error())); } diff --git a/option_parser/Cargo.toml b/option_parser/Cargo.toml index 2b6d0fe110..abacf51ddd 100644 --- a/option_parser/Cargo.toml +++ b/option_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "option_parser" version = "0.1.0" diff --git a/pci/Cargo.toml b/pci/Cargo.toml index 49618b194b..e1d631c348 100644 --- a/pci/Cargo.toml +++ b/pci/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Samuel Ortiz "] -edition = "2021" +edition.workspace = true name = "pci" version = "0.1.0" diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 660f27bd4e..42048b8f5f 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -190,7 +190,7 @@ pub(crate) struct Interrupt { impl Interrupt { fn update_msi(&mut self, offset: u64, data: &[u8]) -> Option { - if let Some(ref mut msi) = &mut self.msi { + if let Some(msi) = &mut self.msi { let action = msi.update(offset, data); return action; } @@ -199,7 +199,7 @@ impl Interrupt { } fn update_msix(&mut self, offset: u64, data: &[u8]) -> Option { - if let Some(ref mut msix) = &mut self.msix { + if let Some(msix) = &mut self.msix { let action = msix.update(offset, data); return action; } @@ -237,7 +237,7 @@ impl Interrupt { } fn msix_write_table(&mut self, offset: u64, data: &[u8]) { - if let Some(ref mut msix) = &mut self.msix { + if let Some(msix) = &mut self.msix { let offset = offset - u64::from(msix.cap.table_offset()); msix.bar.write_table(offset, data) } diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index 2403913767..00e6e74682 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cloud Hypervisor Authors"] build = "../build.rs" -edition = "2021" +edition.workspace = true name = "performance-metrics" version = "0.1.0" diff --git a/rate_limiter/Cargo.toml b/rate_limiter/Cargo.toml index 6c6cdd650e..3067c695bb 100644 --- a/rate_limiter/Cargo.toml +++ b/rate_limiter/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition.workspace = true name = "rate_limiter" version = "0.1.0" diff --git a/serial_buffer/Cargo.toml b/serial_buffer/Cargo.toml index 2c3993cff4..0691b8a3b7 100644 --- a/serial_buffer/Cargo.toml +++ b/serial_buffer/Cargo.toml @@ -1,5 +1,5 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "serial_buffer" version = "0.1.0" diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index 37c6f38454..fe3cba8eea 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "test_infra" version = "0.1.0" diff --git a/tracer/Cargo.toml b/tracer/Cargo.toml index 368520870f..bdcf559695 100644 --- a/tracer/Cargo.toml +++ b/tracer/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "tracer" version = "0.1.0" diff --git a/vhost_user_block/Cargo.toml b/vhost_user_block/Cargo.toml index ea1acf12a3..c2e7385668 100644 --- a/vhost_user_block/Cargo.toml +++ b/vhost_user_block/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cloud Hypervisor Authors"] build = "../build.rs" -edition = "2021" +edition.workspace = true name = "vhost_user_block" version = "0.1.0" diff --git a/vhost_user_net/Cargo.toml b/vhost_user_net/Cargo.toml index f84eae8d18..6cd316e9fe 100644 --- a/vhost_user_net/Cargo.toml +++ b/vhost_user_net/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cloud Hypervisor Authors"] build = "../build.rs" -edition = "2021" +edition.workspace = true name = "vhost_user_net" version = "0.1.0" diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index e13e98f759..64a60910ca 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "virtio-devices" version = "0.1.0" diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index cdf9e59087..df310823af 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -489,7 +489,7 @@ impl Request { .write() .unwrap() .iter() - .filter(|(_, &d)| d == domain_id) + .filter(|&(_, &d)| d == domain_id) .map(|(&e, _)| e) .collect(); @@ -553,7 +553,7 @@ impl Request { .write() .unwrap() .iter() - .filter(|(_, &d)| d == domain_id) + .filter(|&(_, &d)| d == domain_id) .map(|(&e, _)| e) .collect(); @@ -669,7 +669,7 @@ fn detach_endpoint_from_domain( .write() .unwrap() .iter() - .filter(|(_, &d)| d == domain_id) + .filter(|&(_, &d)| d == domain_id) .count() == 0 { diff --git a/vm-allocator/Cargo.toml b/vm-allocator/Cargo.toml index 4f546058e1..e77e877917 100644 --- a/vm-allocator/Cargo.toml +++ b/vm-allocator/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Chromium OS Authors"] -edition = "2021" +edition.workspace = true name = "vm-allocator" version = "0.1.0" diff --git a/vm-device/Cargo.toml b/vm-device/Cargo.toml index ca1d38807d..80ed1489a3 100644 --- a/vm-device/Cargo.toml +++ b/vm-device/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "vm-device" version = "0.1.0" diff --git a/vm-migration/Cargo.toml b/vm-migration/Cargo.toml index de10ebdb33..7a8c9337b3 100644 --- a/vm-migration/Cargo.toml +++ b/vm-migration/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "vm-migration" version = "0.1.0" diff --git a/vm-virtio/Cargo.toml b/vm-virtio/Cargo.toml index b22a2f5551..5f195af492 100644 --- a/vm-virtio/Cargo.toml +++ b/vm-virtio/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "vm-virtio" version = "0.1.0" diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 7793357e2d..b931193a19 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Cloud Hypervisor Authors"] -edition = "2021" +edition.workspace = true name = "vmm" version = "0.1.0" diff --git a/vmm/src/clone3.rs b/vmm/src/clone3.rs index 0ab08126e1..fc273680f8 100644 --- a/vmm/src/clone3.rs +++ b/vmm/src/clone3.rs @@ -33,5 +33,6 @@ pub struct clone_args { /// - On error: `-1` and `errno` is set #[must_use] pub unsafe fn clone3(args: &mut clone_args, size: size_t) -> c_long { - syscall(SYS_clone3, args, size) + // SAFETY: parameters are assumed to be valid + unsafe { syscall(SYS_clone3, args, size) } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 0ad2ab7ca4..256047e877 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3373,7 +3373,7 @@ impl DeviceManager { let mut devices = Vec::new(); let mut vsock = self.config.lock().unwrap().vsock.clone(); - if let Some(ref mut vsock_cfg) = &mut vsock { + if let Some(vsock_cfg) = &mut vsock { devices.push(self.make_virtio_vsock_device(vsock_cfg)?); } self.config.lock().unwrap().vsock = vsock; diff --git a/vmm/src/sigwinch_listener.rs b/vmm/src/sigwinch_listener.rs index 4d7aebdca3..104a680a63 100644 --- a/vmm/src/sigwinch_listener.rs +++ b/vmm/src/sigwinch_listener.rs @@ -83,7 +83,8 @@ unsafe fn close_fds_fallback(keep_fds: &BTreeSet) { .collect(); for fd in open_fds.difference(keep_fds) { - close(*fd); + // SAFETY: The FD is valid + unsafe { close(*fd) }; } } @@ -108,12 +109,14 @@ unsafe fn close_unused_fds(keep_fds: &mut [RawFd]) { continue; } - if syscall(SYS_close_range, first, last, 0) == -1 { + // SAFETY: FDs are valid + if unsafe { syscall(SYS_close_range, first, last, 0) } == -1 { // The kernel might be too old to have close_range, in // which case we need to fall back to an uglier method. let e = io::Error::last_os_error(); if e.raw_os_error() == Some(ENOSYS) { - return close_fds_fallback(&keep_fds.iter().copied().collect()); + // SAFETY: FDs are valid + return unsafe { close_fds_fallback(&keep_fds.iter().copied().collect()) }; } panic!("close_range: {e}"); @@ -212,7 +215,8 @@ unsafe fn clone_clear_sighand() -> io::Result { ..Default::default() }; args.flags |= CLONE_CLEAR_SIGHAND; - let r = clone3(&mut args, size_of::()); + // SAFETY: parameters are assumed to be valid + let r = unsafe { clone3(&mut args, size_of::()) }; if r != -1 { return Ok(r.try_into().unwrap()); } @@ -223,13 +227,15 @@ unsafe fn clone_clear_sighand() -> io::Result { // If CLONE_CLEAR_SIGHAND isn't available, fall back to resetting // all the signal handlers one by one. - let r = fork(); + // SAFETY: trivially safe, and we check the return value. + let r = unsafe { fork() }; if r == -1 { return Err(io::Error::last_os_error()); } if r == 0 { for signum in 1.._NSIG { - let _ = signal(signum, SIG_DFL); + // SAFETY: trivially safe, we unset the user-space signal handler + let _ = unsafe { signal(signum, SIG_DFL) }; } } Ok(r.try_into().unwrap()) From 363273111a4428cc1e9532edad68e32951aee5cb Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 12 Aug 2025 12:59:23 +0200 Subject: [PATCH 0170/1893] build: treewide: fmt for edition 2024 `cargo +nightly fmt` Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- .rustfmt.toml | 2 +- arch/src/aarch64/mod.rs | 2 +- arch/src/lib.rs | 19 +- arch/src/riscv64/mod.rs | 2 +- arch/src/x86_64/mod.rs | 12 +- arch/src/x86_64/mptable.rs | 2 +- arch/src/x86_64/smbios.rs | 2 +- block/src/fixed_vhd.rs | 2 +- block/src/fixed_vhd_sync.rs | 2 +- block/src/lib.rs | 6 +- block/src/qcow/mod.rs | 2 +- block/src/qcow/raw_file.rs | 2 +- block/src/qcow/vec_cache.rs | 2 +- block/src/qcow_sync.rs | 2 +- block/src/raw_async.rs | 2 +- block/src/raw_async_aio.rs | 2 +- block/src/raw_sync.rs | 2 +- block/src/vhd.rs | 4 +- block/src/vhdx/mod.rs | 2 +- block/src/vhdx/vhdx_io.rs | 4 +- block/src/vhdx_sync.rs | 2 +- devices/src/acpi.rs | 4 +- devices/src/gic.rs | 2 +- devices/src/ioapic.rs | 2 +- devices/src/ivshmem.rs | 6 +- devices/src/legacy/cmos.rs | 2 +- devices/src/legacy/fw_cfg.rs | 6 +- devices/src/legacy/gpio_pl061.rs | 10 +- devices/src/legacy/rtc_pl031.rs | 10 +- devices/src/legacy/serial.rs | 2 +- devices/src/legacy/uart_pl011.rs | 2 +- devices/src/lib.rs | 2 +- devices/src/pvmemcontrol.rs | 2 +- devices/src/pvpanic.rs | 6 +- devices/src/tpm.rs | 2 +- .../src/arch/x86/emulator/instructions/mod.rs | 2 +- hypervisor/src/arch/x86/emulator/mod.rs | 10 +- hypervisor/src/cpu.rs | 8 +- hypervisor/src/hypervisor.rs | 2 +- hypervisor/src/kvm/aarch64/gic/dist_regs.rs | 2 +- hypervisor/src/kvm/aarch64/gic/icc_regs.rs | 3 +- hypervisor/src/kvm/aarch64/gic/redist_regs.rs | 10 +- hypervisor/src/kvm/aarch64/mod.rs | 4 +- hypervisor/src/kvm/mod.rs | 47 +-- hypervisor/src/kvm/riscv64/aia.rs | 2 +- hypervisor/src/kvm/riscv64/mod.rs | 4 +- hypervisor/src/kvm/x86_64/mod.rs | 11 +- hypervisor/src/lib.rs | 2 +- hypervisor/src/mshv/mod.rs | 22 +- hypervisor/src/mshv/x86_64/mod.rs | 34 +- hypervisor/src/vm.rs | 4 +- net_util/src/lib.rs | 12 +- net_util/src/open_tap.rs | 2 +- net_util/src/queue_pair.rs | 4 +- net_util/src/tap.rs | 26 +- pci/src/bus.rs | 2 +- pci/src/device.rs | 2 +- pci/src/lib.rs | 12 +- pci/src/vfio.rs | 23 +- pci/src/vfio_user.rs | 2 +- performance-metrics/src/main.rs | 2 +- performance-metrics/src/performance_tests.rs | 2 +- rate_limiter/src/group.rs | 2 +- rate_limiter/src/lib.rs | 4 +- serial_buffer/src/lib.rs | 2 +- src/bin/ch-remote.rs | 8 +- src/main.rs | 8 +- test_infra/src/lib.rs | 27 +- tests/integration.rs | 312 +++++++++++------- vhost_user_block/src/lib.rs | 4 +- vhost_user_net/src/lib.rs | 4 +- virtio-devices/src/balloon.rs | 18 +- virtio-devices/src/block.rs | 12 +- virtio-devices/src/console.rs | 6 +- virtio-devices/src/iommu.rs | 4 +- virtio-devices/src/lib.rs | 4 +- virtio-devices/src/mem.rs | 8 +- virtio-devices/src/net.rs | 10 +- virtio-devices/src/pmem.rs | 6 +- virtio-devices/src/rng.rs | 6 +- virtio-devices/src/seccomp_filters.rs | 16 +- virtio-devices/src/thread_helper.rs | 6 +- virtio-devices/src/transport/mod.rs | 2 +- virtio-devices/src/transport/pci_device.rs | 10 +- virtio-devices/src/vdpa.rs | 8 +- virtio-devices/src/vhost_user/blk.rs | 14 +- virtio-devices/src/vhost_user/fs.rs | 14 +- virtio-devices/src/vhost_user/mod.rs | 10 +- virtio-devices/src/vhost_user/net.rs | 14 +- .../src/vhost_user/vu_common_ctrl.rs | 8 +- virtio-devices/src/vsock/csm/connection.rs | 20 +- virtio-devices/src/vsock/csm/txbuf.rs | 2 +- virtio-devices/src/vsock/device.rs | 8 +- virtio-devices/src/vsock/mod.rs | 2 +- virtio-devices/src/vsock/packet.rs | 4 +- virtio-devices/src/vsock/unix/mod.rs | 2 +- virtio-devices/src/vsock/unix/muxer.rs | 2 +- virtio-devices/src/vsock/unix/muxer_killq.rs | 2 +- virtio-devices/src/vsock/unix/muxer_rxq.rs | 2 +- virtio-devices/src/watchdog.rs | 6 +- vm-allocator/src/page_size.rs | 2 +- vmm/src/acpi.rs | 6 +- vmm/src/api/dbus/mod.rs | 8 +- vmm/src/api/http/http_endpoint.rs | 2 +- vmm/src/api/http/mod.rs | 6 +- vmm/src/api/mod.rs | 4 +- vmm/src/clone3.rs | 2 +- vmm/src/config.rs | 43 ++- vmm/src/console_devices.rs | 10 +- vmm/src/cpu.rs | 50 +-- vmm/src/device_manager.rs | 38 +-- vmm/src/gdb.rs | 12 +- vmm/src/igvm/igvm_loader.rs | 12 +- vmm/src/igvm/loader.rs | 2 +- vmm/src/landlock.rs | 6 +- vmm/src/lib.rs | 140 ++++---- vmm/src/memory_manager.rs | 8 +- vmm/src/pci_segment.rs | 11 +- vmm/src/sigwinch_listener.rs | 17 +- vmm/src/vm.rs | 39 +-- vmm/src/vm_config.rs | 18 +- 121 files changed, 771 insertions(+), 653 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 754d7badfd..394a1065be 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,4 +1,4 @@ -edition = "2021" +edition = "2024" group_imports="StdExternalCrate" imports_granularity="Module" diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index f7a6c36539..f98942b83a 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -15,7 +15,7 @@ use std::sync::{Arc, Mutex}; use hypervisor::arch::aarch64::gic::Vgic; use hypervisor::arch::aarch64::regs::MPIDR_EL1; -use log::{log_enabled, Level}; +use log::{Level, log_enabled}; use thiserror::Error; use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic}; diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 2413fe2235..36fa20f13c 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -81,9 +81,9 @@ pub mod aarch64; #[cfg(target_arch = "aarch64")] pub use aarch64::{ - arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt, - get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, - layout::IRQ_BASE, uefi, EntryPoint, _NSIG, + _NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu, + fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout, + layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi, }; /// Module for riscv64 related functionality. @@ -92,9 +92,9 @@ pub mod riscv64; #[cfg(target_arch = "riscv64")] pub use riscv64::{ - arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt, - get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, - layout::IRQ_BASE, uefi, EntryPoint, _NSIG, + _NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu, + fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout, + layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi, }; #[cfg(target_arch = "x86_64")] @@ -102,10 +102,9 @@ pub mod x86_64; #[cfg(target_arch = "x86_64")] pub use x86_64::{ - 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, CpuidConfig, CpuidFeatureEntry, - EntryPoint, _NSIG, + _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, }; /// Safe wrapper for `sysconf(_SC_PAGESIZE)`. diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index 62554bd1a7..6a0342b3cd 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -15,7 +15,7 @@ use std::fmt::Debug; use std::sync::{Arc, Mutex}; use hypervisor::arch::riscv64::aia::Vaia; -use log::{log_enabled, Level}; +use log::{Level, log_enabled}; use thiserror::Error; use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic}; diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 83cb0876c0..71edd4508e 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -14,7 +14,7 @@ mod mptable; pub mod regs; use std::mem; -use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX}; +use hypervisor::arch::x86::{CPUID_FLAG_VALID_INDEX, CpuIdEntry}; use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError}; use linux_loader::loader::bootparam::{boot_params, setup_header}; use linux_loader::loader::elf::start_info::{ @@ -525,9 +525,13 @@ impl CpuidFeatureEntry { error!( "Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \ compatible_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.", - entry.function, entry.index, entry.feature_reg, - entry.compatible_check, src_vm_feature, dest_vm_feature - ); + entry.function, + entry.index, + entry.feature_reg, + entry.compatible_check, + src_vm_feature, + dest_vm_feature + ); compatible = false; } diff --git a/arch/src/x86_64/mptable.rs b/arch/src/x86_64/mptable.rs index d688e41374..2e2669b38a 100644 --- a/arch/src/x86_64/mptable.rs +++ b/arch/src/x86_64/mptable.rs @@ -12,9 +12,9 @@ use thiserror::Error; use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError}; use super::MAX_SUPPORTED_CPUS_LEGACY; +use crate::GuestMemoryMmap; use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START}; use crate::x86_64::{get_x2apic_id, mpspec}; -use crate::GuestMemoryMmap; // This is a workaround to the Rust enforcement specifying that any implementation of a foreign // trait (in this case `ByteValued`) where: diff --git a/arch/src/x86_64/smbios.rs b/arch/src/x86_64/smbios.rs index 55a7df1e72..7d867a43c6 100644 --- a/arch/src/x86_64/smbios.rs +++ b/arch/src/x86_64/smbios.rs @@ -12,8 +12,8 @@ use thiserror::Error; use uuid::Uuid; use vm_memory::{Address, ByteValued, Bytes, GuestAddress}; -use crate::layout::SMBIOS_START; use crate::GuestMemoryMmap; +use crate::layout::SMBIOS_START; #[derive(Debug, Error)] pub enum Error { diff --git a/block/src/fixed_vhd.rs b/block/src/fixed_vhd.rs index 22ef4dd80d..379005ae28 100644 --- a/block/src/fixed_vhd.rs +++ b/block/src/fixed_vhd.rs @@ -6,8 +6,8 @@ use std::fs::File; use std::io::{Read, Seek, SeekFrom, Write}; use std::os::unix::io::{AsRawFd, RawFd}; -use crate::vhd::VhdFooter; use crate::BlockBackend; +use crate::vhd::VhdFooter; #[derive(Debug)] pub struct FixedVhd { diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index b1f2118f19..c125710698 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -7,12 +7,12 @@ 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; -use crate::BlockBackend; pub struct FixedVhdDiskSync(FixedVhd); diff --git a/block/src/lib.rs b/block/src/lib.rs index 3cca348b3b..5599258e3e 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -31,7 +31,7 @@ pub mod vhd; pub mod vhdx; pub mod vhdx_sync; -use std::alloc::{alloc_zeroed, dealloc, Layout}; +use std::alloc::{Layout, alloc_zeroed, dealloc}; use std::collections::VecDeque; use std::fmt::Debug; use std::fs::File; @@ -44,8 +44,8 @@ use std::time::Instant; use std::{cmp, result}; #[cfg(feature = "io_uring")] -use io_uring::{opcode, IoUring, Probe}; -use libc::{ioctl, S_IFBLK, S_IFMT}; +use io_uring::{IoUring, Probe, opcode}; +use libc::{S_IFBLK, S_IFMT, ioctl}; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use thiserror::Error; diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 4d84918e97..da68146919 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -24,11 +24,11 @@ use vmm_sys_util::file_traits::{FileSetLen, FileSync}; use vmm_sys_util::seek_hole::SeekHole; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; +use crate::BlockBackend; use crate::qcow::qcow_raw_file::QcowRawFile; pub use crate::qcow::raw_file::RawFile; use crate::qcow::refcount::RefCount; use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache}; -use crate::BlockBackend; /// Nesting depth limit for disk formats that can open other disk files. const MAX_NESTING_DEPTH: u32 = 10; diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index 67bc99fca6..f0eff54df3 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -8,7 +8,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use std::alloc::{alloc_zeroed, dealloc, Layout}; +use std::alloc::{Layout, alloc_zeroed, dealloc}; use std::fs::{File, Metadata}; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::os::unix::io::{AsRawFd, RawFd}; diff --git a/block/src/qcow/vec_cache.rs b/block/src/qcow/vec_cache.rs index 67068fdded..76e5d44430 100644 --- a/block/src/qcow/vec_cache.rs +++ b/block/src/qcow/vec_cache.rs @@ -4,8 +4,8 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use std::collections::hash_map::IterMut; use std::collections::HashMap; +use std::collections::hash_map::IterMut; use std::io; use std::ops::{Index, IndexMut}; use std::slice::SliceIndex; diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 332db24868..cd6a1fb774 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -9,11 +9,11 @@ use std::os::fd::AsRawFd; use vmm_sys_util::eventfd::EventFd; +use crate::AsyncAdaptor; use crate::async_io::{ AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; use crate::qcow::{QcowFile, RawFile, Result as QcowResult}; -use crate::AsyncAdaptor; pub struct QcowDiskSync { qcow_file: QcowFile, diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index b3c9882fbb..1a582073b0 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -6,7 +6,7 @@ use std::fs::File; use std::io::{Error, Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; -use io_uring::{opcode, types, IoUring}; +use io_uring::{IoUring, opcode, types}; use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 9ef0c62619..9a74fa41d7 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -12,10 +12,10 @@ use std::os::unix::io::{AsRawFd, RawFd}; use vmm_sys_util::aio; use vmm_sys_util::eventfd::EventFd; +use crate::DiskTopology; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::DiskTopology; pub struct RawFileDiskAio { file: File, diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 54ba1acca6..6b98147e19 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -9,10 +9,10 @@ use std::os::unix::io::{AsRawFd, RawFd}; use vmm_sys_util::eventfd::EventFd; +use crate::DiskTopology; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::DiskTopology; pub struct RawFileDiskSync { file: File, diff --git a/block/src/vhd.rs b/block/src/vhd.rs index 2cc65ca0b9..6659ed0385 100644 --- a/block/src/vhd.rs +++ b/block/src/vhd.rs @@ -5,7 +5,7 @@ use std::fs::File; use std::io::{Seek, SeekFrom}; -use crate::{read_aligned_block_size, DiskTopology}; +use crate::{DiskTopology, read_aligned_block_size}; #[derive(Clone, Copy)] pub struct VhdFooter { @@ -123,7 +123,7 @@ mod tests { use vmm_sys_util::tempfile::TempFile; - use super::{is_fixed_vhd, VhdFooter}; + use super::{VhdFooter, is_fixed_vhd}; fn valid_fixed_vhd_footer() -> Vec { vec![ diff --git a/block/src/vhdx/mod.rs b/block/src/vhdx/mod.rs index 45974c5a3f..d46905d934 100644 --- a/block/src/vhdx/mod.rs +++ b/block/src/vhdx/mod.rs @@ -12,11 +12,11 @@ use remain::sorted; use thiserror::Error; use uuid::Uuid; +use crate::BlockBackend; use crate::vhdx::vhdx_bat::{BatEntry, VhdxBatError}; use crate::vhdx::vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError}; use crate::vhdx::vhdx_io::VhdxIoError; use crate::vhdx::vhdx_metadata::{DiskSpec, VhdxMetadataError}; -use crate::BlockBackend; mod vhdx_bat; mod vhdx_header; diff --git a/block/src/vhdx/vhdx_io.rs b/block/src/vhdx/vhdx_io.rs index 30e3837876..14feac8d9d 100644 --- a/block/src/vhdx/vhdx_io.rs +++ b/block/src/vhdx/vhdx_io.rs @@ -35,9 +35,7 @@ pub enum VhdxIoError { pub type Result = std::result::Result; macro_rules! align { - ($n:expr, $align:expr) => {{ - $n.div_ceil($align) * $align - }}; + ($n:expr, $align:expr) => {{ $n.div_ceil($align) * $align }}; } #[derive(Default)] diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index fd5888e638..01bcbf5e7f 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -8,11 +8,11 @@ use std::os::fd::AsRawFd; use vmm_sys_util::eventfd::EventFd; +use crate::AsyncAdaptor; use crate::async_io::{ AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; use crate::vhdx::{Result as VhdxResult, Vhdx}; -use crate::AsyncAdaptor; pub struct VhdxDiskSync { vhdx_file: Vhdx, diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs index 2a38f5974c..229b67be54 100644 --- a/devices/src/acpi.rs +++ b/devices/src/acpi.rs @@ -8,9 +8,9 @@ use std::sync::{Arc, Barrier}; use std::thread; use std::time::Instant; -use acpi_tables::{aml, Aml, AmlSink}; -use vm_device::interrupt::InterruptSourceGroup; +use acpi_tables::{Aml, AmlSink, aml}; use vm_device::BusDevice; +use vm_device::interrupt::InterruptSourceGroup; use vm_memory::GuestAddress; use vmm_sys_util::eventfd::EventFd; diff --git a/devices/src/gic.rs b/devices/src/gic.rs index dcae0be375..a157c3f25d 100644 --- a/devices/src/gic.rs +++ b/devices/src/gic.rs @@ -9,8 +9,8 @@ use std::sync::{Arc, Mutex}; use anyhow::anyhow; use arch::layout; -use hypervisor::arch::aarch64::gic::{GicState, Vgic, VgicConfig}; use hypervisor::CpuState; +use hypervisor::arch::aarch64::gic::{GicState, Vgic, VgicConfig}; use vm_device::interrupt::{ InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup, LegacyIrqSourceConfig, MsiIrqGroupConfig, diff --git a/devices/src/ioapic.rs b/devices/src/ioapic.rs index 7adbe4f66c..97932f016d 100644 --- a/devices/src/ioapic.rs +++ b/devices/src/ioapic.rs @@ -14,11 +14,11 @@ use std::sync::{Arc, Barrier}; use byteorder::{ByteOrder, LittleEndian}; use serde::{Deserialize, Serialize}; +use vm_device::BusDevice; use vm_device::interrupt::{ InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup, MsiIrqGroupConfig, MsiIrqSourceConfig, }; -use vm_device::BusDevice; use vm_memory::GuestAddress; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vmm_sys_util::eventfd::EventFd; diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs index fc7c88de31..50c056edf3 100644 --- a/devices/src/ivshmem.rs +++ b/devices/src/ivshmem.rs @@ -12,9 +12,9 @@ use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; use byteorder::{ByteOrder, LittleEndian}; use pci::{ - BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, - PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass, - PCI_CONFIGURATION_ID, + BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable, + PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, + PciSubclass, }; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/devices/src/legacy/cmos.rs b/devices/src/legacy/cmos.rs index 386281c67f..b0e140acc3 100644 --- a/devices/src/legacy/cmos.rs +++ b/devices/src/legacy/cmos.rs @@ -12,7 +12,7 @@ use std::{mem, thread}; // https://github.com/rust-lang/libc/issues/1848 #[cfg_attr(target_env = "musl", allow(deprecated))] use libc::time_t; -use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME}; +use libc::{CLOCK_REALTIME, clock_gettime, gmtime_r, timespec, tm}; use vm_device::BusDevice; use vmm_sys_util::eventfd::EventFd; diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 02c52c707f..00b5bd7450 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -20,6 +20,7 @@ use std::{ }; use acpi_tables::rsdp::Rsdp; +use arch::RegionType; #[cfg(target_arch = "aarch64")] use arch::aarch64::layout::{ MEM_32BIT_DEVICES_START, MEM_32BIT_RESERVED_START, RAM_64BIT_START, RAM_START as HIGH_RAM_START, @@ -29,7 +30,6 @@ use arch::layout::{ EBDA_START, HIGH_RAM_START, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_32BIT_RESERVED_START, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START, RAM_64BIT_START, }; -use arch::RegionType; use bitfield_struct::bitfield; #[cfg(target_arch = "x86_64")] use linux_loader::bootparam::boot_params; @@ -752,7 +752,9 @@ impl BusDevice for FwCfg { data.copy_from_slice(&addr_lo.to_be_bytes()); } _ => { - debug!("fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}."); + debug!( + "fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}." + ); } }; } diff --git a/devices/src/legacy/gpio_pl061.rs b/devices/src/legacy/gpio_pl061.rs index c7c66341a7..3a61238dd2 100644 --- a/devices/src/legacy/gpio_pl061.rs +++ b/devices/src/legacy/gpio_pl061.rs @@ -12,8 +12,8 @@ use std::{io, result}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use vm_device::interrupt::InterruptSourceGroup; use vm_device::BusDevice; +use vm_device::interrupt::InterruptSourceGroup; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use crate::{read_le_u32, write_le_u32}; @@ -28,10 +28,10 @@ const GPIORIE: u64 = 0x414; // Raw Interrupt Status Register const GPIOMIS: u64 = 0x418; // Masked Interrupt Status Register const GPIOIC: u64 = 0x41c; // Interrupt Clear Register const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register - // From 0x424 to 0xFDC => reserved space. - // From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers. - // These registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral. - // We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array. +// From 0x424 to 0xFDC => reserved space. +// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers. +// These registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral. +// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array. const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1]; // ID Margins const GPIO_ID_LOW: u64 = 0xfe0; diff --git a/devices/src/legacy/rtc_pl031.rs b/devices/src/legacy/rtc_pl031.rs index 98bca77af2..9ff0c8c1b1 100644 --- a/devices/src/legacy/rtc_pl031.rs +++ b/devices/src/legacy/rtc_pl031.rs @@ -31,11 +31,11 @@ const RTCIMSC: u64 = 0x10; // Interrupt Mask Set or Clear Register. const RTCRIS: u64 = 0x14; // Raw Interrupt Status. const RTCMIS: u64 = 0x18; // Masked Interrupt Status. const RTCICR: u64 = 0x1c; // Interrupt Clear Register. - // From 0x020 to 0xFDC => reserved space. - // From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers. - // AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity - // of these devices (i.e look at the `amba_device_try_add` function). - // We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array. +// From 0x020 to 0xFDC => reserved space. +// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers. +// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity +// of these devices (i.e look at the `amba_device_try_add` function). +// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array. const PL031_ID: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1]; // We are only interested in the margins. const AMBA_ID_LOW: u64 = 0xFE0; diff --git a/devices/src/legacy/serial.rs b/devices/src/legacy/serial.rs index 973c96b0c5..cbfb2c10b9 100644 --- a/devices/src/legacy/serial.rs +++ b/devices/src/legacy/serial.rs @@ -10,8 +10,8 @@ use std::sync::{Arc, Barrier}; use std::{io, result}; use serde::{Deserialize, Serialize}; -use vm_device::interrupt::InterruptSourceGroup; use vm_device::BusDevice; +use vm_device::interrupt::InterruptSourceGroup; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vmm_sys_util::errno::Result; diff --git a/devices/src/legacy/uart_pl011.rs b/devices/src/legacy/uart_pl011.rs index b5603808bf..364dd59278 100644 --- a/devices/src/legacy/uart_pl011.rs +++ b/devices/src/legacy/uart_pl011.rs @@ -13,8 +13,8 @@ use std::{io, result}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use vm_device::interrupt::InterruptSourceGroup; use vm_device::BusDevice; +use vm_device::interrupt::InterruptSourceGroup; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use crate::{read_le_u32, write_le_u32}; diff --git a/devices/src/lib.rs b/devices/src/lib.rs index d7ac32d9d5..0c4bfb8ca4 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -37,7 +37,7 @@ pub mod tpm; pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice}; #[cfg(feature = "ivshmem")] pub use self::ivshmem::IvshmemDevice; -pub use self::pvpanic::{PvPanicDevice, PVPANIC_DEVICE_MMIO_SIZE}; +pub use self::pvpanic::{PVPANIC_DEVICE_MMIO_SIZE, PvPanicDevice}; bitflags! { pub struct AcpiNotificationFlags: u8 { diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index ff2190933c..2977a9a527 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -520,7 +520,7 @@ impl PvmemcontrolBusDevice { ret_value: get_page_size().into(), arg0: MAJOR_VERSION.into(), arg1: MINOR_VERSION.into(), - }) + }); } FunctionCode::Dontneed => self.madvise(addr, length, libc::MADV_DONTNEED), FunctionCode::Remove => self.madvise(addr, length, libc::MADV_REMOVE), diff --git a/devices/src/pvpanic.rs b/devices/src/pvpanic.rs index 98e7bfa9cd..4fd61188b0 100644 --- a/devices/src/pvpanic.rs +++ b/devices/src/pvpanic.rs @@ -9,9 +9,9 @@ use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; use pci::{ - BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, - PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass, - PCI_CONFIGURATION_ID, + BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable, + PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, + PciSubclass, }; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs index c6ed5ce0a9..4219f58308 100644 --- a/devices/src/tpm.rs +++ b/devices/src/tpm.rs @@ -12,8 +12,8 @@ use arch::aarch64::layout::{TPM_SIZE, TPM_START}; #[cfg(target_arch = "x86_64")] use arch::x86_64::layout::{TPM_SIZE, TPM_START}; use thiserror::Error; -use tpm::emulator::{BackendCmd, Emulator}; use tpm::TPM_CRB_BUFFER_MAX; +use tpm::emulator::{BackendCmd, Emulator}; use vm_device::BusDevice; #[derive(Error, Debug)] diff --git a/hypervisor/src/arch/x86/emulator/instructions/mod.rs b/hypervisor/src/arch/x86/emulator/instructions/mod.rs index c2d39aea09..945ce16bac 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/mod.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/mod.rs @@ -7,8 +7,8 @@ use iced_x86::*; use crate::arch::emulator::{EmulationError, PlatformEmulator, PlatformError}; -use crate::arch::x86::emulator::CpuStateManager; use crate::arch::x86::Exception; +use crate::arch::x86::emulator::CpuStateManager; pub mod cmp; pub mod mov; diff --git a/hypervisor/src/arch/x86/emulator/mod.rs b/hypervisor/src/arch/x86/emulator/mod.rs index 61bbd56fdf..9cc8ac4721 100644 --- a/hypervisor/src/arch/x86/emulator/mod.rs +++ b/hypervisor/src/arch/x86/emulator/mod.rs @@ -7,13 +7,13 @@ use anyhow::Context; use iced_x86::*; +use crate::StandardRegisters; use crate::arch::emulator::{EmulationError, EmulationResult, PlatformEmulator, PlatformError}; use crate::arch::x86::emulator::instructions::*; use crate::arch::x86::regs::{CR0_PE, EFER_LMA}; use crate::arch::x86::{ - segment_type_expand_down, segment_type_ro, Exception, SegmentRegister, SpecialRegisters, + Exception, SegmentRegister, SpecialRegisters, segment_type_expand_down, segment_type_ro, }; -use crate::StandardRegisters; #[macro_use] mod instructions; @@ -254,7 +254,7 @@ impl CpuStateManager for EmulatorCpuState { return Err(PlatformError::InvalidRegister(anyhow!( "read_reg invalid GPR {:?}", r - ))) + ))); } }; @@ -375,7 +375,7 @@ impl CpuStateManager for EmulatorCpuState { return Err(PlatformError::InvalidRegister(anyhow!( "write_reg invalid register {:?}", reg - ))) + ))); } } @@ -660,9 +660,9 @@ mod mock_vmm { use std::sync::{Arc, Mutex}; use super::*; + use crate::StandardRegisters; use crate::arch::x86::emulator::EmulatorCpuState as CpuState; use crate::arch::x86::gdt::{gdt_entry, segment_from_gdt}; - use crate::StandardRegisters; #[derive(Debug, Clone)] pub struct MockVmm { diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index 46401cc66c..5c377c5d1d 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -17,14 +17,14 @@ use thiserror::Error; #[cfg(not(target_arch = "riscv64"))] use vm_memory::GuestAddress; -#[cfg(target_arch = "x86_64")] -use crate::arch::x86::{CpuIdEntry, FpuState, LapicState, MsrEntry, SpecialRegisters}; -#[cfg(feature = "tdx")] -use crate::kvm::{TdxExitDetails, TdxExitStatus}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use crate::RegList; #[cfg(target_arch = "aarch64")] use crate::VcpuInit; +#[cfg(target_arch = "x86_64")] +use crate::arch::x86::{CpuIdEntry, FpuState, LapicState, MsrEntry, SpecialRegisters}; +#[cfg(feature = "tdx")] +use crate::kvm::{TdxExitDetails, TdxExitStatus}; use crate::{CpuState, MpState, StandardRegisters}; #[cfg(target_arch = "x86_64")] diff --git a/hypervisor/src/hypervisor.rs b/hypervisor/src/hypervisor.rs index 4fc98fb8bb..13d00fa009 100644 --- a/hypervisor/src/hypervisor.rs +++ b/hypervisor/src/hypervisor.rs @@ -13,6 +13,7 @@ use std::sync::Arc; use thiserror::Error; +use crate::HypervisorType; #[cfg(target_arch = "x86_64")] use crate::arch::x86::CpuIdEntry; #[cfg(target_arch = "x86_64")] @@ -20,7 +21,6 @@ use crate::cpu::CpuVendor; #[cfg(feature = "tdx")] use crate::kvm::TdxCapabilities; use crate::vm::Vm; -use crate::HypervisorType; #[derive(Error, Debug)] pub enum HypervisorError { diff --git a/hypervisor/src/kvm/aarch64/gic/dist_regs.rs b/hypervisor/src/kvm/aarch64/gic/dist_regs.rs index 0aa3da76de..9a3c719e7c 100644 --- a/hypervisor/src/kvm/aarch64/gic/dist_regs.rs +++ b/hypervisor/src/kvm/aarch64/gic/dist_regs.rs @@ -6,7 +6,7 @@ use kvm_ioctls::DeviceFd; use crate::arch::aarch64::gic::{Error, Result}; use crate::device::HypervisorDeviceError; use crate::kvm::kvm_bindings::{ - kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_DIST_REGS, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, + KVM_DEV_ARM_VGIC_GRP_DIST_REGS, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, kvm_device_attr, }; /* diff --git a/hypervisor/src/kvm/aarch64/gic/icc_regs.rs b/hypervisor/src/kvm/aarch64/gic/icc_regs.rs index f993581840..b084c89899 100644 --- a/hypervisor/src/kvm/aarch64/gic/icc_regs.rs +++ b/hypervisor/src/kvm/aarch64/gic/icc_regs.rs @@ -7,10 +7,11 @@ use kvm_ioctls::DeviceFd; use crate::arch::aarch64::gic::{Error, Result}; use crate::device::HypervisorDeviceError; use crate::kvm::kvm_bindings::{ - kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, KVM_REG_ARM64_SYSREG_CRM_MASK, + KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, KVM_REG_ARM64_SYSREG_CRM_MASK, KVM_REG_ARM64_SYSREG_CRM_SHIFT, KVM_REG_ARM64_SYSREG_CRN_MASK, KVM_REG_ARM64_SYSREG_CRN_SHIFT, KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP1_MASK, KVM_REG_ARM64_SYSREG_OP1_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT, + kvm_device_attr, }; const KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT: u32 = 32; diff --git a/hypervisor/src/kvm/aarch64/gic/redist_regs.rs b/hypervisor/src/kvm/aarch64/gic/redist_regs.rs index 7adc0efefc..c06818e046 100644 --- a/hypervisor/src/kvm/aarch64/gic/redist_regs.rs +++ b/hypervisor/src/kvm/aarch64/gic/redist_regs.rs @@ -4,15 +4,15 @@ use kvm_ioctls::DeviceFd; +use crate::CpuState; use crate::arch::aarch64::gic::{Error, Result}; use crate::device::HypervisorDeviceError; +use crate::kvm::VcpuKvmState; use crate::kvm::kvm_bindings::{ - kvm_device_attr, kvm_one_reg, KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, KVM_REG_ARM64, - KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, - KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT, KVM_REG_SIZE_U64, + KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, + KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, + KVM_REG_ARM64_SYSREG_OP2_SHIFT, KVM_REG_SIZE_U64, kvm_device_attr, kvm_one_reg, }; -use crate::kvm::VcpuKvmState; -use crate::CpuState; // Relevant redistributor registers that we want to save/restore. const GICR_CTLR: u32 = 0x0000; diff --git a/hypervisor/src/kvm/aarch64/mod.rs b/hypervisor/src/kvm/aarch64/mod.rs index 20fef72444..a94ed55f1c 100644 --- a/hypervisor/src/kvm/aarch64/mod.rs +++ b/hypervisor/src/kvm/aarch64/mod.rs @@ -11,8 +11,8 @@ pub mod gic; use kvm_bindings::{ - kvm_mp_state, kvm_one_reg, kvm_regs, KVM_REG_ARM_COPROC_MASK, KVM_REG_ARM_CORE, - KVM_REG_SIZE_MASK, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64, + KVM_REG_ARM_COPROC_MASK, KVM_REG_ARM_CORE, KVM_REG_SIZE_MASK, KVM_REG_SIZE_U32, + KVM_REG_SIZE_U64, kvm_mp_state, kvm_one_reg, kvm_regs, }; pub use kvm_ioctls::{Cap, Kvm}; use serde::{Deserialize, Serialize}; diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 9aaafd5b07..6becd0e411 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -29,7 +29,7 @@ use vmm_sys_util::eventfd::EventFd; #[cfg(target_arch = "aarch64")] use crate::aarch64::gic::KvmGicV3Its; #[cfg(target_arch = "aarch64")] -pub use crate::aarch64::{check_required_kvm_extensions, is_system_register, VcpuKvmState}; +pub use crate::aarch64::{VcpuKvmState, check_required_kvm_extensions, is_system_register}; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::gic::{Vgic, VgicConfig}; #[cfg(target_arch = "riscv64")] @@ -40,36 +40,37 @@ use crate::arm64_core_reg_id; use crate::riscv64::aia::KvmAiaImsics; #[cfg(target_arch = "riscv64")] pub use crate::riscv64::{ - aia::AiaImsicsState as AiaState, check_required_kvm_extensions, is_non_core_register, - VcpuKvmState, + VcpuKvmState, aia::AiaImsicsState as AiaState, check_required_kvm_extensions, + is_non_core_register, }; #[cfg(target_arch = "riscv64")] use crate::riscv64_reg_id; use crate::vm::{self, InterruptSourceConfig, VmOps}; -use crate::{cpu, hypervisor, HypervisorType}; +use crate::{HypervisorType, cpu, hypervisor}; // x86_64 dependencies #[cfg(target_arch = "x86_64")] pub mod x86_64; #[cfg(target_arch = "x86_64")] use kvm_bindings::{ - kvm_enable_cap, kvm_msr_entry, MsrList, KVM_CAP_HYPERV_SYNIC, KVM_CAP_SPLIT_IRQCHIP, - KVM_CAP_X2APIC_API, KVM_GUESTDBG_USE_HW_BP, KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK, - KVM_X2APIC_API_USE_32BIT_IDS, + KVM_CAP_HYPERV_SYNIC, KVM_CAP_SPLIT_IRQCHIP, KVM_CAP_X2APIC_API, KVM_GUESTDBG_USE_HW_BP, + KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK, KVM_X2APIC_API_USE_32BIT_IDS, MsrList, kvm_enable_cap, + kvm_msr_entry, }; #[cfg(target_arch = "x86_64")] use x86_64::check_required_kvm_extensions; #[cfg(target_arch = "x86_64")] pub use x86_64::{CpuId, ExtendedControlRegisters, MsrEntries, VcpuKvmState}; +#[cfg(target_arch = "x86_64")] +use crate::ClockData; #[cfg(target_arch = "x86_64")] use crate::arch::x86::{ - CpuIdEntry, FpuState, LapicState, MsrEntry, SpecialRegisters, XsaveState, NUM_IOAPIC_PINS, + CpuIdEntry, FpuState, LapicState, MsrEntry, NUM_IOAPIC_PINS, SpecialRegisters, XsaveState, }; -#[cfg(target_arch = "x86_64")] -use crate::ClockData; use crate::{ - CpuState, IoEventAddress, IrqRoutingEntry, MpState, StandardRegisters, UserMemoryRegion, + CpuState, IoEventAddress, IrqRoutingEntry, MpState, StandardRegisters, USER_MEMORY_REGION_LOG_DIRTY, USER_MEMORY_REGION_READ, USER_MEMORY_REGION_WRITE, + UserMemoryRegion, }; // aarch64 dependencies #[cfg(target_arch = "aarch64")] @@ -86,23 +87,23 @@ use std::mem; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] pub use kvm_bindings::kvm_vcpu_events as VcpuEvents; pub use kvm_bindings::{ - kvm_clock_data, kvm_create_device, kvm_create_device as CreateDevice, - kvm_device_attr as DeviceAttr, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_guest_debug, - kvm_irq_routing, kvm_irq_routing_entry, kvm_mp_state, kvm_run, kvm_userspace_memory_region, KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_IRQ_ROUTING_IRQCHIP, KVM_IRQ_ROUTING_MSI, - KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID, + KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID, kvm_clock_data, + kvm_create_device, kvm_create_device as CreateDevice, kvm_device_attr as DeviceAttr, + kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_guest_debug, kvm_irq_routing, kvm_irq_routing_entry, + kvm_mp_state, kvm_run, kvm_userspace_memory_region, }; #[cfg(target_arch = "aarch64")] use kvm_bindings::{ - kvm_regs, user_pt_regs, KVM_GUESTDBG_USE_HW, KVM_NR_SPSR, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, + KVM_GUESTDBG_USE_HW, KVM_NR_SPSR, KVM_REG_ARM_CORE, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_CRM_MASK, KVM_REG_ARM64_SYSREG_CRN_MASK, KVM_REG_ARM64_SYSREG_OP0_MASK, - KVM_REG_ARM64_SYSREG_OP1_MASK, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM_CORE, - KVM_REG_SIZE_U128, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64, + KVM_REG_ARM64_SYSREG_OP1_MASK, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_SIZE_U32, + KVM_REG_SIZE_U64, KVM_REG_SIZE_U128, kvm_regs, user_pt_regs, }; #[cfg(target_arch = "riscv64")] -use kvm_bindings::{kvm_riscv_core, KVM_REG_RISCV_CORE}; +use kvm_bindings::{KVM_REG_RISCV_CORE, kvm_riscv_core}; #[cfg(feature = "tdx")] -use kvm_bindings::{kvm_run__bindgen_ty_1, KVMIO}; +use kvm_bindings::{KVMIO, kvm_run__bindgen_ty_1}; pub use kvm_ioctls::{Cap, Kvm, VcpuExit}; use thiserror::Error; use vfio_ioctls::VfioDeviceFd; @@ -112,10 +113,10 @@ use vmm_sys_util::ioctl_io_nr; use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_iowr_nr}; pub use {kvm_bindings, kvm_ioctls}; -#[cfg(target_arch = "aarch64")] -use crate::arch::aarch64::regs; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use crate::RegList; +#[cfg(target_arch = "aarch64")] +use crate::arch::aarch64::regs; #[cfg(target_arch = "x86_64")] ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a); @@ -2866,7 +2867,7 @@ impl cpu::Vcpu for KvmVcpu { /// Return the list of initial MSR entries for a VCPU /// fn boot_msr_entries(&self) -> Vec { - use crate::arch::x86::{msr_index, MTRR_ENABLE, MTRR_MEM_TYPE_WB}; + use crate::arch::x86::{MTRR_ENABLE, MTRR_MEM_TYPE_WB, msr_index}; [ msr!(msr_index::MSR_IA32_SYSENTER_CS), diff --git a/hypervisor/src/kvm/riscv64/aia.rs b/hypervisor/src/kvm/riscv64/aia.rs index 607c9034c5..1aebbafbe4 100644 --- a/hypervisor/src/kvm/riscv64/aia.rs +++ b/hypervisor/src/kvm/riscv64/aia.rs @@ -7,10 +7,10 @@ use std::any::Any; use kvm_ioctls::DeviceFd; use serde::{Deserialize, Serialize}; +use crate::Vm; use crate::arch::riscv64::aia::{Error, Result, Vaia, VaiaConfig}; use crate::device::HypervisorDeviceError; use crate::kvm::KvmVm; -use crate::Vm; pub struct KvmAiaImsics { /// The KVM device for the Aia diff --git a/hypervisor/src/kvm/riscv64/mod.rs b/hypervisor/src/kvm/riscv64/mod.rs index c589b488a3..07f54efb3d 100644 --- a/hypervisor/src/kvm/riscv64/mod.rs +++ b/hypervisor/src/kvm/riscv64/mod.rs @@ -5,8 +5,8 @@ pub mod aia; use kvm_bindings::{ - kvm_mp_state, kvm_one_reg, kvm_riscv_core, KVM_REG_RISCV_CORE, KVM_REG_RISCV_TYPE_MASK, - KVM_REG_SIZE_MASK, KVM_REG_SIZE_U64, + KVM_REG_RISCV_CORE, KVM_REG_RISCV_TYPE_MASK, KVM_REG_SIZE_MASK, KVM_REG_SIZE_U64, kvm_mp_state, + kvm_one_reg, kvm_riscv_core, }; pub use kvm_ioctls::{Cap, Kvm}; use serde::{Deserialize, Serialize}; diff --git a/hypervisor/src/kvm/x86_64/mod.rs b/hypervisor/src/kvm/x86_64/mod.rs index 4cf05a1ac3..a01fb9d727 100644 --- a/hypervisor/src/kvm/x86_64/mod.rs +++ b/hypervisor/src/kvm/x86_64/mod.rs @@ -13,18 +13,17 @@ use serde::{Deserialize, Serialize}; /// Export generically-named wrappers of kvm-bindings for Unix-based platforms /// pub use { - kvm_bindings::kvm_cpuid_entry2, kvm_bindings::kvm_dtable, kvm_bindings::kvm_fpu, - kvm_bindings::kvm_lapic_state, kvm_bindings::kvm_mp_state as MpState, + kvm_bindings::CpuId, kvm_bindings::KVM_CPUID_FLAG_SIGNIFCANT_INDEX, kvm_bindings::MsrList, + kvm_bindings::Msrs as MsrEntries, kvm_bindings::kvm_cpuid_entry2, kvm_bindings::kvm_dtable, + kvm_bindings::kvm_fpu, kvm_bindings::kvm_lapic_state, kvm_bindings::kvm_mp_state as MpState, kvm_bindings::kvm_msr_entry, kvm_bindings::kvm_regs, kvm_bindings::kvm_segment, kvm_bindings::kvm_sregs, kvm_bindings::kvm_vcpu_events as VcpuEvents, kvm_bindings::kvm_xcrs as ExtendedControlRegisters, kvm_bindings::kvm_xsave, - kvm_bindings::CpuId, kvm_bindings::MsrList, kvm_bindings::Msrs as MsrEntries, - kvm_bindings::KVM_CPUID_FLAG_SIGNIFCANT_INDEX, }; use crate::arch::x86::{ - CpuIdEntry, DescriptorTable, FpuState, LapicState, MsrEntry, SegmentRegister, SpecialRegisters, - XsaveState, CPUID_FLAG_VALID_INDEX, + CPUID_FLAG_VALID_INDEX, CpuIdEntry, DescriptorTable, FpuState, LapicState, MsrEntry, + SegmentRegister, SpecialRegisters, XsaveState, }; use crate::kvm::{Cap, Kvm, KvmError, KvmResult}; diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index af383e3f3c..205691a421 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -61,7 +61,7 @@ pub use device::HypervisorDeviceError; #[cfg(all(feature = "kvm", target_arch = "aarch64"))] pub use kvm::aarch64; #[cfg(all(feature = "kvm", target_arch = "riscv64"))] -pub use kvm::{riscv64, AiaState}; +pub use kvm::{AiaState, riscv64}; pub use vm::{ DataMatch, HypervisorVmError, InterruptSourceConfig, LegacyIrqSourceConfig, MsiIrqSourceConfig, Vm, VmOps, diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 93634ada0f..bc03c44dab 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -14,7 +14,7 @@ use arc_swap::ArcSwap; use mshv_bindings::*; #[cfg(target_arch = "x86_64")] use mshv_ioctls::InterruptRequest; -use mshv_ioctls::{set_registers_64, Mshv, NoDatamatch, VcpuFd, VmFd, VmType}; +use mshv_ioctls::{Mshv, NoDatamatch, VcpuFd, VmFd, VmType, set_registers_64}; use vfio_ioctls::VfioDeviceFd; use vm::DataMatch; #[cfg(feature = "sev_snp")] @@ -32,7 +32,7 @@ use crate::arch::x86::emulator::Emulator; use crate::mshv::aarch64::emulator; use crate::mshv::emulator::MshvEmulatorContext; use crate::vm::{self, InterruptSourceConfig, VmOps}; -use crate::{cpu, hypervisor, vec_with_array_field, HypervisorType}; +use crate::{HypervisorType, cpu, hypervisor, vec_with_array_field}; #[cfg(feature = "sev_snp")] mod snp_constants; // x86_64 dependencies @@ -45,10 +45,10 @@ use std::os::unix::io::AsRawFd; #[cfg(target_arch = "aarch64")] use std::sync::Mutex; -#[cfg(target_arch = "aarch64")] -use aarch64::gic::{MshvGicV2M, BASE_SPI_IRQ}; #[cfg(target_arch = "aarch64")] pub use aarch64::VcpuMshvState; +#[cfg(target_arch = "aarch64")] +use aarch64::gic::{BASE_SPI_IRQ, MshvGicV2M}; #[cfg(feature = "sev_snp")] use igvm_defs::IGVM_VHS_SNP_ID_BLOCK; #[cfg(feature = "sev_snp")] @@ -57,7 +57,7 @@ use vmm_sys_util::eventfd::EventFd; #[cfg(target_arch = "x86_64")] pub use x86_64::*; #[cfg(target_arch = "x86_64")] -pub use x86_64::{emulator, VcpuMshvState}; +pub use x86_64::{VcpuMshvState, emulator}; /// /// Export generically-named wrappers of mshv-bindings for Unix-based platforms /// @@ -66,18 +66,18 @@ pub use { mshv_bindings::mshv_device_attr as DeviceAttr, mshv_ioctls, mshv_ioctls::DeviceFd, }; +#[cfg(target_arch = "x86_64")] +use crate::ClockData; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::gic::{Vgic, VgicConfig}; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::regs; #[cfg(target_arch = "x86_64")] use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry}; -#[cfg(target_arch = "x86_64")] -use crate::ClockData; use crate::{ - CpuState, IoEventAddress, IrqRoutingEntry, MpState, UserMemoryRegion, - USER_MEMORY_REGION_ADJUSTABLE, USER_MEMORY_REGION_EXECUTE, USER_MEMORY_REGION_READ, - USER_MEMORY_REGION_WRITE, + CpuState, IoEventAddress, IrqRoutingEntry, MpState, USER_MEMORY_REGION_ADJUSTABLE, + USER_MEMORY_REGION_EXECUTE, USER_MEMORY_REGION_READ, USER_MEMORY_REGION_WRITE, + UserMemoryRegion, }; pub const PAGE_SHIFT: usize = 12; @@ -1512,7 +1512,7 @@ impl cpu::Vcpu for MshvVcpu { /// Return the list of initial MSR entries for a VCPU /// fn boot_msr_entries(&self) -> Vec { - use crate::arch::x86::{msr_index, MTRR_ENABLE, MTRR_MEM_TYPE_WB}; + use crate::arch::x86::{MTRR_ENABLE, MTRR_MEM_TYPE_WB, msr_index}; [ msr!(msr_index::MSR_IA32_SYSENTER_CS), diff --git a/hypervisor/src/mshv/x86_64/mod.rs b/hypervisor/src/mshv/x86_64/mod.rs index 1853d234d8..a25dcc3ca7 100644 --- a/hypervisor/src/mshv/x86_64/mod.rs +++ b/hypervisor/src/mshv/x86_64/mod.rs @@ -21,16 +21,16 @@ pub mod emulator; /// Export generically-named wrappers of mshv_bindings for Unix-based platforms /// pub use { - mshv_bindings::hv_cpuid_entry, mshv_bindings::mshv_user_mem_region as MemoryRegion, - mshv_bindings::msr_entry, mshv_bindings::AllVpStateComponents, mshv_bindings::CpuId, - mshv_bindings::DebugRegisters, mshv_bindings::FloatingPointUnit, - mshv_bindings::LapicState as MshvLapicState, mshv_bindings::MiscRegs as MiscRegisters, - mshv_bindings::MsrList, mshv_bindings::Msrs as MsrEntries, mshv_bindings::Msrs, + mshv_bindings::AllVpStateComponents, mshv_bindings::CpuId, mshv_bindings::DebugRegisters, + mshv_bindings::FloatingPointUnit, mshv_bindings::LapicState as MshvLapicState, + mshv_bindings::MiscRegs as MiscRegisters, mshv_bindings::MsrList, + mshv_bindings::Msrs as MsrEntries, mshv_bindings::Msrs, mshv_bindings::SegmentRegister as MshvSegmentRegister, mshv_bindings::SpecialRegisters as MshvSpecialRegisters, mshv_bindings::StandardRegisters as MshvStandardRegisters, mshv_bindings::SuspendRegisters, mshv_bindings::TableRegister, mshv_bindings::VcpuEvents, mshv_bindings::XSave as Xsave, - mshv_bindings::Xcrs as ExtendedControlRegisters, + mshv_bindings::Xcrs as ExtendedControlRegisters, mshv_bindings::hv_cpuid_entry, + mshv_bindings::mshv_user_mem_region as MemoryRegion, mshv_bindings::msr_entry, }; #[derive(Clone, Serialize, Deserialize)] @@ -60,16 +60,18 @@ impl fmt::Display for VcpuMshvState { msr_entries[i][1] = entry.data; msr_entries[i][0] = entry.index as u64; } - write!(f, "Number of MSRs: {}: MSRs: {:#010X?}, -- VCPU Events: {:?} -- Standard registers: {:?} Special Registers: {:?} ---- Floating Point Unit: {:?} --- Extended Control Register: {:?} --- DBG: {:?} --- VP States: {:?}", - msr_entries.len(), - msr_entries, - self.vcpu_events, - self.regs, - self.sregs, - self.fpu, - self.xcrs, - self.dbg, - self.vp_states, + write!( + f, + "Number of MSRs: {}: MSRs: {:#010X?}, -- VCPU Events: {:?} -- Standard registers: {:?} Special Registers: {:?} ---- Floating Point Unit: {:?} --- Extended Control Register: {:?} --- DBG: {:?} --- VP States: {:?}", + msr_entries.len(), + msr_entries, + self.vcpu_events, + self.regs, + self.sregs, + self.fpu, + self.xcrs, + self.dbg, + self.vp_states, ) } } diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index bd9c0e6746..a2f7921314 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -20,6 +20,8 @@ use igvm_defs::IGVM_VHS_SNP_ID_BLOCK; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; +#[cfg(target_arch = "x86_64")] +use crate::ClockData; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::gic::{Vgic, VgicConfig}; #[cfg(target_arch = "riscv64")] @@ -27,8 +29,6 @@ use crate::arch::riscv64::aia::{Vaia, VaiaConfig}; #[cfg(feature = "tdx")] use crate::arch::x86::CpuIdEntry; use crate::cpu::Vcpu; -#[cfg(target_arch = "x86_64")] -use crate::ClockData; use crate::{IoEventAddress, IrqRoutingEntry, UserMemoryRegion}; /// diff --git a/net_util/src/lib.rs b/net_util/src/lib.rs index a28bcc433a..4ad7a1c77e 100644 --- a/net_util/src/lib.rs +++ b/net_util/src/lib.rs @@ -23,18 +23,18 @@ use std::{io, mem, net}; use serde::{Deserialize, Serialize}; use thiserror::Error; use virtio_bindings::virtio_net::{ - virtio_net_hdr_v1, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN, - VIRTIO_NET_F_GUEST_CSUM, VIRTIO_NET_F_GUEST_ECN, VIRTIO_NET_F_GUEST_TSO4, - VIRTIO_NET_F_GUEST_TSO6, VIRTIO_NET_F_GUEST_UFO, VIRTIO_NET_F_MAC, VIRTIO_NET_F_MQ, + VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN, VIRTIO_NET_F_GUEST_CSUM, + VIRTIO_NET_F_GUEST_ECN, VIRTIO_NET_F_GUEST_TSO4, VIRTIO_NET_F_GUEST_TSO6, + VIRTIO_NET_F_GUEST_UFO, VIRTIO_NET_F_MAC, VIRTIO_NET_F_MQ, virtio_net_hdr_v1, }; -use vm_memory::bitmap::AtomicBitmap; use vm_memory::ByteValued; +use vm_memory::bitmap::AtomicBitmap; type GuestMemoryMmap = vm_memory::GuestMemoryMmap; pub use ctrl_queue::{CtrlQueue, Error as CtrlQueueError}; -pub use mac::{MacAddr, MAC_ADDR_LEN}; -pub use open_tap::{open_tap, Error as OpenTapError}; +pub use mac::{MAC_ADDR_LEN, MacAddr}; +pub use open_tap::{Error as OpenTapError, open_tap}; pub use queue_pair::{NetCounters, NetQueuePair, NetQueuePairError, RxVirtio, TxVirtio}; pub use tap::{Error as TapError, Tap}; diff --git a/net_util/src/open_tap.rs b/net_util/src/open_tap.rs index 21c48f8392..61e763ba20 100644 --- a/net_util/src/open_tap.rs +++ b/net_util/src/open_tap.rs @@ -8,7 +8,7 @@ use std::{fs, io}; use thiserror::Error; -use super::{vnet_hdr_len, MacAddr, Tap, TapError}; +use super::{MacAddr, Tap, TapError, vnet_hdr_len}; #[derive(Error, Debug)] pub enum Error { diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index f28d759fe5..63fe677509 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -5,8 +5,8 @@ use std::io; use std::num::Wrapping; use std::os::unix::io::{AsRawFd, RawFd}; -use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicU64, Ordering}; use rate_limiter::{RateLimiter, TokenType}; use thiserror::Error; @@ -15,7 +15,7 @@ use vm_memory::bitmap::Bitmap; use vm_memory::{Bytes, GuestMemory}; use vm_virtio::{AccessPlatform, Translatable}; -use super::{register_listener, unregister_listener, vnet_hdr_len, Tap}; +use super::{Tap, register_listener, unregister_listener, vnet_hdr_len}; #[derive(Clone)] pub struct TxVirtio { diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 591dbc4912..2544b9eee7 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -15,8 +15,8 @@ use thiserror::Error; use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val}; use super::{ - create_inet_socket, create_sockaddr, create_unix_socket, vnet_hdr_len, Error as NetUtilError, - MacAddr, + Error as NetUtilError, MacAddr, create_inet_socket, create_sockaddr, create_unix_socket, + vnet_hdr_len, }; use crate::mac::MAC_ADDR_LEN; @@ -551,7 +551,7 @@ impl AsRawFd for Tap { #[cfg(test)] mod tests { use std::net::Ipv4Addr; - use std::sync::{mpsc, LazyLock, Mutex}; + use std::sync::{LazyLock, Mutex, mpsc}; use std::time::Duration; use std::{str, thread}; @@ -860,15 +860,17 @@ mod tests { // We use a separate thread to wait for the test packet because the API exposed by pnet is // blocking. This thread will be killed when the main thread exits. - let _handle = thread::spawn(move || loop { - let buf = rx.next().unwrap(); - let p = ParsedPkt::new(buf); - p.print(); - - if let Some(ref udp) = p.udp { - if payload == udp.payload() { - channel_tx.send(true).unwrap(); - break; + let _handle = thread::spawn(move || { + loop { + let buf = rx.next().unwrap(); + let p = ParsedPkt::new(buf); + p.print(); + + if let Some(ref udp) = p.udp { + if payload == udp.payload() { + channel_tx.send(true).unwrap(); + break; + } } } }); diff --git a/pci/src/bus.rs b/pci/src/bus.rs index f6f8ce2d01..fd19321de5 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -13,11 +13,11 @@ use byteorder::{ByteOrder, LittleEndian}; use thiserror::Error; use vm_device::{Bus, BusDevice, BusDeviceSync}; +use crate::PciBarConfiguration; use crate::configuration::{ PciBarRegionType, PciBridgeSubclass, PciClassCode, PciConfiguration, PciHeaderType, }; use crate::device::{BarReprogrammingParams, DeviceRelocation, Error as PciDeviceError, PciDevice}; -use crate::PciBarConfiguration; const VENDOR_ID_INTEL: u16 = 0x8086; const DEVICE_ID_INTEL_VIRT_PCIE_HOST: u16 = 0x0d57; diff --git a/pci/src/device.rs b/pci/src/device.rs index cddb30fce9..3c5b3315f8 100644 --- a/pci/src/device.rs +++ b/pci/src/device.rs @@ -12,8 +12,8 @@ use thiserror::Error; use vm_allocator::{AddressAllocator, SystemAllocator}; use vm_device::Resource; -use crate::configuration::{self, PciBarRegionType}; use crate::PciBarConfiguration; +use crate::configuration::{self, PciBarRegionType}; #[derive(Error, Debug)] pub enum Error { diff --git a/pci/src/lib.rs b/pci/src/lib.rs index 438a8ce94a..c95a38b339 100644 --- a/pci/src/lib.rs +++ b/pci/src/lib.rs @@ -24,16 +24,16 @@ use serde::de::Visitor; pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError}; pub use self::configuration::{ - PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityId, - PciClassCode, PciConfiguration, PciExpressCapabilityId, PciHeaderType, PciMassStorageSubclass, - PciNetworkControllerSubclass, PciProgrammingInterface, PciSerialBusSubClass, PciSubclass, - PCI_CONFIGURATION_ID, + PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, + PciCapabilityId, PciClassCode, PciConfiguration, PciExpressCapabilityId, PciHeaderType, + PciMassStorageSubclass, PciNetworkControllerSubclass, PciProgrammingInterface, + PciSerialBusSubClass, PciSubclass, }; pub use self::device::{ BarReprogrammingParams, DeviceRelocation, Error as PciDeviceError, PciDevice, }; -pub use self::msi::{msi_num_enabled_vectors, MsiCap, MsiConfig}; -pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_CONFIG_ID, MSIX_TABLE_ENTRY_SIZE}; +pub use self::msi::{MsiCap, MsiConfig, msi_num_enabled_vectors}; +pub use self::msix::{MSIX_CONFIG_ID, MSIX_TABLE_ENTRY_SIZE, MsixCap, MsixConfig, MsixTableEntry}; pub use self::vfio::{MmioRegion, VfioDmaMapping, VfioPciDevice, VfioPciError}; pub use self::vfio_user::{VfioUserDmaMapping, VfioUserPciDevice, VfioUserPciDeviceError}; diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 42048b8f5f..97be4a7bc1 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -14,7 +14,7 @@ use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; use byteorder::{ByteOrder, LittleEndian}; use hypervisor::HypervisorVmError; -use libc::{sysconf, _SC_PAGESIZE}; +use libc::{_SC_PAGESIZE, sysconf}; use serde::{Deserialize, Serialize}; use thiserror::Error; use vfio_bindings::bindings::vfio::*; @@ -34,13 +34,13 @@ use vm_memory::{Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestUsiz use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vmm_sys_util::eventfd::EventFd; -use crate::msi::{MsiConfigState, MSI_CONFIG_ID}; +use crate::msi::{MSI_CONFIG_ID, MsiConfigState}; use crate::msix::MsixConfigState; use crate::{ - msi_num_enabled_vectors, BarReprogrammingParams, MsiCap, MsiConfig, MsixCap, MsixConfig, - PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciBdf, PciCapabilityId, - PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciExpressCapabilityId, - PciHeaderType, PciSubclass, MSIX_CONFIG_ID, MSIX_TABLE_ENTRY_SIZE, PCI_CONFIGURATION_ID, + BarReprogrammingParams, MSIX_CONFIG_ID, MSIX_TABLE_ENTRY_SIZE, MsiCap, MsiConfig, MsixCap, + MsixConfig, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, + PciBdf, PciCapabilityId, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, + PciExpressCapabilityId, PciHeaderType, PciSubclass, msi_num_enabled_vectors, }; pub(crate) const VFIO_COMMON_ID: &str = "vfio_common"; @@ -1664,9 +1664,8 @@ impl VfioPciDevice { if !is_page_size_aligned(area.size) || !is_page_size_aligned(area.offset) { warn!( "Could not mmap sparse area that is not page size aligned (offset = 0x{:x}, size = 0x{:x})", - area.offset, - area.size, - ); + area.offset, area.size, + ); return Ok(()); } @@ -2040,9 +2039,9 @@ impl ExternalDmaMapping for VfioDmaMapping t as u64, Err(e) => { - return Err(io::Error::other( - format!("unable to retrieve user address for gpa 0x{gpa:x} from guest memory region: {e}") - )); + return Err(io::Error::other(format!( + "unable to retrieve user address for gpa 0x{gpa:x} from guest memory region: {e}" + ))); } } } else if self.mmio_regions.lock().unwrap().check_range(gpa, size) { diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index f23259f48f..7ca1d28814 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -24,7 +24,7 @@ use vm_memory::{ use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vmm_sys_util::eventfd::EventFd; -use crate::vfio::{UserMemoryRegion, Vfio, VfioCommon, VfioError, VFIO_COMMON_ID}; +use crate::vfio::{UserMemoryRegion, VFIO_COMMON_ID, Vfio, VfioCommon, VfioError}; use crate::{ BarReprogrammingParams, PciBarConfiguration, PciBdf, PciDevice, PciDeviceError, PciSubclass, VfioPciError, diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index c348155817..220532f6cb 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -9,8 +9,8 @@ extern crate test_infra; mod performance_tests; use std::process::Command; -use std::sync::mpsc::channel; use std::sync::Arc; +use std::sync::mpsc::channel; use std::time::Duration; use std::{env, fmt, thread}; diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 46eb090fcc..e29dca1743 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -12,7 +12,7 @@ use std::{fs, thread}; use test_infra::{Error as InfraError, *}; use thiserror::Error; -use crate::{mean, ImageFormat, PerformanceTestControl, PerformanceTestOverrides}; +use crate::{ImageFormat, PerformanceTestControl, PerformanceTestOverrides, mean}; #[cfg(target_arch = "x86_64")] pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw"; diff --git a/rate_limiter/src/group.rs b/rate_limiter/src/group.rs index 51e18196a8..f883115641 100644 --- a/rate_limiter/src/group.rs +++ b/rate_limiter/src/group.rs @@ -306,7 +306,7 @@ pub(crate) mod tests { use super::RateLimiterGroupHandle; use crate::group::RateLimiterGroup; - use crate::{TokenBucket, TokenType, REFILL_TIMER_INTERVAL_MS}; + use crate::{REFILL_TIMER_INTERVAL_MS, TokenBucket, TokenType}; impl RateLimiterGroupHandle { fn bandwidth(&self) -> Option { diff --git a/rate_limiter/src/lib.rs b/rate_limiter/src/lib.rs index 9d67993386..72221416f1 100644 --- a/rate_limiter/src/lib.rs +++ b/rate_limiter/src/lib.rs @@ -48,8 +48,8 @@ extern crate log; use std::io; use std::os::unix::io::{AsRawFd, RawFd}; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; use thiserror::Error; @@ -470,7 +470,7 @@ impl RateLimiter { std::io::ErrorKind::WouldBlock => { return Err(Error::SpuriousRateLimiterEvent( "Rate limiter event handler called without a present timer", - )) + )); } _ => return Err(Error::TimerFdWaitError(err)), } diff --git a/serial_buffer/src/lib.rs b/serial_buffer/src/lib.rs index 6b9182d4c7..f914f2ef54 100644 --- a/serial_buffer/src/lib.rs +++ b/serial_buffer/src/lib.rs @@ -5,8 +5,8 @@ use std::collections::VecDeque; use std::io::Write; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; const MAX_BUFFER_SIZE: usize = 1 << 20; diff --git a/src/bin/ch-remote.rs b/src/bin/ch-remote.rs index 904565e5cd..803ffc7ee9 100644 --- a/src/bin/ch-remote.rs +++ b/src/bin/ch-remote.rs @@ -13,8 +13,8 @@ use std::os::unix::net::UnixStream; use std::process; use api_client::{ - simple_api_command, simple_api_command_with_fds, simple_api_full_command, - Error as ApiClientError, + Error as ApiClientError, simple_api_command, simple_api_command_with_fds, + simple_api_full_command, }; use clap::{Arg, ArgAction, ArgMatches, Command}; use log::error; @@ -1128,7 +1128,9 @@ fn main() { process::exit(1); } _ => { - error!("Please either provide the api-socket option or dbus-service-name and dbus-object-path options"); + error!( + "Please either provide the api-socket option or dbus-service-name and dbus-object-path options" + ); process::exit(1); } }; diff --git a/src/main.rs b/src/main.rs index 8329100b6f..3c94f36d88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,22 +8,22 @@ mod test_util; use std::fs::File; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::sync::mpsc::channel; use std::sync::Mutex; +use std::sync::mpsc::channel; use std::{env, io}; use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command}; use event_monitor::event; use libc::EFD_NONBLOCK; -use log::{error, warn, LevelFilter}; +use log::{LevelFilter, error, warn}; use option_parser::OptionParser; use seccompiler::SeccompAction; use signal_hook::consts::SIGSYS; use thiserror::Error; +use vmm::api::ApiAction; #[cfg(feature = "dbus_api")] -use vmm::api::dbus::{dbus_api_graceful_shutdown, DBusApiOptions}; +use vmm::api::dbus::{DBusApiOptions, dbus_api_graceful_shutdown}; use vmm::api::http::http_api_graceful_shutdown; -use vmm::api::ApiAction; use vmm::config::{RestoreConfig, VmParams}; use vmm::landlock::{Landlock, LandlockError}; use vmm::vm_config; diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 812a3a9339..641c5a15ab 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1118,10 +1118,12 @@ impl Guest { thread::sleep(std::time::Duration::new(10, 0)); // Write something to vsock from the host - assert!(exec_host_command_status(&format!( - "echo -e \"CONNECT 16\\nHelloWorld!\" | socat - UNIX-CONNECT:{socket}" - )) - .success()); + assert!( + exec_host_command_status(&format!( + "echo -e \"CONNECT 16\\nHelloWorld!\" | socat - UNIX-CONNECT:{socket}" + )) + .success() + ); // Wait for the thread to terminate. listen_socat.join().unwrap(); @@ -1134,10 +1136,11 @@ impl Guest { #[cfg(target_arch = "x86_64")] pub fn check_nvidia_gpu(&self) { - assert!(self - .ssh_command("nvidia-smi") - .unwrap() - .contains("NVIDIA L40S")); + assert!( + self.ssh_command("nvidia-smi") + .unwrap() + .contains("NVIDIA L40S") + ); } pub fn reboot_linux(&self, current_reboot_count: u32, custom_timeout: Option) { @@ -1334,11 +1337,9 @@ impl<'a> GuestCommand<'a> { if pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE { Ok(child) } else { - Err(std::io::Error::other( - format!( - "resizing pipe w/ 'fnctl' failed: stdout pipesize {pipesize}, stderr pipesize {pipesize1}" - ), - )) + Err(std::io::Error::other(format!( + "resizing pipe w/ 'fnctl' failed: stdout pipesize {pipesize}, stderr pipesize {pipesize1}" + ))) } } else { // The caller should call .wait() on the returned child diff --git a/tests/integration.rs b/tests/integration.rs index 255c27adf1..bdb258a22b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -20,7 +20,7 @@ use std::path::PathBuf; use std::process::{Child, Command, Stdio}; use std::string::String; use std::sync::mpsc::Receiver; -use std::sync::{mpsc, Mutex}; +use std::sync::{Mutex, mpsc}; use std::time::Duration; use std::{fs, io, thread}; @@ -719,10 +719,12 @@ fn setup_ovs_dpdk() { assert!(exec_host_command_status("service openvswitch-switch restart").success()); // Create OVS-DPDK bridge and ports - assert!(exec_host_command_status( - "ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev", - ) - .success()); + assert!( + exec_host_command_status( + "ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev", + ) + .success() + ); assert!(exec_host_command_status("ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuserclient options:vhost-server-path=/tmp/dpdkvhostclient1").success()); assert!(exec_host_command_status("ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuserclient options:vhost-server-path=/tmp/dpdkvhostclient2").success()); assert!(exec_host_command_status("ip link set up dev ovsbr0").success()); @@ -1658,8 +1660,10 @@ fn _test_virtio_fs( "{{\"id\":\"myfs0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" ))); } else { - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}") + ); } thread::sleep(std::time::Duration::new(10, 0)); @@ -1739,8 +1743,10 @@ fn _test_virtio_fs( "{{\"id\":\"myfs0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" ))); } else { - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}") + ); } thread::sleep(std::time::Duration::new(10, 0)); @@ -1894,8 +1900,10 @@ fn _test_virtio_vsock(hotplug: bool) { Some(format!("cid=3,socket={socket},id=test0").as_str()), ); assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); thread::sleep(std::time::Duration::new(10, 0)); // Check adding a second one fails assert!(!remote_command( @@ -2112,18 +2120,20 @@ fn get_counters(api_socket: &str) -> Counters { fn pty_read(mut pty: std::fs::File) -> Receiver { let (tx, rx) = mpsc::channel::(); - thread::spawn(move || loop { - thread::sleep(std::time::Duration::new(1, 0)); - let mut buf = [0; 512]; - match pty.read(&mut buf) { - Ok(_bytes) => { - let output = std::str::from_utf8(&buf).unwrap().to_string(); - match tx.send(output) { - Ok(_) => (), - Err(_) => break, + thread::spawn(move || { + loop { + thread::sleep(std::time::Duration::new(1, 0)); + let mut buf = [0; 512]; + match pty.read(&mut buf) { + Ok(_bytes) => { + let output = std::str::from_utf8(&buf).unwrap().to_string(); + match tx.send(output) { + Ok(_) => (), + Err(_) => break, + } } + Err(_) => break, } - Err(_) => break, } }); rx @@ -2257,9 +2267,11 @@ fn _test_virtio_iommu(acpi: bool) { guest.wait_vm_boot(None).unwrap(); // Verify the virtio-iommu device is present. - assert!(guest - .does_device_vendor_pair_match("0x1057", "0x1af4") - .unwrap_or_default()); + assert!( + guest + .does_device_vendor_pair_match("0x1057", "0x1af4") + .unwrap_or_default() + ); // On AArch64, if the guest system boots from FDT, the behavior of IOMMU is a bit // different with ACPI. @@ -2319,9 +2331,11 @@ fn get_reboot_count(guest: &Guest) -> u32 { fn enable_guest_watchdog(guest: &Guest, watchdog_sec: u32) { // Check for PCI device - assert!(guest - .does_device_vendor_pair_match("0x1063", "0x1af4") - .unwrap_or_default()); + assert!( + guest + .does_device_vendor_pair_match("0x1063", "0x1af4") + .unwrap_or_default() + ); // Enable systemd watchdog guest @@ -2335,9 +2349,11 @@ fn enable_guest_watchdog(guest: &Guest, watchdog_sec: u32) { fn make_guest_panic(guest: &Guest) { // Check for pvpanic device - assert!(guest - .does_device_vendor_pair_match("0x0011", "0x1b36") - .unwrap_or_default()); + assert!( + guest + .does_device_vendor_pair_match("0x0011", "0x1b36") + .unwrap_or_default() + ); // Trigger guest a panic guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); @@ -2974,13 +2990,17 @@ mod common_parallel { ), ); assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0001:00:01.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0001:00:01.0\"}") + ); // Check IOMMU setup - assert!(guest - .does_device_vendor_pair_match("0x1057", "0x1af4") - .unwrap_or_default()); + assert!( + guest + .does_device_vendor_pair_match("0x1057", "0x1af4") + .unwrap_or_default() + ); assert_eq!( guest .ssh_command("ls /sys/kernel/iommu_groups/0/devices") @@ -4429,9 +4449,11 @@ mod common_parallel { let r = std::panic::catch_unwind(|| { guest.wait_vm_boot(None).unwrap(); - assert!(guest - .does_device_vendor_pair_match("0x1043", "0x1af4") - .unwrap_or_default()); + assert!( + guest + .does_device_vendor_pair_match("0x1043", "0x1af4") + .unwrap_or_default() + ); guest.ssh_command(&cmd).unwrap(); }); @@ -5407,8 +5429,10 @@ mod common_parallel { ), ); assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); thread::sleep(std::time::Duration::new(10, 0)); @@ -5454,8 +5478,10 @@ mod common_parallel { ), ); assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); thread::sleep(std::time::Duration::new(10, 0)); @@ -5953,8 +5979,10 @@ mod common_parallel { "{{\"id\":\"test0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" ))); } else { - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); } // Check that /dev/pmem0 exists and the block size is 128M @@ -6084,8 +6112,10 @@ mod common_parallel { "{{\"id\":\"test0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" ))); } else { - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:05.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:05.0\"}") + ); } thread::sleep(std::time::Duration::new(5, 0)); @@ -6128,8 +6158,10 @@ mod common_parallel { "{{\"id\":\"test1\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" ))); } else { - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test1\",\"bdf\":\"0000:00:05.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test1\",\"bdf\":\"0000:00:05.0\"}") + ); } thread::sleep(std::time::Duration::new(5, 0)); @@ -6566,15 +6598,19 @@ mod common_parallel { let phy_net = "eth0"; // Create a macvtap interface for the guest VM to use - assert!(exec_host_command_status(&format!( - "sudo ip link add link {phy_net} name {guest_macvtap_name} type macvtap mod bridge" - )) - .success()); - assert!(exec_host_command_status(&format!( - "sudo ip link set {} address {} up", - guest_macvtap_name, guest.network.guest_mac - )) - .success()); + assert!( + exec_host_command_status(&format!( + "sudo ip link add link {phy_net} name {guest_macvtap_name} type macvtap mod bridge" + )) + .success() + ); + assert!( + exec_host_command_status(&format!( + "sudo ip link set {} address {} up", + guest_macvtap_name, guest.network.guest_mac + )) + .success() + ); assert!( exec_host_command_status(&format!("sudo ip link show {guest_macvtap_name}")).success() ); @@ -6593,16 +6629,20 @@ mod common_parallel { // Create a macvtap on the same physical net interface for // the host machine to use - assert!(exec_host_command_status(&format!( - "sudo ip link add link {phy_net} name {host_macvtap_name} type macvtap mod bridge" - )) - .success()); + assert!( + exec_host_command_status(&format!( + "sudo ip link add link {phy_net} name {host_macvtap_name} type macvtap mod bridge" + )) + .success() + ); // Use default mask "255.255.255.0" - assert!(exec_host_command_status(&format!( - "sudo ip address add {}/24 dev {}", - guest.network.host_ip, host_macvtap_name - )) - .success()); + assert!( + exec_host_command_status(&format!( + "sudo ip address add {}/24 dev {}", + guest.network.host_ip, host_macvtap_name + )) + .success() + ); assert!( exec_host_command_status(&format!("sudo ip link set dev {host_macvtap_name} up")) .success() @@ -6638,11 +6678,15 @@ mod common_parallel { remote_command_w_output(&api_socket, "add-net", Some(&net_params)); assert!(cmd_success); #[cfg(target_arch = "x86_64")] - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"_net2\",\"bdf\":\"0000:00:05.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"_net2\",\"bdf\":\"0000:00:05.0\"}") + ); #[cfg(target_arch = "aarch64")] - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"_net0\",\"bdf\":\"0000:00:05.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"_net0\",\"bdf\":\"0000:00:05.0\"}") + ); } // The functional connectivity provided by the virtio-net device @@ -6818,21 +6862,27 @@ mod common_parallel { fn setup_spdk_nvme(nvme_dir: &std::path::Path) -> Child { cleanup_spdk_nvme(); - assert!(exec_host_command_status(&format!( - "mkdir -p {}", - nvme_dir.join("nvme-vfio-user").to_str().unwrap() - )) - .success()); - assert!(exec_host_command_status(&format!( - "truncate {} -s 128M", - nvme_dir.join("test-disk.raw").to_str().unwrap() - )) - .success()); - assert!(exec_host_command_status(&format!( - "mkfs.ext4 {}", - nvme_dir.join("test-disk.raw").to_str().unwrap() - )) - .success()); + assert!( + exec_host_command_status(&format!( + "mkdir -p {}", + nvme_dir.join("nvme-vfio-user").to_str().unwrap() + )) + .success() + ); + assert!( + exec_host_command_status(&format!( + "truncate {} -s 128M", + nvme_dir.join("test-disk.raw").to_str().unwrap() + )) + .success() + ); + assert!( + exec_host_command_status(&format!( + "mkfs.ext4 {}", + nvme_dir.join("test-disk.raw").to_str().unwrap() + )) + .success() + ); // Start the SPDK nvmf_tgt daemon to present NVMe device as a VFIO user device let child = Command::new("/usr/local/bin/spdk-nvme/nvmf_tgt") @@ -6846,11 +6896,13 @@ mod common_parallel { 3, std::time::Duration::new(5, 0), )); - assert!(exec_host_command_status(&format!( - "/usr/local/bin/spdk-nvme/rpc.py bdev_aio_create {} test 512", - nvme_dir.join("test-disk.raw").to_str().unwrap() - )) - .success()); + assert!( + exec_host_command_status(&format!( + "/usr/local/bin/spdk-nvme/rpc.py bdev_aio_create {} test 512", + nvme_dir.join("test-disk.raw").to_str().unwrap() + )) + .success() + ); assert!(exec_host_command_status( "/usr/local/bin/spdk-nvme/rpc.py nvmf_create_subsystem nqn.2019-07.io.spdk:cnode -a -s test" ) @@ -6911,8 +6963,10 @@ mod common_parallel { )), ); assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"vfio_user0\",\"bdf\":\"0000:00:05.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"vfio_user0\",\"bdf\":\"0000:00:05.0\"}") + ); thread::sleep(std::time::Duration::new(10, 0)); @@ -7017,15 +7071,19 @@ mod common_parallel { Some("id=myvdpa0,path=/dev/vhost-vdpa-1,num_queues=1,pci_segment=1,iommu=on"), ); assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"myvdpa0\",\"bdf\":\"0001:00:01.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"myvdpa0\",\"bdf\":\"0001:00:01.0\"}") + ); thread::sleep(std::time::Duration::new(10, 0)); // Check IOMMU setup - assert!(guest - .does_device_vendor_pair_match("0x1057", "0x1af4") - .unwrap_or_default()); + assert!( + guest + .does_device_vendor_pair_match("0x1057", "0x1af4") + .unwrap_or_default() + ); assert_eq!( guest .ssh_command("ls /sys/kernel/iommu_groups/0/devices") @@ -7419,7 +7477,7 @@ mod ivshmem { use std::fs::remove_dir_all; use std::process::Command; - use test_infra::{handle_child_output, kill_child, Guest, GuestCommand, UbuntuDiskConfig}; + use test_infra::{Guest, GuestCommand, UbuntuDiskConfig, handle_child_output, kill_child}; use crate::*; @@ -9404,8 +9462,10 @@ mod windows { Some(format!("path={disk},readonly=off").as_str()), ); assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output) - .contains(format!("\"id\":\"{disk_id}\"").as_str())); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains(format!("\"id\":\"{disk_id}\"").as_str()) + ); thread::sleep(std::time::Duration::new(5, 0)); // Online disk devices windows_guest.disks_set_rw(); @@ -9615,8 +9675,10 @@ mod vfio { Some(format!("id=vfio0,path={NVIDIA_VFIO_DEVICE}").as_str()), ); assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"vfio0\",\"bdf\":\"0000:00:06.0\"}")); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"vfio0\",\"bdf\":\"0000:00:06.0\"}") + ); thread::sleep(std::time::Duration::new(10, 0)); @@ -9694,10 +9756,12 @@ mod vfio { let r = std::panic::catch_unwind(|| { guest.wait_vm_boot(None).unwrap(); - assert!(guest - .ssh_command("sudo dmesg") - .unwrap() - .contains("input address: 42 bits")); + assert!( + guest + .ssh_command("sudo dmesg") + .unwrap() + .contains("input address: 42 bits") + ); }); let _ = child.kill(); @@ -9818,13 +9882,13 @@ mod live_migration { let _ = dest_vm.kill(); let dest_output = dest_vm.wait_with_output().unwrap(); eprintln!( - "\n\n==== Start 'destination_vm' stdout ====\n\n{}\n\n==== End 'destination_vm' stdout ====", - String::from_utf8_lossy(&dest_output.stdout) - ); + "\n\n==== Start 'destination_vm' stdout ====\n\n{}\n\n==== End 'destination_vm' stdout ====", + String::from_utf8_lossy(&dest_output.stdout) + ); eprintln!( - "\n\n==== Start 'destination_vm' stderr ====\n\n{}\n\n==== End 'destination_vm' stderr ====", - String::from_utf8_lossy(&dest_output.stderr) - ); + "\n\n==== Start 'destination_vm' stderr ====\n\n{}\n\n==== End 'destination_vm' stderr ====", + String::from_utf8_lossy(&dest_output.stderr) + ); if let Some(ovs_vm) = ovs_vm { let mut ovs_vm = ovs_vm; @@ -11382,11 +11446,13 @@ mod rate_limiter { String::from(test_img_dir.as_path().join("blk.img").to_str().unwrap()); // Create the test block image - assert!(exec_host_command_output(&format!( - "dd if=/dev/zero of={blk_rate_limiter_test_img} bs=1M count=1024" - )) - .status - .success()); + assert!( + exec_host_command_output(&format!( + "dd if=/dev/zero of={blk_rate_limiter_test_img} bs=1M count=1024" + )) + .status + .success() + ); let test_blk_params = if bandwidth { format!( @@ -11491,11 +11557,13 @@ mod rate_limiter { .unwrap(), ); - assert!(exec_host_command_output(&format!( - "dd if=/dev/zero of={test_img_path} bs=1M count=1024" - )) - .status - .success()); + assert!( + exec_host_command_output(&format!( + "dd if=/dev/zero of={test_img_path} bs=1M count=1024" + )) + .status + .success() + ); disk_args.push(format!( "path={test_img_path},num_queues={num_queues},rate_limit_group=group0" diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 5e668c0c5a..3977a25ab5 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -19,13 +19,13 @@ use std::time::Instant; use std::{convert, io, process, result}; use block::qcow::{self, ImageType, QcowFile}; -use block::{build_serial, Request, VirtioBlockConfig}; +use block::{Request, VirtioBlockConfig, build_serial}; use libc::EFD_NONBLOCK; use log::*; use option_parser::{OptionParser, OptionParserError, Toggle}; use thiserror::Error; -use vhost::vhost_user::message::*; use vhost::vhost_user::Listener; +use vhost::vhost_user::message::*; use vhost_user_backend::bitmap::BitmapMmapRegion; use vhost_user_backend::{VhostUserBackendMut, VhostUserDaemon, VringRwLock, VringState, VringT}; use virtio_bindings::virtio_blk::*; diff --git a/vhost_user_net/src/lib.rs b/vhost_user_net/src/lib.rs index de2a416aa0..b5c0aa8019 100644 --- a/vhost_user_net/src/lib.rs +++ b/vhost_user_net/src/lib.rs @@ -15,12 +15,12 @@ use std::{io, process}; use libc::EFD_NONBLOCK; use log::*; use net_util::{ - open_tap, MacAddr, NetCounters, NetQueuePair, OpenTapError, RxVirtio, Tap, TxVirtio, + MacAddr, NetCounters, NetQueuePair, OpenTapError, RxVirtio, Tap, TxVirtio, open_tap, }; use option_parser::{OptionParser, OptionParserError, Toggle}; use thiserror::Error; -use vhost::vhost_user::message::*; use vhost::vhost_user::Listener; +use vhost::vhost_user::message::*; use vhost_user_backend::bitmap::BitmapMmapRegion; use vhost_user_backend::{VhostUserBackendMut, VhostUserDaemon, VringRwLock, VringT}; use virtio_bindings::virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1}; diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index 2a34b6688f..1a5e202fdf 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -37,9 +37,9 @@ use vmm_sys_util::eventfd::EventFd; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::{ - ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, GuestMemoryMmap, - VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioInterruptType, - EPOLL_HELPER_EVENT_LAST, VIRTIO_F_VERSION_1, + ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, + GuestMemoryMmap, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, + VirtioInterrupt, VirtioInterruptType, }; const QUEUE_SIZE: u16 = 128; @@ -575,12 +575,12 @@ impl VirtioDevice for Balloon { let data_len = data.len() as u64; if offset + data_len > config_len { error!( - "Out-of-bound access to configuration: config_len = {} offset = {:x} length = {} for {}", - config_len, - offset, - data_len, - self.device_type() - ); + "Out-of-bound access to configuration: config_len = {} offset = {:x} length = {} for {}", + config_len, + offset, + data_len, + self.device_type() + ); return; } diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 415ae3f365..dad4fd2158 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -19,12 +19,12 @@ use std::{io, result}; use anyhow::anyhow; use block::async_io::{AsyncIo, AsyncIoError, DiskFile}; -use block::fcntl::{get_lock_state, LockError, LockType}; +use block::fcntl::{LockError, LockType, get_lock_state}; use block::{ - build_serial, fcntl, ExecuteAsync, ExecuteError, Request, RequestType, VirtioBlockConfig, + ExecuteAsync, ExecuteError, Request, RequestType, VirtioBlockConfig, build_serial, fcntl, }; -use rate_limiter::group::{RateLimiterGroup, RateLimiterGroupHandle}; use rate_limiter::TokenType; +use rate_limiter::group::{RateLimiterGroup, RateLimiterGroupHandle}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -38,9 +38,9 @@ use vm_virtio::AccessPlatform; use vmm_sys_util::eventfd::EventFd; use super::{ - ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, - Error as DeviceError, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterruptType, - EPOLL_HELPER_EVENT_LAST, + ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, + EpollHelperHandler, Error as DeviceError, VirtioCommon, VirtioDevice, VirtioDeviceType, + VirtioInterruptType, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index f05c0c4f4d..2c0e250a4d 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -22,9 +22,9 @@ use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; use super::{ - ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, Error as DeviceError, - VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterruptType, EPOLL_HELPER_EVENT_LAST, - VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, + ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, + Error as DeviceError, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, + VirtioDeviceType, VirtioInterruptType, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index df310823af..71c10c1d0c 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -24,8 +24,8 @@ use vm_virtio::AccessPlatform; use vmm_sys_util::eventfd::EventFd; use super::{ - ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, Error as DeviceError, - VirtioCommon, VirtioDevice, VirtioDeviceType, EPOLL_HELPER_EVENT_LAST, VIRTIO_F_VERSION_1, + ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, + Error as DeviceError, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index a59661eb61..86359da657 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -51,10 +51,10 @@ pub use self::device::{ VirtioSharedMemoryList, }; pub use self::epoll_helper::{ - EpollHelper, EpollHelperError, EpollHelperHandler, EPOLL_HELPER_EVENT_LAST, + EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, }; pub use self::iommu::{AccessPlatformMapping, Iommu, IommuMapping}; -pub use self::mem::{BlocksState, Mem, VirtioMemMappingSource, VIRTIO_MEM_ALIGN_SIZE}; +pub use self::mem::{BlocksState, Mem, VIRTIO_MEM_ALIGN_SIZE, VirtioMemMappingSource}; pub use self::net::{Net, NetCtrlEpollHandler}; pub use self::pmem::Pmem; pub use self::rng::Rng; diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 8d5830ac3c..6971f07151 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -18,7 +18,7 @@ use std::collections::BTreeMap; use std::mem::size_of; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::atomic::AtomicBool; -use std::sync::{mpsc, Arc, Barrier, Mutex}; +use std::sync::{Arc, Barrier, Mutex, mpsc}; use std::{io, result}; use anyhow::anyhow; @@ -36,9 +36,9 @@ use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottabl use vmm_sys_util::eventfd::EventFd; use super::{ - ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, - Error as DeviceError, VirtioCommon, VirtioDevice, VirtioDeviceType, EPOLL_HELPER_EVENT_LAST, - VIRTIO_F_VERSION_1, + ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, + EpollHelperHandler, Error as DeviceError, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, + VirtioDeviceType, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 950cedb519..4518321ecf 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -18,8 +18,8 @@ use anyhow::anyhow; #[cfg(not(fuzzing))] use net_util::virtio_features_to_tap_offload; use net_util::{ - build_net_config_space, build_net_config_space_with_mq, open_tap, CtrlQueue, MacAddr, - NetCounters, NetQueuePair, OpenTapError, RxVirtio, Tap, TapError, TxVirtio, VirtioNetConfig, + CtrlQueue, MacAddr, NetCounters, NetQueuePair, OpenTapError, RxVirtio, Tap, TapError, TxVirtio, + VirtioNetConfig, build_net_config_space, build_net_config_space_with_mq, open_tap, }; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; @@ -34,9 +34,9 @@ use vm_virtio::AccessPlatform; use vmm_sys_util::eventfd::EventFd; use super::{ - ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, - Error as DeviceError, RateLimiterConfig, VirtioCommon, VirtioDevice, VirtioDeviceType, - VirtioInterruptType, EPOLL_HELPER_EVENT_LAST, + ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, + EpollHelperHandler, Error as DeviceError, RateLimiterConfig, VirtioCommon, VirtioDevice, + VirtioDeviceType, VirtioInterruptType, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 1e4353477c..8e41f0ec05 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -28,9 +28,9 @@ use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; use super::{ - ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, - Error as DeviceError, VirtioCommon, VirtioDevice, VirtioDeviceType, EPOLL_HELPER_EVENT_LAST, - VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, + ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, + EpollHelperHandler, Error as DeviceError, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, + VirtioCommon, VirtioDevice, VirtioDeviceType, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 8429e3b31e..6dccb2de19 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -21,9 +21,9 @@ use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; use super::{ - ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, - Error as DeviceError, VirtioCommon, VirtioDevice, VirtioDeviceType, EPOLL_HELPER_EVENT_LAST, - VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, + ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, + EpollHelperHandler, Error as DeviceError, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, + VirtioCommon, VirtioDevice, VirtioDeviceType, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs index abad842bb1..5986e72ea3 100644 --- a/virtio-devices/src/seccomp_filters.rs +++ b/virtio-devices/src/seccomp_filters.rs @@ -53,13 +53,15 @@ const VFIO_IOMMU_UNMAP_DMA: u64 = 0x3b72; #[cfg(feature = "sev_snp")] fn mshv_sev_snp_ioctl_seccomp_rule() -> SeccompRule { - and![Cond::new( - 1, - ArgLen::Dword, - Eq, - mshv_ioctls::MSHV_MODIFY_GPA_HOST_ACCESS() - ) - .unwrap()] + and![ + Cond::new( + 1, + ArgLen::Dword, + Eq, + mshv_ioctls::MSHV_MODIFY_GPA_HOST_ACCESS() + ) + .unwrap() + ] } #[cfg(feature = "sev_snp")] diff --git a/virtio-devices/src/thread_helper.rs b/virtio-devices/src/thread_helper.rs index 41eb99b7c7..d4df6c86b6 100644 --- a/virtio-devices/src/thread_helper.rs +++ b/virtio-devices/src/thread_helper.rs @@ -6,12 +6,12 @@ use std::panic::AssertUnwindSafe; use std::thread::{self, JoinHandle}; -use seccompiler::{apply_filter, SeccompAction}; +use seccompiler::{SeccompAction, apply_filter}; use vmm_sys_util::eventfd::EventFd; -use crate::epoll_helper::EpollHelperError; -use crate::seccomp_filters::{get_seccomp_filter, Thread}; use crate::ActivateError; +use crate::epoll_helper::EpollHelperError; +use crate::seccomp_filters::{Thread, get_seccomp_filter}; pub(crate) fn spawn_virtio_thread( name: &str, diff --git a/virtio-devices/src/transport/mod.rs b/virtio-devices/src/transport/mod.rs index fae6f166b8..9214de5dbe 100644 --- a/virtio-devices/src/transport/mod.rs +++ b/virtio-devices/src/transport/mod.rs @@ -5,7 +5,7 @@ use vmm_sys_util::eventfd::EventFd; mod pci_common_config; mod pci_device; -pub use pci_common_config::{VirtioPciCommonConfig, VIRTIO_PCI_COMMON_CONFIG_ID}; +pub use pci_common_config::{VIRTIO_PCI_COMMON_CONFIG_ID, VirtioPciCommonConfig}; pub use pci_device::{VirtioPciDevice, VirtioPciDeviceActivator, VirtioPciDeviceError}; pub trait VirtioTransport { diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index f493e32ab0..a16102218e 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -35,11 +35,11 @@ use vm_virtio::AccessPlatform; use vmm_sys_util::eventfd::EventFd; use super::pci_common_config::VirtioPciCommonConfigState; -use crate::transport::{VirtioPciCommonConfig, VirtioTransport, VIRTIO_PCI_COMMON_CONFIG_ID}; +use crate::transport::{VIRTIO_PCI_COMMON_CONFIG_ID, VirtioPciCommonConfig, VirtioTransport}; use crate::{ - ActivateResult, GuestMemoryMmap, VirtioDevice, VirtioDeviceType, VirtioInterrupt, - VirtioInterruptType, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED, - DEVICE_FEATURES_OK, DEVICE_INIT, + ActivateResult, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED, + DEVICE_FEATURES_OK, DEVICE_INIT, GuestMemoryMmap, VirtioDevice, VirtioDeviceType, + VirtioInterrupt, VirtioInterruptType, }; /// Vector value used to disable MSI for a queue. @@ -973,7 +973,7 @@ impl PciDevice for VirtioPciDevice { settings_bar_addr = Some(GuestAddress(base)); use_64bit_bar = match type_ { PciBarType::Io => { - return Err(PciDeviceError::InvalidResource(resource)) + return Err(PciDeviceError::InvalidResource(resource)); } PciBarType::Mmio32 => false, PciBarType::Mmio64 => true, diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index decd6ede64..6ca4f7ddaa 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -12,9 +12,9 @@ use anyhow::anyhow; use serde::{Deserialize, Serialize}; use thiserror::Error; use vhost::vdpa::{VhostVdpa, VhostVdpaIovaRange}; +use vhost::vhost_kern::VhostKernFeatures; use vhost::vhost_kern::vdpa::VhostKernVdpa; use vhost::vhost_kern::vhost_binding::VHOST_BACKEND_F_SUSPEND; -use vhost::vhost_kern::VhostKernFeatures; use vhost::{VhostBackend, VringConfigData}; use virtio_queue::desc::RawDescriptor; use virtio_queue::{Queue, QueueT}; @@ -25,9 +25,9 @@ use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; use crate::{ - ActivateError, ActivateResult, GuestMemoryMmap, VirtioCommon, VirtioDevice, VirtioInterrupt, - VirtioInterruptType, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FEATURES_OK, - VIRTIO_F_IOMMU_PLATFORM, + ActivateError, ActivateResult, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, + DEVICE_FEATURES_OK, GuestMemoryMmap, VIRTIO_F_IOMMU_PLATFORM, VirtioCommon, VirtioDevice, + VirtioInterrupt, VirtioInterruptType, }; #[derive(Error, Debug)] diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 87ca4130a8..d009280a86 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -9,8 +9,8 @@ use block::VirtioBlockConfig; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use vhost::vhost_user::message::{ - VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures, - VHOST_USER_CONFIG_OFFSET, + VHOST_USER_CONFIG_OFFSET, VhostUserConfigFlags, VhostUserProtocolFeatures, + VhostUserVirtioFeatures, }; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; use virtio_bindings::virtio_blk::{ @@ -26,11 +26,11 @@ use vmm_sys_util::eventfd::EventFd; use super::super::{ActivateResult, VirtioCommon, VirtioDevice, VirtioDeviceType}; use super::vu_common_ctrl::{VhostUserConfig, VhostUserHandle}; -use super::{Error, Result, DEFAULT_VIRTIO_FEATURES}; +use super::{DEFAULT_VIRTIO_FEATURES, Error, Result}; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::VhostUserCommon; -use crate::{GuestMemoryMmap, GuestRegionMmap, VirtioInterrupt, VIRTIO_F_IOMMU_PLATFORM}; +use crate::{GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_IOMMU_PLATFORM, VirtioInterrupt}; const DEFAULT_QUEUE_NUMBER: usize = 1; @@ -134,8 +134,10 @@ impl Blk { }; if num_queues > backend_num_queues { - error!("vhost-user-blk requested too many queues ({}) since the backend only supports {}\n", - num_queues, backend_num_queues); + error!( + "vhost-user-blk requested too many queues ({}) since the backend only supports {}\n", + num_queues, backend_num_queues + ); return Err(Error::BadQueueNum); } diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 4aa6920475..4c70db2f1a 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -7,7 +7,7 @@ use std::{result, thread}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, Bytes}; +use serde_with::{Bytes, serde_as}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; use virtio_queue::Queue; @@ -18,13 +18,13 @@ use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottabl use vmm_sys_util::eventfd::EventFd; use super::vu_common_ctrl::VhostUserHandle; -use super::{Error, Result, DEFAULT_VIRTIO_FEATURES}; +use super::{DEFAULT_VIRTIO_FEATURES, Error, Result}; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::VhostUserCommon; use crate::{ - ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VirtioCommon, VirtioDevice, - VirtioDeviceType, VirtioInterrupt, VirtioSharedMemoryList, VIRTIO_F_IOMMU_PLATFORM, + ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_IOMMU_PLATFORM, + VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioSharedMemoryList, }; const NUM_QUEUE_OFFSET: usize = 1; @@ -148,9 +148,9 @@ impl Fs { if num_queues > backend_num_queues { error!( - "vhost-user-fs requested too many queues ({}) since the backend only supports {}\n", - num_queues, backend_num_queues - ); + "vhost-user-fs requested too many queues ({}) since the backend only supports {}\n", + num_queues, backend_num_queues + ); return Err(Error::BadQueueNum); } diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 188942721f..14aa173b78 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -10,11 +10,11 @@ use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; use serde::{Deserialize, Serialize}; use thiserror::Error; +use vhost::Error as VhostError; use vhost::vhost_user::message::{ VhostUserInflight, VhostUserProtocolFeatures, VhostUserVirtioFeatures, }; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontendReqHandler}; -use vhost::Error as VhostError; use virtio_queue::{Error as QueueError, Queue}; use vm_memory::mmap::MmapRegionError; use vm_memory::{Address, Error as MmapError, GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; @@ -24,10 +24,10 @@ use vmm_sys_util::eventfd::EventFd; use vu_common_ctrl::VhostUserHandle; use crate::{ - ActivateError, EpollHelper, EpollHelperError, EpollHelperHandler, GuestMemoryMmap, - GuestRegionMmap, VirtioInterrupt, EPOLL_HELPER_EVENT_LAST, VIRTIO_F_IN_ORDER, - VIRTIO_F_NOTIFICATION_DATA, VIRTIO_F_ORDER_PLATFORM, VIRTIO_F_RING_EVENT_IDX, - VIRTIO_F_RING_INDIRECT_DESC, VIRTIO_F_VERSION_1, + ActivateError, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, + GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_IN_ORDER, VIRTIO_F_NOTIFICATION_DATA, + VIRTIO_F_ORDER_PLATFORM, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC, + VIRTIO_F_VERSION_1, VirtioInterrupt, }; pub mod blk; diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index c52d5ca385..99c0f81555 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -5,7 +5,7 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier, Mutex}; use std::{result, thread}; -use net_util::{build_net_config_space, CtrlQueue, MacAddr, VirtioNetConfig}; +use net_util::{CtrlQueue, MacAddr, VirtioNetConfig, build_net_config_space}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; @@ -28,9 +28,9 @@ use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::vu_common_ctrl::{VhostUserConfig, VhostUserHandle}; use crate::vhost_user::{Error, Result, VhostUserCommon}; use crate::{ - ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, VirtioCommon, - VirtioDevice, VirtioDeviceType, VirtioInterrupt, VIRTIO_F_IOMMU_PLATFORM, - VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_VERSION_1, + ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, VIRTIO_F_IOMMU_PLATFORM, + VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, + VirtioInterrupt, }; const DEFAULT_QUEUE_NUMBER: usize = 2; @@ -168,8 +168,10 @@ impl Net { }; if num_queues > backend_num_queues { - error!("vhost-user-net requested too many queues ({}) since the backend only supports {}\n", - num_queues, backend_num_queues); + error!( + "vhost-user-net requested too many queues ({}) since the backend only supports {}\n", + num_queues, backend_num_queues + ); return Err(Error::BadQueueNum); } diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index e1204fbd7d..91fb55a207 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -5,8 +5,8 @@ use std::ffi; use std::fs::File; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::net::UnixListener; -use std::sync::atomic::Ordering; use std::sync::Arc; +use std::sync::atomic::Ordering; use std::thread::sleep; use std::time::{Duration, Instant}; @@ -29,8 +29,8 @@ use vmm_sys_util::eventfd::EventFd; use super::{Error, Result}; use crate::vhost_user::Inflight; use crate::{ - get_host_address_range, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VirtioInterrupt, - VirtioInterruptType, + GuestMemoryMmap, GuestRegionMmap, MmapRegion, VirtioInterrupt, VirtioInterruptType, + get_host_address_range, }; // Size of a dirty page for vhost-user. @@ -399,7 +399,7 @@ impl VhostUserHandle { acked_features: 0, vrings_info: None, queue_indexes: Vec::new(), - }) + }); } Err(e) => e, }; diff --git a/virtio-devices/src/vsock/csm/connection.rs b/virtio-devices/src/vsock/csm/connection.rs index 50f3c30341..e36f58ccb7 100644 --- a/virtio-devices/src/vsock/csm/connection.rs +++ b/virtio-devices/src/vsock/csm/connection.rs @@ -89,7 +89,7 @@ use super::super::defs::uapi; use super::super::packet::VsockPacket; use super::super::{Result as VsockResult, VsockChannel, VsockEpollListener, VsockError}; use super::txbuf::TxBuf; -use super::{defs, ConnState, Error, PendingRx, PendingRxSet, Result}; +use super::{ConnState, Error, PendingRx, PendingRxSet, Result, defs}; /// A self-managing connection object, that handles communication between a guest-side AF_VSOCK /// socket and a host-side `Read + Write + AsRawFd` stream. @@ -1158,10 +1158,11 @@ mod tests { // When there's data in the TX buffer, the connection should ask to be notified when it // can write to its backing stream. - assert!(ctx - .conn - .get_polled_evset() - .contains(epoll::Events::EPOLLOUT)); + assert!( + ctx.conn + .get_polled_evset() + .contains(epoll::Events::EPOLLOUT) + ); assert_eq!(ctx.conn.tx_buf.len(), data.len()); // Unlock the write stream and notify the connection it can now write its buffered @@ -1212,10 +1213,11 @@ mod tests { stream.write_state = StreamState::Closed; ctx.set_stream(stream); - assert!(ctx - .conn - .get_polled_evset() - .contains(epoll::Events::EPOLLOUT)); + assert!( + ctx.conn + .get_polled_evset() + .contains(epoll::Events::EPOLLOUT) + ); ctx.notify_epollout(); assert_eq!(ctx.conn.state, ConnState::Killed); } diff --git a/virtio-devices/src/vsock/csm/txbuf.rs b/virtio-devices/src/vsock/csm/txbuf.rs index 4c16913f34..1a8c5dd151 100644 --- a/virtio-devices/src/vsock/csm/txbuf.rs +++ b/virtio-devices/src/vsock/csm/txbuf.rs @@ -5,7 +5,7 @@ use std::io::Write; use std::num::Wrapping; -use super::{defs, Error, Result}; +use super::{Error, Result, defs}; /// A simple ring-buffer implementation, used by vsock connections to buffer TX (guest -> host) /// data. Memory for this buffer is allocated lazily, since buffering will only be needed when diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index f8c024833b..4d073c4923 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -47,10 +47,10 @@ use super::{VsockBackend, VsockPacket}; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::{ - ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, Error as DeviceError, - GuestMemoryMmap, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, - VirtioInterruptType, EPOLL_HELPER_EVENT_LAST, VIRTIO_F_IN_ORDER, VIRTIO_F_IOMMU_PLATFORM, - VIRTIO_F_VERSION_1, + ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, + Error as DeviceError, GuestMemoryMmap, VIRTIO_F_IN_ORDER, VIRTIO_F_IOMMU_PLATFORM, + VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, + VirtioInterruptType, }; const QUEUE_SIZE: u16 = 256; diff --git a/virtio-devices/src/vsock/mod.rs b/virtio-devices/src/vsock/mod.rs index ecd523d1c0..3cd9592a64 100644 --- a/virtio-devices/src/vsock/mod.rs +++ b/virtio-devices/src/vsock/mod.rs @@ -172,7 +172,7 @@ pub mod tests { use vm_virtio::queue::testing::VirtQueue as GuestQ; use vmm_sys_util::eventfd::EventFd; - use super::device::{VsockEpollHandler, RX_QUEUE_EVENT, TX_QUEUE_EVENT}; + use super::device::{RX_QUEUE_EVENT, TX_QUEUE_EVENT, VsockEpollHandler}; use super::packet::VSOCK_PKT_HDR_SIZE; use super::*; use crate::device::{VirtioInterrupt, VirtioInterruptType}; diff --git a/virtio-devices/src/vsock/packet.rs b/virtio-devices/src/vsock/packet.rs index 01ee18fbb1..a6749381df 100644 --- a/virtio-devices/src/vsock/packet.rs +++ b/virtio-devices/src/vsock/packet.rs @@ -24,7 +24,7 @@ use virtio_queue::DescriptorChain; use vm_memory::{Address, GuestMemory}; use vm_virtio::{AccessPlatform, Translatable}; -use super::{defs, Result, VsockError}; +use super::{Result, VsockError, defs}; use crate::get_host_address_range; // The vsock packet header is defined by the C struct: @@ -427,8 +427,8 @@ mod tests { use super::super::tests::TestContext; use super::*; - use crate::vsock::defs::MAX_PKT_BUF_SIZE; use crate::GuestMemoryMmap; + use crate::vsock::defs::MAX_PKT_BUF_SIZE; macro_rules! create_context { ($test_ctx:ident, $handler_ctx:ident) => { diff --git a/virtio-devices/src/vsock/unix/mod.rs b/virtio-devices/src/vsock/unix/mod.rs index 13c9883194..bb44698121 100644 --- a/virtio-devices/src/vsock/unix/mod.rs +++ b/virtio-devices/src/vsock/unix/mod.rs @@ -13,9 +13,9 @@ mod muxer; mod muxer_killq; mod muxer_rxq; +pub use Error as VsockUnixError; pub use muxer::VsockMuxer as VsockUnixBackend; use thiserror::Error; -pub use Error as VsockUnixError; mod defs { /// Maximum number of established connections that we can handle. diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index ebe0dc723b..58df496e20 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -52,7 +52,7 @@ use super::super::{ }; use super::muxer_killq::MuxerKillQ; use super::muxer_rxq::MuxerRxQ; -use super::{defs, Error, MuxerConnection, Result}; +use super::{Error, MuxerConnection, Result, defs}; /// A unique identifier of a `MuxerConnection` object. Connections are stored in a hash map, /// keyed by a `ConnMapKey` object. diff --git a/virtio-devices/src/vsock/unix/muxer_killq.rs b/virtio-devices/src/vsock/unix/muxer_killq.rs index 925f4d9383..5295ab69e1 100644 --- a/virtio-devices/src/vsock/unix/muxer_killq.rs +++ b/virtio-devices/src/vsock/unix/muxer_killq.rs @@ -29,7 +29,7 @@ use std::collections::{HashMap, VecDeque}; use std::time::Instant; use super::muxer::ConnMapKey; -use super::{defs, MuxerConnection}; +use super::{MuxerConnection, defs}; /// A kill queue item, holding the connection key and the scheduled time for termination. /// diff --git a/virtio-devices/src/vsock/unix/muxer_rxq.rs b/virtio-devices/src/vsock/unix/muxer_rxq.rs index 701db3459f..077cbc8899 100644 --- a/virtio-devices/src/vsock/unix/muxer_rxq.rs +++ b/virtio-devices/src/vsock/unix/muxer_rxq.rs @@ -20,7 +20,7 @@ use std::collections::{HashMap, VecDeque}; use super::super::VsockChannel; use super::muxer::{ConnMapKey, MuxerRx}; -use super::{defs, MuxerConnection}; +use super::{MuxerConnection, defs}; /// The muxer RX queue. /// diff --git a/virtio-devices/src/watchdog.rs b/virtio-devices/src/watchdog.rs index fdfc977d96..23a33a3e57 100644 --- a/virtio-devices/src/watchdog.rs +++ b/virtio-devices/src/watchdog.rs @@ -23,9 +23,9 @@ use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottabl use vmm_sys_util::eventfd::EventFd; use super::{ - ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, - Error as DeviceError, VirtioCommon, VirtioDevice, VirtioDeviceType, EPOLL_HELPER_EVENT_LAST, - VIRTIO_F_VERSION_1, + ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, + EpollHelperHandler, Error as DeviceError, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, + VirtioDeviceType, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; diff --git a/vm-allocator/src/page_size.rs b/vm-allocator/src/page_size.rs index 96ae01edf7..6dedb6847a 100644 --- a/vm-allocator/src/page_size.rs +++ b/vm-allocator/src/page_size.rs @@ -1,7 +1,7 @@ // Copyright 2023 Arm Limited (or its affiliates). All rights reserved. // SPDX-License-Identifier: Apache-2.0 -use libc::{sysconf, _SC_PAGESIZE}; +use libc::{_SC_PAGESIZE, sysconf}; /// get host page size pub fn get_page_size() -> u64 { diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 215deac912..de2f581294 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -5,16 +5,16 @@ use std::sync::{Arc, Mutex}; use std::time::Instant; +use acpi_tables::Aml; use acpi_tables::rsdp::Rsdp; #[cfg(target_arch = "aarch64")] use acpi_tables::sdt::GenericAddress; use acpi_tables::sdt::Sdt; -use acpi_tables::Aml; -#[cfg(target_arch = "aarch64")] -use arch::aarch64::DeviceInfoForFdt; #[cfg(target_arch = "aarch64")] use arch::DeviceType; use arch::NumaNodes; +#[cfg(target_arch = "aarch64")] +use arch::aarch64::DeviceInfoForFdt; use bitflags::bitflags; use pci::PciBdf; use tracer::trace_scoped; diff --git a/vmm/src/api/dbus/mod.rs b/vmm/src/api/dbus/mod.rs index 85bd9d54fa..f329b86bcd 100644 --- a/vmm/src/api/dbus/mod.rs +++ b/vmm/src/api/dbus/mod.rs @@ -3,14 +3,14 @@ // SPDX-License-Identifier: Apache-2.0 // use std::panic::AssertUnwindSafe; -use std::sync::mpsc::Sender; use std::sync::Arc; +use std::sync::mpsc::Sender; use std::thread; use futures::channel::oneshot; -use futures::{executor, FutureExt}; +use futures::{FutureExt, executor}; use hypervisor::HypervisorType; -use seccompiler::{apply_filter, SeccompAction}; +use seccompiler::{SeccompAction, apply_filter}; use vmm_sys_util::eventfd::EventFd; use zbus::connection::Builder; use zbus::fdo::{self, Result}; @@ -26,7 +26,7 @@ use crate::api::{ VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot, VmmPing, VmmShutdown, }; -use crate::seccomp_filters::{get_seccomp_filter, Thread}; +use crate::seccomp_filters::{Thread, get_seccomp_filter}; use crate::{Error as VmmError, NetConfig, Result as VmmResult, VmConfig}; pub type DBusApiShutdownChannels = (oneshot::Sender<()>, oneshot::Receiver<()>); diff --git a/vmm/src/api/http/http_endpoint.rs b/vmm/src/api/http/http_endpoint.rs index ff1de56296..5a4bf93da6 100644 --- a/vmm/src/api/http/http_endpoint.rs +++ b/vmm/src/api/http/http_endpoint.rs @@ -11,9 +11,9 @@ use std::sync::mpsc::Sender; use micro_http::{Body, Method, Request, Response, StatusCode, Version}; use vmm_sys_util::eventfd::EventFd; -use crate::api::http::{error_response, EndpointHandler, HttpError}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::api::VmCoredump; +use crate::api::http::{EndpointHandler, HttpError, error_response}; use crate::api::{ AddDisk, ApiAction, ApiError, ApiRequest, NetConfig, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmConfig, VmCounters, VmDelete, VmNmi, VmPause, diff --git a/vmm/src/api/http/mod.rs b/vmm/src/api/http/mod.rs index 418a4d4961..243429154d 100644 --- a/vmm/src/api/http/mod.rs +++ b/vmm/src/api/http/mod.rs @@ -10,15 +10,15 @@ use std::os::unix::io::{IntoRawFd, RawFd}; use std::os::unix::net::UnixListener; use std::panic::AssertUnwindSafe; use std::path::PathBuf; -use std::sync::mpsc::Sender; use std::sync::LazyLock; +use std::sync::mpsc::Sender; use std::thread; use hypervisor::HypervisorType; use micro_http::{ Body, HttpServer, MediaType, Method, Request, Response, ServerError, StatusCode, Version, }; -use seccompiler::{apply_filter, SeccompAction}; +use seccompiler::{SeccompAction, apply_filter}; use serde_json::Error as SerdeError; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; @@ -33,7 +33,7 @@ use crate::api::{ VmSendMigration, VmShutdown, VmSnapshot, }; use crate::landlock::Landlock; -use crate::seccomp_filters::{get_seccomp_filter, Thread}; +use crate::seccomp_filters::{Thread, get_seccomp_filter}; use crate::{Error as VmmError, Result}; pub mod http_endpoint; diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index a856f49f55..5ef63ffa39 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -34,7 +34,7 @@ pub mod dbus; pub mod http; use std::io; -use std::sync::mpsc::{channel, RecvError, SendError, Sender}; +use std::sync::mpsc::{RecvError, SendError, Sender, channel}; use micro_http::Body; use serde::{Deserialize, Serialize}; @@ -45,6 +45,7 @@ use vmm_sys_util::eventfd::EventFd; #[cfg(feature = "dbus_api")] pub use self::dbus::start_dbus_thread; pub use self::http::{start_http_fd_thread, start_http_path_thread}; +use crate::Error as VmmError; use crate::config::RestoreConfig; use crate::device_tree::DeviceTree; use crate::vm::{Error as VmError, VmState}; @@ -52,7 +53,6 @@ use crate::vm_config::{ DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; -use crate::Error as VmmError; /// API errors are sent back from the VMM API server through the ApiResponse. #[derive(Error, Debug)] diff --git a/vmm/src/clone3.rs b/vmm/src/clone3.rs index fc273680f8..eca0d90632 100644 --- a/vmm/src/clone3.rs +++ b/vmm/src/clone3.rs @@ -1,7 +1,7 @@ // Copyright 2021 Alyssa Ross // SPDX-License-Identifier: Apache-2.0 -use libc::{c_long, size_t, syscall, SYS_clone3}; +use libc::{SYS_clone3, c_long, size_t, syscall}; pub const CLONE_CLEAR_SIGHAND: u64 = 0x100000000; diff --git a/vmm/src/config.rs b/vmm/src/config.rs index b66c71b8c6..d213b3a2d8 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -270,7 +270,9 @@ pub enum ValidationError { #[error("Invalid PCI segment aperture weight: {0}")] InvalidPciSegmentApertureWeight(u32), /// Invalid IOMMU address width in bits - #[error("IOMMU address width in bits ({0}) should be less than or equal to {MAX_IOMMU_ADDRESS_WIDTH_BITS}")] + #[error( + "IOMMU address width in bits ({0}) should be less than or equal to {MAX_IOMMU_ADDRESS_WIDTH_BITS}" + )] InvalidIommuAddressWidthBits(u8), /// Balloon too big #[error("Ballon size ({0}) greater than RAM ({1})")] @@ -1536,8 +1538,7 @@ impl RngConfig { } impl BalloonConfig { - pub const SYNTAX: &'static str = - "Balloon parameters \"size=,deflate_on_oom=on|off,\ + pub const SYNTAX: &'static str = "Balloon parameters \"size=,deflate_on_oom=on|off,\ free_page_reporting=on|off\""; pub fn parse(balloon: &str) -> Result { @@ -1911,8 +1912,7 @@ impl DebugConsoleConfig { } impl DeviceConfig { - pub const SYNTAX: &'static str = - "Direct device assignment parameters \"path=,iommu=on|off,id=,pci_segment=\""; + pub const SYNTAX: &'static str = "Direct device assignment parameters \"path=,iommu=on|off,id=,pci_segment=\""; pub fn parse(device: &str) -> Result { let mut parser = OptionParser::new(); @@ -2206,7 +2206,9 @@ where S: serde::Serializer, { if let Some(x) = x { - warn!("'RestoredNetConfig' contains FDs that can't be serialized correctly. Serializing them as invalid FDs."); + warn!( + "'RestoredNetConfig' contains FDs that can't be serialized correctly. Serializing them as invalid FDs." + ); let invalid_fds = vec![-1; x.len()]; s.serialize_some(&invalid_fds) } else { @@ -2222,7 +2224,9 @@ where { let invalid_fds: Option> = Option::deserialize(d)?; if let Some(invalid_fds) = invalid_fds { - warn!("'RestoredNetConfig' contains FDs that can't be deserialized correctly. Deserializing them as invalid FDs."); + warn!( + "'RestoredNetConfig' contains FDs that can't be deserialized correctly. Deserializing them as invalid FDs." + ); Ok(Some(vec![-1; invalid_fds.len()])) } else { Ok(None) @@ -3556,7 +3560,9 @@ mod tests { ); assert_eq!( - NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,num_queues=4,queue_size=1024,iommu=on")?, + NetConfig::parse( + "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,num_queues=4,queue_size=1024,iommu=on" + )?, NetConfig { num_queues: 4, queue_size: 1024, @@ -4814,18 +4820,19 @@ mod tests { )?, FwCfgConfig { items: Some(FwCfgItemList { - item_list: vec![FwCfgItem { - name: "opt/org.test/fw_cfg_test_item".to_string(), - file: PathBuf::from("/tmp/fw_cfg_test_item"), - }, - FwCfgItem { - name: "opt/org.test/fw_cfg_test_item2".to_string(), - file: PathBuf::from("/tmp/fw_cfg_test_item2"), - }] + item_list: vec![ + FwCfgItem { + name: "opt/org.test/fw_cfg_test_item".to_string(), + file: PathBuf::from("/tmp/fw_cfg_test_item"), + }, + FwCfgItem { + name: "opt/org.test/fw_cfg_test_item2".to_string(), + file: PathBuf::from("/tmp/fw_cfg_test_item2"), + } + ] }), ..Default::default() - }, - + }, ); Ok(()) } diff --git a/vmm/src/console_devices.rs b/vmm/src/console_devices.rs index c4137733bc..9f8d18ae7c 100644 --- a/vmm/src/console_devices.rs +++ b/vmm/src/console_devices.rs @@ -10,7 +10,7 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // -use std::fs::{read_link, File, OpenOptions}; +use std::fs::{File, OpenOptions, read_link}; use std::mem::zeroed; use std::os::fd::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::fs::OpenOptionsExt; @@ -19,12 +19,12 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::{io, result}; -use libc::{cfmakeraw, isatty, tcgetattr, tcsetattr, termios, TCSANOW}; +use libc::{TCSANOW, cfmakeraw, isatty, tcgetattr, tcsetattr, termios}; use thiserror::Error; +use crate::Vmm; use crate::sigwinch_listener::listen_for_sigwinch_on_tty; use crate::vm_config::ConsoleOutputMode; -use crate::Vmm; const TIOCSPTLCK: libc::c_int = 0x4004_5431; const TIOCGPTPEER: libc::c_int = 0x5441; @@ -225,7 +225,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { - return Err(ConsoleDeviceError::NoSocketOptionSupportForConsoleDevice) + return Err(ConsoleDeviceError::NoSocketOptionSupportForConsoleDevice); } ConsoleOutputMode::Null => ConsoleOutput::Null, ConsoleOutputMode::Off => ConsoleOutput::Off, @@ -288,7 +288,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { - return Err(ConsoleDeviceError::NoSocketOptionSupportForConsoleDevice) + return Err(ConsoleDeviceError::NoSocketOptionSupportForConsoleDevice); } ConsoleOutputMode::Null => ConsoleOutput::Null, ConsoleOutputMode::Off => ConsoleOutput::Off, diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 00d2468f39..5ae2f6da26 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -23,7 +23,7 @@ use std::{cmp, io, result, thread}; #[cfg(not(target_arch = "riscv64"))] use acpi_tables::sdt::Sdt; -use acpi_tables::{aml, Aml}; +use acpi_tables::{Aml, aml}; use anyhow::anyhow; #[cfg(target_arch = "x86_64")] use arch::x86_64::get_x2apic_id; @@ -34,30 +34,30 @@ use devices::interrupt_controller::InterruptController; #[cfg(all(target_arch = "aarch64", feature = "guest_debug"))] use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] -use gdbstub_arch::x86::reg::{X86SegmentRegs, X86_64CoreRegs as CoreRegs}; +use gdbstub_arch::x86::reg::{X86_64CoreRegs as CoreRegs, X86SegmentRegs}; +#[cfg(target_arch = "x86_64")] +use hypervisor::CpuVendor; +#[cfg(feature = "kvm")] +use hypervisor::HypervisorType; +#[cfg(feature = "guest_debug")] +use hypervisor::StandardRegisters; #[cfg(all(target_arch = "aarch64", feature = "guest_debug"))] use hypervisor::arch::aarch64::regs::{ID_AA64MMFR0_EL1, TCR_EL1, TTBR1_EL1}; -#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] -use hypervisor::arch::x86::msr_index; #[cfg(target_arch = "x86_64")] use hypervisor::arch::x86::CpuIdEntry; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use hypervisor::arch::x86::MsrEntry; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use hypervisor::arch::x86::SpecialRegisters; +#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] +use hypervisor::arch::x86::msr_index; #[cfg(feature = "tdx")] use hypervisor::kvm::{TdxExitDetails, TdxExitStatus}; -#[cfg(target_arch = "x86_64")] -use hypervisor::CpuVendor; -#[cfg(feature = "kvm")] -use hypervisor::HypervisorType; -#[cfg(feature = "guest_debug")] -use hypervisor::StandardRegisters; use hypervisor::{CpuState, HypervisorCpuError, VmExit, VmOps}; use libc::{c_void, siginfo_t}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use linux_loader::elf::Elf64_Nhdr; -use seccompiler::{apply_filter, SeccompAction}; +use seccompiler::{SeccompAction, apply_filter}; use thiserror::Error; use tracer::trace_scoped; use vm_device::BusDevice; @@ -67,26 +67,26 @@ use vm_memory::ByteValued; use vm_memory::{Bytes, GuestAddressSpace}; use vm_memory::{GuestAddress, GuestMemoryAtomic}; use vm_migration::{ - snapshot_from_id, Migratable, MigratableError, Pausable, Snapshot, SnapshotData, Snapshottable, - Transportable, + Migratable, MigratableError, Pausable, Snapshot, SnapshotData, Snapshottable, Transportable, + snapshot_from_id, }; use vmm_sys_util::eventfd::EventFd; -use vmm_sys_util::signal::{register_signal_handler, SIGRTMIN}; +use vmm_sys_util::signal::{SIGRTMIN, register_signal_handler}; use zerocopy::{FromBytes, Immutable, IntoBytes}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::coredump::{ - CpuElf64Writable, CpuSegment, CpuState as DumpCpusState, DumpState, Elf64Writable, - GuestDebuggableError, NoteDescType, X86_64ElfPrStatus, X86_64UserRegs, COREDUMP_NAME_SIZE, - NT_PRSTATUS, + COREDUMP_NAME_SIZE, CpuElf64Writable, CpuSegment, CpuState as DumpCpusState, DumpState, + Elf64Writable, GuestDebuggableError, NT_PRSTATUS, NoteDescType, X86_64ElfPrStatus, + X86_64UserRegs, }; #[cfg(feature = "guest_debug")] -use crate::gdb::{get_raw_tid, Debuggable, DebuggableError}; -use crate::seccomp_filters::{get_seccomp_filter, Thread}; +use crate::gdb::{Debuggable, DebuggableError, get_raw_tid}; +use crate::seccomp_filters::{Thread, get_seccomp_filter}; #[cfg(target_arch = "x86_64")] use crate::vm::physical_bits; use crate::vm_config::CpusConfig; -use crate::{GuestMemoryMmap, CPU_MANAGER_SNAPSHOT_ID}; +use crate::{CPU_MANAGER_SNAPSHOT_ID, GuestMemoryMmap}; #[cfg(all(target_arch = "aarch64", feature = "guest_debug"))] /// Extract the specified bits of a 64-bit integer. @@ -1835,7 +1835,7 @@ impl CpuManager { _ => { return Err(Error::TranslateVirtualAddress(anyhow!(format!( "PA range not supported {pa_range}" - )))) + )))); } }; @@ -2858,8 +2858,8 @@ mod tests { use arch::layout::{BOOT_STACK_POINTER, ZERO_PAGE_START}; use arch::x86_64::interrupts::*; use arch::x86_64::regs::*; - use hypervisor::arch::x86::{FpuState, LapicState}; use hypervisor::StandardRegisters; + use hypervisor::arch::x86::{FpuState, LapicState}; use linux_loader::loader::bootparam::setup_header; #[test] @@ -2912,7 +2912,7 @@ mod tests { #[test] fn test_setup_msrs() { - use hypervisor::arch::x86::{msr_index, MsrEntry}; + use hypervisor::arch::x86::{MsrEntry, msr_index}; let hv = hypervisor::new().unwrap(); let vm = hv.create_vm().expect("new VM fd creation failed"); @@ -2997,6 +2997,7 @@ mod tests { use std::{mem, mem::offset_of}; use arch::layout; + use hypervisor::HypervisorCpuError; use hypervisor::arch::aarch64::regs::MPIDR_EL1; #[cfg(feature = "kvm")] use hypervisor::arm64_core_reg_id; @@ -3004,9 +3005,8 @@ mod tests { use hypervisor::kvm::aarch64::is_system_register; #[cfg(feature = "kvm")] use hypervisor::kvm::kvm_bindings::{ - user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_ARM_CORE, KVM_REG_SIZE_U64, + KVM_REG_ARM_CORE, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_SIZE_U64, user_pt_regs, }; - use hypervisor::HypervisorCpuError; #[test] fn test_setup_regs() { diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 256047e877..3bc814fd77 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -11,7 +11,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fs::{File, OpenOptions}; -use std::io::{self, stdout, IsTerminal, Seek, SeekFrom}; +use std::io::{self, IsTerminal, Seek, SeekFrom, stdout}; use std::num::Wrapping; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, FromRawFd}; @@ -23,14 +23,14 @@ use std::time::Instant; use acpi_tables::sdt::GenericAddress; #[cfg(not(target_arch = "riscv64"))] -use acpi_tables::{aml, Aml}; +use acpi_tables::{Aml, aml}; #[cfg(not(target_arch = "riscv64"))] use anyhow::anyhow; #[cfg(target_arch = "x86_64")] use arch::layout::{APIC_START, IOAPIC_SIZE, IOAPIC_START}; -use arch::{layout, NumaNodes}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use arch::{DeviceType, MmioDeviceInfo}; +use arch::{NumaNodes, layout}; use block::async_io::DiskFile; use block::fixed_vhd_sync::FixedVhdDiskSync; use block::qcow_sync::QcowDiskSync; @@ -38,7 +38,7 @@ use block::raw_async_aio::RawFileDiskAio; use block::raw_sync::RawFileDiskSync; use block::vhdx_sync::VhdxDiskSync; use block::{ - block_aio_is_supported, block_io_uring_is_supported, detect_image_type, qcow, vhdx, ImageType, + ImageType, block_aio_is_supported, block_io_uring_is_supported, detect_image_type, qcow, vhdx, }; #[cfg(feature = "io_uring")] use block::{fixed_vhd_async::FixedVhdDiskAsync, raw_async::RawFileDisk}; @@ -55,26 +55,26 @@ use devices::interrupt_controller::InterruptController; use devices::ioapic; #[cfg(feature = "ivshmem")] use devices::ivshmem::{IvshmemError, IvshmemOps}; -#[cfg(all(feature = "fw_cfg", target_arch = "x86_64"))] -use devices::legacy::fw_cfg::FW_CFG_ACPI_ID; #[cfg(target_arch = "aarch64")] use devices::legacy::Pl011; #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] use devices::legacy::Serial; +#[cfg(all(feature = "fw_cfg", target_arch = "x86_64"))] +use devices::legacy::fw_cfg::FW_CFG_ACPI_ID; #[cfg(feature = "fw_cfg")] use devices::legacy::{ - fw_cfg::{PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH}, FwCfg, + fw_cfg::{PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH}, }; #[cfg(feature = "pvmemcontrol")] use devices::pvmemcontrol::{PvmemcontrolBusDevice, PvmemcontrolPciDevice}; -use devices::{interrupt_controller, AcpiNotificationFlags}; +use devices::{AcpiNotificationFlags, interrupt_controller}; +use hypervisor::IoEventAddress; #[cfg(target_arch = "aarch64")] use hypervisor::arch::aarch64::regs::AARCH64_PMU_IRQ; -use hypervisor::IoEventAddress; use libc::{ - tcsetattr, termios, MAP_NORESERVE, MAP_PRIVATE, MAP_SHARED, O_TMPFILE, PROT_READ, PROT_WRITE, - TCSANOW, + MAP_NORESERVE, MAP_PRIVATE, MAP_SHARED, O_TMPFILE, PROT_READ, PROT_WRITE, TCSANOW, tcsetattr, + termios, }; use pci::{ DeviceRelocation, MmioRegion, PciBarRegionType, PciBdf, PciDevice, VfioDmaMapping, @@ -104,27 +104,27 @@ use vm_memory::{Address, GuestAddress, GuestMemoryRegion, GuestUsize, MmapRegion use vm_memory::{GuestAddressSpace, GuestMemory}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{ - snapshot_from_id, state_from_id, Migratable, MigratableError, Pausable, Snapshot, SnapshotData, - Snapshottable, Transportable, + Migratable, MigratableError, Pausable, Snapshot, SnapshotData, Snapshottable, Transportable, + snapshot_from_id, state_from_id, }; use vm_virtio::{AccessPlatform, VirtioDeviceType}; use vmm_sys_util::eventfd::EventFd; use crate::console_devices::{ConsoleDeviceError, ConsoleInfo, ConsoleOutput}; -use crate::cpu::{CpuManager, CPU_MANAGER_ACPI_SIZE}; +use crate::cpu::{CPU_MANAGER_ACPI_SIZE, CpuManager}; use crate::device_tree::{DeviceNode, DeviceTree}; use crate::interrupt::{LegacyUserspaceInterruptManager, MsiInterruptManager}; -use crate::memory_manager::{Error as MemoryManagerError, MemoryManager, MEMORY_MANAGER_ACPI_SIZE}; +use crate::memory_manager::{Error as MemoryManagerError, MEMORY_MANAGER_ACPI_SIZE, MemoryManager}; use crate::pci_segment::PciSegment; use crate::serial_manager::{Error as SerialManagerError, SerialManager}; #[cfg(feature = "ivshmem")] use crate::vm_config::IvshmemConfig; use crate::vm_config::{ - ConsoleOutputMode, DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, - VdpaConfig, VhostMode, VmConfig, VsockConfig, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, - DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT, + ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT, + DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, + VhostMode, VmConfig, VsockConfig, }; -use crate::{device_node, GuestRegionMmap, PciDeviceInfo, DEVICE_MANAGER_SNAPSHOT_ID}; +use crate::{DEVICE_MANAGER_SNAPSHOT_ID, GuestRegionMmap, PciDeviceInfo, device_node}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] const MMIO_LEN: u64 = 0x1000; diff --git a/vmm/src/gdb.rs b/vmm/src/gdb.rs index 16c9f64d98..ef4f4de8fd 100644 --- a/vmm/src/gdb.rs +++ b/vmm/src/gdb.rs @@ -11,24 +11,24 @@ use std::sync::mpsc; use gdbstub::arch::Arch; use gdbstub::common::{Signal, Tid}; use gdbstub::conn::{Connection, ConnectionExt}; -use gdbstub::stub::{run_blocking, DisconnectReason, MultiThreadStopReason}; +use gdbstub::stub::{DisconnectReason, MultiThreadStopReason, run_blocking}; +use gdbstub::target::ext::base::BaseOps; use gdbstub::target::ext::base::multithread::{ MultiThreadBase, MultiThreadResume, MultiThreadResumeOps, MultiThreadSingleStep, MultiThreadSingleStepOps, }; -use gdbstub::target::ext::base::BaseOps; use gdbstub::target::ext::breakpoints::{ Breakpoints, BreakpointsOps, HwBreakpoint, HwBreakpointOps, }; use gdbstub::target::{Target, TargetError, TargetResult}; #[cfg(target_arch = "aarch64")] -use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs; -#[cfg(target_arch = "aarch64")] use gdbstub_arch::aarch64::AArch64 as GdbArch; -#[cfg(target_arch = "x86_64")] -use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs; +#[cfg(target_arch = "aarch64")] +use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs; #[cfg(target_arch = "x86_64")] use gdbstub_arch::x86::X86_64_SSE as GdbArch; +#[cfg(target_arch = "x86_64")] +use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs; use thiserror::Error; use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryError}; diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 805bb94b41..97297a0920 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -10,21 +10,21 @@ use std::sync::{Arc, Mutex}; use igvm::snp_defs::SevVmsa; use igvm::{IgvmDirectiveHeader, IgvmFile, IgvmPlatformHeader, IsolationType}; +#[cfg(feature = "sev_snp")] +use igvm_defs::{IGVM_VHS_MEMORY_MAP_ENTRY, MemoryMapEntryType}; use igvm_defs::{ - IgvmPageDataType, IgvmPlatformType, IGVM_VHS_PARAMETER, IGVM_VHS_PARAMETER_INSERT, + IGVM_VHS_PARAMETER, IGVM_VHS_PARAMETER_INSERT, IgvmPageDataType, IgvmPlatformType, }; -#[cfg(feature = "sev_snp")] -use igvm_defs::{MemoryMapEntryType, IGVM_VHS_MEMORY_MAP_ENTRY}; use mshv_bindings::*; use thiserror::Error; use zerocopy::IntoBytes; +#[cfg(feature = "sev_snp")] +use crate::GuestMemoryMmap; use crate::cpu::CpuManager; use crate::igvm::loader::Loader; -use crate::igvm::{BootPageAcceptance, IgvmLoadedInfo, StartupMemoryType, HV_PAGE_SIZE}; +use crate::igvm::{BootPageAcceptance, HV_PAGE_SIZE, IgvmLoadedInfo, StartupMemoryType}; use crate::memory_manager::MemoryManager; -#[cfg(feature = "sev_snp")] -use crate::GuestMemoryMmap; #[derive(Debug, Error)] pub enum Error { diff --git a/vmm/src/igvm/loader.rs b/vmm/src/igvm/loader.rs index 215c84c50e..316cadb1ac 100644 --- a/vmm/src/igvm/loader.rs +++ b/vmm/src/igvm/loader.rs @@ -10,7 +10,7 @@ use vm_memory::{ GuestMemoryRegion, }; -use crate::igvm::{BootPageAcceptance, StartupMemoryType, HV_PAGE_SIZE}; +use crate::igvm::{BootPageAcceptance, HV_PAGE_SIZE, StartupMemoryType}; /// Structure to hold the guest memory info/layout to check /// the if the memory is accepted within the layout. diff --git a/vmm/src/landlock.rs b/vmm/src/landlock.rs index b7fbf6edb7..e7efd9cbcc 100644 --- a/vmm/src/landlock.rs +++ b/vmm/src/landlock.rs @@ -9,8 +9,8 @@ use std::path::Path; #[cfg(test)] use landlock::make_bitflags; use landlock::{ - path_beneath_rules, Access, AccessFs, BitFlags, Compatible, Ruleset, RulesetAttr, - RulesetCreated, RulesetCreatedAttr, RulesetError, ABI, + ABI, Access, AccessFs, BitFlags, Compatible, Ruleset, RulesetAttr, RulesetCreated, + RulesetCreatedAttr, RulesetError, path_beneath_rules, }; use thiserror::Error; @@ -59,7 +59,7 @@ impl TryFrom<&str> for LandlockAccess { _ => { return Err(LandlockError::InvalidLandlockAccess( format!("Invalid access: {c}").to_string(), - )) + )); } }; } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index cb430728b8..ef23793088 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -10,7 +10,7 @@ extern crate log; use std::collections::HashMap; use std::fs::File; -use std::io::{stdout, Read, Write}; +use std::io::{Read, Write, stdout}; use std::net::{TcpListener, TcpStream}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::net::{UnixListener, UnixStream}; @@ -29,12 +29,12 @@ use api::dbus::{DBusApiOptions, DBusApiShutdownChannels}; use api::http::HttpApiHandle; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] use arch::x86_64::MAX_SUPPORTED_CPUS_LEGACY; -use console_devices::{pre_create_console_devices, ConsoleInfo}; +use console_devices::{ConsoleInfo, pre_create_console_devices}; use landlock::LandlockError; -use libc::{tcsetattr, termios, EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW}; +use libc::{EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW, tcsetattr, termios}; use memory_manager::MemoryManagerSnapshotData; use pci::PciBdf; -use seccompiler::{apply_filter, SeccompAction}; +use seccompiler::{SeccompAction, apply_filter}; use serde::ser::{SerializeStruct, Serializer}; use serde::{Deserialize, Serialize}; use signal_hook::iterator::{Handle, Signals}; @@ -52,7 +52,7 @@ use crate::api::{ ApiRequest, ApiResponse, RequestHandler, VmInfoResponse, VmReceiveMigrationData, VmSendMigrationData, VmmPingResponse, }; -use crate::config::{add_to_config, RestoreConfig}; +use crate::config::{RestoreConfig, add_to_config}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::coredump::GuestDebuggable; use crate::landlock::Landlock; @@ -60,7 +60,7 @@ use crate::memory_manager::MemoryManager; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] use crate::migration::get_vm_snapshot; use crate::migration::{recv_vm_config, recv_vm_state}; -use crate::seccomp_filters::{get_seccomp_filter, Thread}; +use crate::seccomp_filters::{Thread, get_seccomp_filter}; use crate::vm::{Error as VmError, Vm, VmState}; use crate::vm_config::{ DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, @@ -2469,14 +2469,15 @@ mod unit_tests { )); let _ = vmm.vm_create(create_dummy_vm_config()); - assert!(vmm - .vm_config - .as_ref() - .unwrap() - .lock() - .unwrap() - .devices - .is_none()); + assert!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .devices + .is_none() + ); assert!(vmm.vm_add_device(device_config.clone()).unwrap().is_none()); assert_eq!( @@ -2516,19 +2517,21 @@ mod unit_tests { )); let _ = vmm.vm_create(create_dummy_vm_config()); - assert!(vmm - .vm_config - .as_ref() - .unwrap() - .lock() - .unwrap() - .user_devices - .is_none()); + assert!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .user_devices + .is_none() + ); - assert!(vmm - .vm_add_user_device(user_device_config.clone()) - .unwrap() - .is_none()); + assert!( + vmm.vm_add_user_device(user_device_config.clone()) + .unwrap() + .is_none() + ); assert_eq!( vmm.vm_config .as_ref() @@ -2565,14 +2568,15 @@ mod unit_tests { )); let _ = vmm.vm_create(create_dummy_vm_config()); - assert!(vmm - .vm_config - .as_ref() - .unwrap() - .lock() - .unwrap() - .disks - .is_none()); + assert!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .disks + .is_none() + ); assert!(vmm.vm_add_disk(disk_config.clone()).unwrap().is_none()); assert_eq!( @@ -2650,14 +2654,15 @@ mod unit_tests { )); let _ = vmm.vm_create(create_dummy_vm_config()); - assert!(vmm - .vm_config - .as_ref() - .unwrap() - .lock() - .unwrap() - .pmem - .is_none()); + assert!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .pmem + .is_none() + ); assert!(vmm.vm_add_pmem(pmem_config.clone()).unwrap().is_none()); assert_eq!( @@ -2699,14 +2704,15 @@ mod unit_tests { )); let _ = vmm.vm_create(create_dummy_vm_config()); - assert!(vmm - .vm_config - .as_ref() - .unwrap() - .lock() - .unwrap() - .net - .is_none()); + assert!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .net + .is_none() + ); assert!(vmm.vm_add_net(net_config.clone()).unwrap().is_none()); assert_eq!( @@ -2745,14 +2751,15 @@ mod unit_tests { )); let _ = vmm.vm_create(create_dummy_vm_config()); - assert!(vmm - .vm_config - .as_ref() - .unwrap() - .lock() - .unwrap() - .vdpa - .is_none()); + assert!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .vdpa + .is_none() + ); assert!(vmm.vm_add_vdpa(vdpa_config.clone()).unwrap().is_none()); assert_eq!( @@ -2791,14 +2798,15 @@ mod unit_tests { )); let _ = vmm.vm_create(create_dummy_vm_config()); - assert!(vmm - .vm_config - .as_ref() - .unwrap() - .lock() - .unwrap() - .vsock - .is_none()); + assert!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .vsock + .is_none() + ); assert!(vmm.vm_add_vsock(vsock_config.clone()).unwrap().is_none()); assert_eq!( diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 461eb24b9a..15225e9dc7 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -17,7 +17,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Barrier, Mutex}; use std::{ffi, result, thread}; -use acpi_tables::{aml, Aml}; +use acpi_tables::{Aml, aml}; use anyhow::anyhow; use arch::RegionType; #[cfg(target_arch = "x86_64")] @@ -278,7 +278,9 @@ pub enum Error { /// It's invalid to try applying a NUMA policy to a memory zone that is /// memory mapped with MAP_SHARED. - #[error("Invalid to try applying a NUMA policy to a memory zone that is memory mapped with MAP_SHARED")] + #[error( + "Invalid to try applying a NUMA policy to a memory zone that is memory mapped with MAP_SHARED" + )] InvalidSharedMemoryZoneWithHostNuma, /// Failed applying NUMA memory policy. @@ -2611,7 +2613,7 @@ impl Migratable for MemoryManager { return Err(MigratableError::MigrateSend(anyhow!( "Error finding 'guest memory region' with address {:x}", r.gpa - ))) + ))); } }; diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index 010859e05f..345869c1da 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -11,11 +11,11 @@ use std::sync::{Arc, Mutex}; -use acpi_tables::{aml, Aml}; +use acpi_tables::{Aml, aml}; use arch::layout; use pci::{DeviceRelocation, PciBdf, PciBus, PciConfigMmio, PciRoot}; #[cfg(target_arch = "x86_64")] -use pci::{PciConfigIo, PCI_CONFIG_IO_PORT, PCI_CONFIG_IO_PORT_SIZE}; +use pci::{PCI_CONFIG_IO_PORT, PCI_CONFIG_IO_PORT_SIZE, PciConfigIo}; use uuid::Uuid; use vm_allocator::AddressAllocator; use vm_device::BusDeviceSync; @@ -105,7 +105,12 @@ impl PciSegment { info!( "Adding PCI segment: id={}, PCI MMIO config address: 0x{:x}, mem32 area [0x{:x}-0x{:x}, mem64 area [0x{:x}-0x{:x}", - segment.id, segment.mmio_config_address, segment.start_of_mem32_area, segment.end_of_mem32_area, segment.start_of_mem64_area, segment.end_of_mem64_area + segment.id, + segment.mmio_config_address, + segment.start_of_mem32_area, + segment.end_of_mem32_area, + segment.start_of_mem64_area, + segment.end_of_mem64_area ); Ok(segment) } diff --git a/vmm/src/sigwinch_listener.rs b/vmm/src/sigwinch_listener.rs index 104a680a63..b50e93c986 100644 --- a/vmm/src/sigwinch_listener.rs +++ b/vmm/src/sigwinch_listener.rs @@ -3,10 +3,10 @@ use std::cell::RefCell; use std::collections::BTreeSet; -use std::fs::{read_dir, File}; +use std::fs::{File, read_dir}; use std::io::{self, ErrorKind, Read, Write}; use std::iter::once; -use std::mem::{size_of, MaybeUninit}; +use std::mem::{MaybeUninit, size_of}; use std::os::unix::prelude::*; use std::process::exit; use std::ptr::null_mut; @@ -14,15 +14,16 @@ use std::ptr::null_mut; use arch::_NSIG; use hypervisor::HypervisorType; use libc::{ - c_int, c_void, close, fork, getpgrp, ioctl, pipe2, poll, pollfd, setsid, sigemptyset, - siginfo_t, signal, sigprocmask, syscall, tcgetpgrp, tcsetpgrp, SYS_close_range, EINVAL, ENOSYS, - ENOTTY, O_CLOEXEC, POLLERR, SIGCHLD, SIGWINCH, SIG_DFL, SIG_SETMASK, STDERR_FILENO, TIOCSCTTY, + EINVAL, ENOSYS, ENOTTY, O_CLOEXEC, POLLERR, SIG_DFL, SIG_SETMASK, SIGCHLD, SIGWINCH, + STDERR_FILENO, SYS_close_range, TIOCSCTTY, c_int, c_void, close, fork, getpgrp, ioctl, pipe2, + poll, pollfd, setsid, sigemptyset, siginfo_t, signal, sigprocmask, syscall, tcgetpgrp, + tcsetpgrp, }; -use seccompiler::{apply_filter, BpfProgram, SeccompAction}; +use seccompiler::{BpfProgram, SeccompAction, apply_filter}; use vmm_sys_util::signal::register_signal_handler; -use crate::clone3::{clone3, clone_args, CLONE_CLEAR_SIGHAND}; -use crate::seccomp_filters::{get_seccomp_filter, Thread}; +use crate::clone3::{CLONE_CLEAR_SIGHAND, clone_args, clone3}; +use crate::seccomp_filters::{Thread, get_seccomp_filter}; thread_local! { // The tty file descriptor is stored in a global variable so it diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index d5e1e808d6..0e85b1ddc1 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -25,20 +25,20 @@ use std::time::Instant; use std::{cmp, result, str, thread}; use anyhow::anyhow; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +use arch::PciSpaceInfo; #[cfg(target_arch = "x86_64")] use arch::layout::{KVM_IDENTITY_MAP_START, KVM_TSS_START}; -#[cfg(feature = "tdx")] -use arch::x86_64::tdx::TdvfSection; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] use arch::x86_64::MAX_SUPPORTED_CPUS_LEGACY; -#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -use arch::PciSpaceInfo; -use arch::{get_host_cpu_phys_bits, EntryPoint, NumaNode, NumaNodes}; +#[cfg(feature = "tdx")] +use arch::x86_64::tdx::TdvfSection; +use arch::{EntryPoint, NumaNode, NumaNodes, get_host_cpu_phys_bits}; +use devices::AcpiNotificationFlags; #[cfg(target_arch = "aarch64")] use devices::interrupt_controller; #[cfg(feature = "fw_cfg")] use devices::legacy::fw_cfg::FwCfgItem; -use devices::AcpiNotificationFlags; #[cfg(all(target_arch = "aarch64", feature = "guest_debug"))] use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] @@ -46,17 +46,17 @@ use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs; #[cfg(target_arch = "aarch64")] use hypervisor::arch::aarch64::regs::AARCH64_PMU_IRQ; use hypervisor::{HypervisorVmError, VmOps}; -use libc::{termios, SIGWINCH}; +use libc::{SIGWINCH, termios}; use linux_loader::cmdline::Cmdline; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use linux_loader::elf; +use linux_loader::loader::KernelLoader; #[cfg(target_arch = "x86_64")] use linux_loader::loader::bzimage::BzImage; #[cfg(target_arch = "x86_64")] use linux_loader::loader::elf::PvhBootCapability::PvhEntryPresent; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use linux_loader::loader::pe::Error::InvalidImageMagicNumber; -use linux_loader::loader::KernelLoader; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -69,12 +69,12 @@ use vm_memory::{ }; use vm_migration::protocol::{MemoryRangeTable, Request, Response}; use vm_migration::{ - snapshot_from_id, Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, + Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, snapshot_from_id, }; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::sock_ctrl_msg::ScmSocket; -use crate::config::{add_to_config, ValidationError}; +use crate::config::{ValidationError, add_to_config}; use crate::console_devices::{ConsoleDeviceError, ConsoleInfo}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::coredump::{ @@ -94,7 +94,7 @@ use crate::memory_manager::{ use crate::migration::get_vm_snapshot; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::migration::url_to_file; -use crate::migration::{url_to_path, SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE}; +use crate::migration::{SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE, url_to_path}; #[cfg(feature = "fw_cfg")] use crate::vm_config::FwCfgConfig; use crate::vm_config::{ @@ -102,8 +102,8 @@ use crate::vm_config::{ PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; use crate::{ - cpu, GuestMemoryMmap, PciDeviceInfo, CPU_MANAGER_SNAPSHOT_ID, DEVICE_MANAGER_SNAPSHOT_ID, - MEMORY_MANAGER_SNAPSHOT_ID, + CPU_MANAGER_SNAPSHOT_ID, DEVICE_MANAGER_SNAPSHOT_ID, GuestMemoryMmap, + MEMORY_MANAGER_SNAPSHOT_ID, PciDeviceInfo, cpu, }; /// Errors associated with VM management @@ -1315,10 +1315,7 @@ impl Vm { return Self::load_igvm(igvm, memory_manager, cpu_manager); } } - match ( - &payload.firmware, - &payload.kernel, - ) { + match (&payload.firmware, &payload.kernel) { (Some(firmware), None) => { let firmware = File::open(firmware).map_err(Error::FirmwareFile)?; Self::load_kernel(firmware, None, memory_manager) @@ -1328,7 +1325,9 @@ impl Vm { let cmdline = Self::generate_cmdline(payload)?; Self::load_kernel(kernel, Some(cmdline), memory_manager) } - _ => unreachable!("Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'"), + _ => unreachable!( + "Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'" + ), } } @@ -1346,7 +1345,9 @@ impl Vm { let kernel = File::open(kernel).map_err(Error::KernelFile)?; Self::load_kernel(kernel, memory_manager) } - _ => unreachable!("Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'"), + _ => unreachable!( + "Unsupported boot configuration: programming error from 'PayloadConfigError::validate()'" + ), } } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 96269bfb27..7a581307e7 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -13,8 +13,8 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use virtio_devices::RateLimiterConfig; -use crate::landlock::LandlockError; use crate::Landlock; +use crate::landlock::LandlockError; pub type LandlockResult = result::Result; @@ -353,12 +353,16 @@ pub fn default_netconfig_tap() -> Option { } pub fn default_netconfig_ip() -> IpAddr { - warn!("Deprecation warning: No IP address provided. A default IP address is assigned. This behavior will be deprecated soon."); + warn!( + "Deprecation warning: No IP address provided. A default IP address is assigned. This behavior will be deprecated soon." + ); IpAddr::V4(Ipv4Addr::new(192, 168, 249, 1)) } pub fn default_netconfig_mask() -> IpAddr { - warn!("Deprecation warning: No network mask provided. A default network mask is assigned. This behavior will be deprecated soon."); + warn!( + "Deprecation warning: No network mask provided. A default network mask is assigned. This behavior will be deprecated soon." + ); IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0)) } @@ -383,7 +387,9 @@ where S: serde::Serializer, { if let Some(x) = x { - warn!("'NetConfig' contains FDs that can't be serialized correctly. Serializing them as invalid FDs."); + warn!( + "'NetConfig' contains FDs that can't be serialized correctly. Serializing them as invalid FDs." + ); let invalid_fds = vec![-1; x.len()]; s.serialize_some(&invalid_fds) } else { @@ -397,7 +403,9 @@ where { let invalid_fds: Option> = Option::deserialize(d)?; if let Some(invalid_fds) = invalid_fds { - warn!("'NetConfig' contains FDs that can't be deserialized correctly. Deserializing them as invalid FDs."); + warn!( + "'NetConfig' contains FDs that can't be deserialized correctly. Deserializing them as invalid FDs." + ); Ok(Some(vec![-1; invalid_fds.len()])) } else { Ok(None) From f73a6c8d8e0abf4400656b9c26ac28f832e3e92b Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 15 Aug 2025 08:58:55 +0200 Subject: [PATCH 0171/1893] build: treewide: clippy for edition 2024 This commit includes all simple clippy fixes excluding the collapsing of nested ifs using the let-chains feature. This follows in the next commit. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- tests/integration.rs | 3 +-- vmm/src/config.rs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index bdb258a22b..e18d7fb7b1 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -522,8 +522,7 @@ fn temp_snapshot_dir_path(tmp_dir: &TempDir) -> String { } fn temp_vmcore_file_path(tmp_dir: &TempDir) -> String { - let vmcore_file = String::from(tmp_dir.as_path().join("vmcore").to_str().unwrap()); - vmcore_file + String::from(tmp_dir.as_path().join("vmcore").to_str().unwrap()) } // Creates the path for direct kernel boot and return the path. diff --git a/vmm/src/config.rs b/vmm/src/config.rs index d213b3a2d8..1614dc0b51 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -636,6 +636,7 @@ impl CpusConfig { // list as it will always be checked for. #[allow(unused_mut)] let mut features = CpuFeatures::default(); + #[allow(clippy::never_loop)] for s in features_list.0 { match >::as_ref(&s) { #[cfg(target_arch = "x86_64")] From c995b72384ea532e237878beb65be692923254b7 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 15 Aug 2025 09:00:01 +0200 Subject: [PATCH 0172/1893] build: treewide: clippy: collapse nested ifs, use let chains This bumps the MSRV to 1.88 (also, Rust edition 2024 is mandatory). Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- api_client/src/lib.rs | 11 +- arch/src/aarch64/fdt.rs | 66 +++---- arch/src/x86_64/mod.rs | 49 ++--- block/src/qcow/mod.rs | 33 ++-- block/src/qcow/vec_cache.rs | 8 +- block/src/vhdx/mod.rs | 10 +- build.rs | 15 +- devices/src/tpm.rs | 5 +- hypervisor/src/arch/x86/emulator/mod.rs | 10 +- hypervisor/src/mshv/x86_64/emulator.rs | 24 +-- net_util/src/tap.rs | 10 +- pci/src/configuration.rs | 34 ++-- pci/src/msi.rs | 12 +- pci/src/vfio.rs | 180 +++++++++--------- pci/src/vfio_user.rs | 16 +- rate_limiter/src/group.rs | 8 +- src/main.rs | 8 +- test_infra/src/lib.rs | 24 +-- virtio-devices/src/console.rs | 19 +- virtio-devices/src/iommu.rs | 25 ++- virtio-devices/src/mem.rs | 8 +- virtio-devices/src/net.rs | 8 +- virtio-devices/src/thread_helper.rs | 12 +- .../src/transport/pci_common_config.rs | 52 +++-- virtio-devices/src/transport/pci_device.rs | 21 +- virtio-devices/src/vhost_user/blk.rs | 35 ++-- virtio-devices/src/vhost_user/fs.rs | 18 +- virtio-devices/src/vhost_user/net.rs | 35 ++-- .../src/vhost_user/vu_common_ctrl.rs | 17 +- virtio-devices/src/vsock/unix/muxer.rs | 8 +- virtio-devices/src/vsock/unix/muxer_killq.rs | 9 +- vm-allocator/src/address.rs | 8 +- vmm/src/config.rs | 145 +++++++------- vmm/src/cpu.rs | 13 +- vmm/src/device_manager.rs | 141 +++++++------- vmm/src/igvm/igvm_loader.rs | 10 +- vmm/src/lib.rs | 29 ++- vmm/src/memory_manager.rs | 32 ++-- vmm/src/serial_manager.rs | 12 +- vmm/src/vm.rs | 12 +- 40 files changed, 579 insertions(+), 613 deletions(-) diff --git a/api_client/src/lib.rs b/api_client/src/lib.rs index 52e85a3367..0ee7fa1d0b 100644 --- a/api_client/src/lib.rs +++ b/api_client/src/lib.rs @@ -118,12 +118,11 @@ fn parse_http_response(socket: &mut dyn Read) -> Result, Error> { } } - if let Some(body_offset) = body_offset { - if let Some(content_length) = content_length { - if res.len() >= content_length + body_offset { - break; - } - } + if let Some(body_offset) = body_offset + && let Some(content_length) = content_length + && res.len() >= content_length + body_offset + { + break; } } let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..]))); diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index e6b9722270..2755012440 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -999,39 +999,39 @@ fn create_pci_nodes( fdt.property_array_u32("msi-map", &msi_map)?; fdt.property_u32("msi-parent", MSI_PHANDLE)?; - if pci_device_info_elem.pci_segment_id == 0 { - if let Some(virtio_iommu_bdf) = virtio_iommu_bdf { - // See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt - // for 'iommu-map' attribute setting. - let iommu_map = [ - 0_u32, - VIRTIO_IOMMU_PHANDLE, - 0_u32, - virtio_iommu_bdf, - virtio_iommu_bdf + 1, - VIRTIO_IOMMU_PHANDLE, - virtio_iommu_bdf + 1, - 0xffff - virtio_iommu_bdf, - ]; - fdt.property_array_u32("iommu-map", &iommu_map)?; - - // See kernel document Documentation/devicetree/bindings/virtio/iommu.txt - // for virtio-iommu node settings. - let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}"); - let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?; - fdt.property_u32("#iommu-cells", 1)?; - fdt.property_string("compatible", "virtio,pci-iommu")?; - - // 'reg' is a five-cell address encoded as - // (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the - // device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells - // should be zero. - let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32]; - fdt.property_array_u32("reg", ®)?; - fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?; - - fdt.end_node(virtio_iommu_node)?; - } + if pci_device_info_elem.pci_segment_id == 0 + && let Some(virtio_iommu_bdf) = virtio_iommu_bdf + { + // See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt + // for 'iommu-map' attribute setting. + let iommu_map = [ + 0_u32, + VIRTIO_IOMMU_PHANDLE, + 0_u32, + virtio_iommu_bdf, + virtio_iommu_bdf + 1, + VIRTIO_IOMMU_PHANDLE, + virtio_iommu_bdf + 1, + 0xffff - virtio_iommu_bdf, + ]; + fdt.property_array_u32("iommu-map", &iommu_map)?; + + // See kernel document Documentation/devicetree/bindings/virtio/iommu.txt + // for virtio-iommu node settings. + let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}"); + let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?; + fdt.property_u32("#iommu-cells", 1)?; + fdt.property_string("compatible", "virtio,pci-iommu")?; + + // 'reg' is a five-cell address encoded as + // (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the + // device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells + // should be zero. + let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32]; + fdt.property_array_u32("reg", ®)?; + fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?; + + fdt.end_node(virtio_iommu_node)?; } fdt.end_node(pci_node)?; diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 71edd4508e..648220e070 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -810,31 +810,22 @@ pub fn configure_vcpu( ); // The TSC frequency CPUID leaf should not be included when running with HyperV emulation - if !kvm_hyperv { - if let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? { - // Need to check that the TSC doesn't vary with dynamic frequency - // SAFETY: cpuid called with valid leaves - if unsafe { std::arch::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 { - function: 0x4000_0010, - eax: tsc_khz, - ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's - * APIC_BUS_CYCLE_NS */ - ..Default::default() - }); - }; - } + 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 + // SAFETY: cpuid called with valid leaves + if unsafe { std::arch::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 { + function: 0x4000_0010, + eax: tsc_khz, + ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's + * APIC_BUS_CYCLE_NS */ + ..Default::default() + }); + }; } for c in &cpuid { @@ -932,10 +923,10 @@ pub fn configure_system( mptable::setup_mptable(offset, guest_mem, _num_cpus, topology).map_err(Error::MpTableSetup)?; // Check that the RAM is not smaller than the RSDP start address - if let Some(rsdp_addr) = rsdp_addr { - if rsdp_addr.0 > guest_mem.last_addr().0 { - return Err(super::Error::RsdpPastRamEnd); - } + if let Some(rsdp_addr) = rsdp_addr + && rsdp_addr.0 > guest_mem.last_addr().0 + { + return Err(super::Error::RsdpPastRamEnd); } match setup_header { diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index da68146919..14deafc856 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -287,11 +287,12 @@ impl QcowHeader { 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 { - if path.len() > max_length { - return Err(Error::BackingFileTooLong(path.len() - max_length)); - } + 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::() as u32; let num_clusters: u32 = div_round_up_u64(size, u64::from(cluster_size)) as u32; @@ -589,14 +590,12 @@ impl QcowFile { // Check for compressed blocks for l2_addr_disk in l1_table.get_values() { - if *l2_addr_disk != 0 { - if let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk) { - if let Some(os_error) = e.raw_os_error() { - if os_error == ENOTSUP { - return Err(Error::CompressedBlocksNotSupported); - } - } - } + if *l2_addr_disk != 0 + && let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk) + && let Some(os_error) = e.raw_os_error() + && os_error == ENOTSUP + { + return Err(Error::CompressedBlocksNotSupported); } } @@ -1584,11 +1583,11 @@ impl Seek for QcowFile { } }; - if let Some(o) = new_offset { - if o <= self.virtual_size() { - self.current_offset = o; - return Ok(o); - } + if let Some(o) = new_offset + && o <= self.virtual_size() + { + self.current_offset = o; + return Ok(o); } Err(std::io::Error::from_raw_os_error(EINVAL)) } diff --git a/block/src/qcow/vec_cache.rs b/block/src/qcow/vec_cache.rs index 76e5d44430..4b18518b0e 100644 --- a/block/src/qcow/vec_cache.rs +++ b/block/src/qcow/vec_cache.rs @@ -123,10 +123,10 @@ impl CacheMap { if self.map.len() == self.capacity { // TODO(dgreid) - smarter eviction strategy. let to_evict = *self.map.iter().next().unwrap().0; - if let Some(evicted) = self.map.remove(&to_evict) { - if evicted.dirty() { - write_callback(to_evict, evicted)?; - } + if let Some(evicted) = self.map.remove(&to_evict) + && evicted.dirty() + { + write_callback(to_evict, evicted)?; } } self.map.insert(index, block); diff --git a/block/src/vhdx/mod.rs b/block/src/vhdx/mod.rs index d46905d934..141c43c6d5 100644 --- a/block/src/vhdx/mod.rs +++ b/block/src/vhdx/mod.rs @@ -187,11 +187,11 @@ impl Seek for Vhdx { } }; - if let Some(o) = new_offset { - if o <= self.virtual_disk_size() { - self.current_offset = o; - return Ok(o); - } + if let Some(o) = new_offset + && o <= self.virtual_disk_size() + { + self.current_offset = o; + return Ok(o); } Err(std::io::Error::new( diff --git a/build.rs b/build.rs index 37a5ffd9fa..080c625599 100644 --- a/build.rs +++ b/build.rs @@ -9,14 +9,13 @@ use std::process::Command; fn main() { let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION"); - if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() { - if git_out.status.success() { - if let Ok(git_out_str) = String::from_utf8(git_out.stdout) { - version = git_out_str; - // Pop the trailing newline. - version.pop(); - } - } + if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() + && git_out.status.success() + && let Ok(git_out_str) = String::from_utf8(git_out.stdout) + { + version = git_out_str; + // Pop the trailing newline. + version.pop(); } // Append CH_EXTRA_VERSION to version if it is set. diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs index 4219f58308..72fef3e539 100644 --- a/devices/src/tpm.rs +++ b/devices/src/tpm.rs @@ -458,10 +458,9 @@ impl BusDevice for Tpm { CRB_CTRL_CANCEL => { if v == CRB_CANCEL_INVOKE && (self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE != 0) + && let Err(e) = self.emulator.cancel_cmd() { - if let Err(e) = self.emulator.cancel_cmd() { - error!("Failed to run cancel command. Error: {:?}", e); - } + error!("Failed to run cancel command. Error: {:?}", e); } } CRB_CTRL_START => { diff --git a/hypervisor/src/arch/x86/emulator/mod.rs b/hypervisor/src/arch/x86/emulator/mod.rs index 9cc8ac4721..778fd47f57 100644 --- a/hypervisor/src/arch/x86/emulator/mod.rs +++ b/hypervisor/src/arch/x86/emulator/mod.rs @@ -624,11 +624,11 @@ impl Emulator<'_, T> { last_decoded_ip = decoder.ip(); num_insn_emulated += 1; - if let Some(num_insn) = num_insn { - if num_insn_emulated >= num_insn { - // Exit the decoding loop, do not decode the next instruction. - stop_emulation = true; - } + if let Some(num_insn) = num_insn + && num_insn_emulated >= num_insn + { + // Exit the decoding loop, do not decode the next instruction. + stop_emulation = true; } } diff --git a/hypervisor/src/mshv/x86_64/emulator.rs b/hypervisor/src/mshv/x86_64/emulator.rs index a8f38ba86f..4ecdee2a42 100644 --- a/hypervisor/src/mshv/x86_64/emulator.rs +++ b/hypervisor/src/mshv/x86_64/emulator.rs @@ -44,12 +44,12 @@ impl MshvEmulatorContext<'_> { gpa ); - if let Some(vm_ops) = &self.vcpu.vm_ops { - if vm_ops.guest_mem_read(gpa, data).is_err() { - vm_ops - .mmio_read(gpa, data) - .map_err(|e| PlatformError::MemoryReadFailure(e.into()))?; - } + if let Some(vm_ops) = &self.vcpu.vm_ops + && vm_ops.guest_mem_read(gpa, data).is_err() + { + vm_ops + .mmio_read(gpa, data) + .map_err(|e| PlatformError::MemoryReadFailure(e.into()))?; } Ok(()) @@ -94,12 +94,12 @@ impl MshvEmulatorContext<'_> { gpa ); - if let Some(vm_ops) = &self.vcpu.vm_ops { - if vm_ops.guest_mem_write(gpa, data).is_err() { - vm_ops - .mmio_write(gpa, data) - .map_err(|e| PlatformError::MemoryWriteFailure(e.into()))?; - } + if let Some(vm_ops) = &self.vcpu.vm_ops + && vm_ops.guest_mem_write(gpa, data).is_err() + { + vm_ops + .mmio_write(gpa, data) + .map_err(|e| PlatformError::MemoryWriteFailure(e.into()))?; } Ok(()) diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 2544b9eee7..2916e66cf6 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -866,11 +866,11 @@ mod tests { let p = ParsedPkt::new(buf); p.print(); - if let Some(ref udp) = p.udp { - if payload == udp.payload() { - channel_tx.send(true).unwrap(); - break; - } + if let Some(ref udp) = p.udp + && payload == udp.payload() + { + channel_tx.send(true).unwrap(); + break; } } }); diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index 7264f7caf1..706947050c 100644 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -828,10 +828,10 @@ impl PciConfiguration { let mut addr = u64::from(self.bars[bar_num].addr & self.writable_bits[bar_idx]); - if let Some(bar_type) = self.bars[bar_num].r#type { - if bar_type == PciBarRegionType::Memory64BitRegion { - addr |= u64::from(self.bars[bar_num + 1].addr) << 32; - } + if let Some(bar_type) = self.bars[bar_num].r#type + && bar_type == PciBarRegionType::Memory64BitRegion + { + addr |= u64::from(self.bars[bar_num + 1].addr) << 32; } addr @@ -907,19 +907,19 @@ impl PciConfiguration { } // Handle potential write to MSI-X message control register - if let Some(msix_cap_reg_idx) = self.msix_cap_reg_idx { - if let Some(msix_config) = &self.msix_config { - if msix_cap_reg_idx == reg_idx && offset == 2 && data.len() == 2 { - msix_config - .lock() - .unwrap() - .set_msg_ctl(LittleEndian::read_u16(data)); - } else if msix_cap_reg_idx == reg_idx && offset == 0 && data.len() == 4 { - msix_config - .lock() - .unwrap() - .set_msg_ctl((LittleEndian::read_u32(data) >> 16) as u16); - } + if let Some(msix_cap_reg_idx) = self.msix_cap_reg_idx + && let Some(msix_config) = &self.msix_config + { + if msix_cap_reg_idx == reg_idx && offset == 2 && data.len() == 2 { + msix_config + .lock() + .unwrap() + .set_msg_ctl(LittleEndian::read_u16(data)); + } else if msix_cap_reg_idx == reg_idx && offset == 0 && data.len() == 4 { + msix_config + .lock() + .unwrap() + .set_msg_ctl((LittleEndian::read_u32(data) >> 16) as u16); } } diff --git a/pci/src/msi.rs b/pci/src/msi.rs index ebb7aa3e90..a0215dcdd3 100644 --- a/pci/src/msi.rs +++ b/pci/src/msi.rs @@ -271,15 +271,11 @@ impl MsiConfig { } } - if !old_enabled { - if let Err(e) = self.interrupt_source_group.enable() { - error!("Failed enabling irq_fd: {:?}", e); - } - } - } else if old_enabled { - if let Err(e) = self.interrupt_source_group.disable() { - error!("Failed disabling irq_fd: {:?}", e); + if !old_enabled && let Err(e) = self.interrupt_source_group.enable() { + error!("Failed enabling irq_fd: {:?}", e); } + } else if old_enabled && let Err(e) = self.interrupt_source_group.disable() { + error!("Failed disabling irq_fd: {:?}", e); } } } diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 97be4a7bc1..8372046acf 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -208,21 +208,20 @@ impl Interrupt { } fn accessed(&self, offset: u64) -> Option<(PciCapabilityId, u64)> { - if let Some(msi) = &self.msi { - if offset >= u64::from(msi.cap_offset) - && offset < u64::from(msi.cap_offset) + msi.cfg.size() - { - return Some(( - PciCapabilityId::MessageSignalledInterrupts, - u64::from(msi.cap_offset), - )); - } + if let Some(msi) = &self.msi + && offset >= u64::from(msi.cap_offset) + && offset < u64::from(msi.cap_offset) + msi.cfg.size() + { + return Some(( + PciCapabilityId::MessageSignalledInterrupts, + u64::from(msi.cap_offset), + )); } - if let Some(msix) = &self.msix { - if offset == u64::from(msix.cap_offset) { - return Some((PciCapabilityId::MsiX, u64::from(msix.cap_offset))); - } + if let Some(msix) = &self.msix + && offset == u64::from(msix.cap_offset) + { + return Some((PciCapabilityId::MsiX, u64::from(msix.cap_offset))); } None @@ -603,13 +602,12 @@ impl VfioCommon { type_, .. } = resource + && *index == bar_id as usize { - if *index == bar_id as usize { - restored_bar_addr = Some(GuestAddress(*base)); - region_size = *size; - region_type = PciBarRegionType::from(*type_); - break; - } + restored_bar_addr = Some(GuestAddress(*base)); + region_size = *size; + region_type = PciBarRegionType::from(*type_); + break; } } if restored_bar_addr.is_none() { @@ -925,24 +923,23 @@ impl VfioCommon { match PciCapabilityId::from(cap_id) { PciCapabilityId::MessageSignalledInterrupts => { - if let Some(irq_info) = self.vfio_wrapper.get_irq_info(VFIO_PCI_MSI_IRQ_INDEX) { - if irq_info.count > 0 { - // Parse capability only if the VFIO device - // supports MSI. - let msg_ctl = self.parse_msi_capabilities(cap_iter); - self.initialize_msi(msg_ctl, cap_iter as u32, None); - } + if let Some(irq_info) = self.vfio_wrapper.get_irq_info(VFIO_PCI_MSI_IRQ_INDEX) + && irq_info.count > 0 + { + // Parse capability only if the VFIO device + // supports MSI. + let msg_ctl = self.parse_msi_capabilities(cap_iter); + self.initialize_msi(msg_ctl, cap_iter as u32, None); } } PciCapabilityId::MsiX => { if let Some(irq_info) = self.vfio_wrapper.get_irq_info(VFIO_PCI_MSIX_IRQ_INDEX) + && irq_info.count > 0 { - if irq_info.count > 0 { - // Parse capability only if the VFIO device - // supports MSI-X. - let msix_cap = self.parse_msix_capabilities(cap_iter); - self.initialize_msix(msix_cap, cap_iter as u32, bdf, None); - } + // Parse capability only if the VFIO device + // supports MSI-X. + let msix_cap = self.parse_msix_capabilities(cap_iter); + self.initialize_msix(msix_cap, cap_iter as u32, bdf, None); } } PciCapabilityId::PciExpress => pci_express_cap_found = true, @@ -1038,17 +1035,17 @@ impl VfioCommon { } pub(crate) fn enable_intx(&mut self) -> Result<(), VfioPciError> { - if let Some(intx) = &mut self.interrupt.intx { - if !intx.enabled { - if let Some(eventfd) = intx.interrupt_source_group.notifier(0) { - self.vfio_wrapper - .enable_irq(VFIO_PCI_INTX_IRQ_INDEX, vec![&eventfd]) - .map_err(VfioPciError::EnableIntx)?; + if let Some(intx) = &mut self.interrupt.intx + && !intx.enabled + { + if let Some(eventfd) = intx.interrupt_source_group.notifier(0) { + self.vfio_wrapper + .enable_irq(VFIO_PCI_INTX_IRQ_INDEX, vec![&eventfd]) + .map_err(VfioPciError::EnableIntx)?; - intx.enabled = true; - } else { - return Err(VfioPciError::MissingNotifier); - } + intx.enabled = true; + } else { + return Err(VfioPciError::MissingNotifier); } } @@ -1056,13 +1053,13 @@ impl VfioCommon { } pub(crate) fn disable_intx(&mut self) { - if let Some(intx) = &mut self.interrupt.intx { - if intx.enabled { - if let Err(e) = self.vfio_wrapper.disable_irq(VFIO_PCI_INTX_IRQ_INDEX) { - error!("Could not disable INTx: {}", e); - } else { - intx.enabled = false; - } + if let Some(intx) = &mut self.interrupt.intx + && intx.enabled + { + if let Err(e) = self.vfio_wrapper.disable_irq(VFIO_PCI_INTX_IRQ_INDEX) { + error!("Could not disable INTx: {}", e); + } else { + intx.enabled = false; } } } @@ -1118,12 +1115,12 @@ impl VfioCommon { } fn initialize_legacy_interrupt(&mut self) -> Result<(), VfioPciError> { - if let Some(irq_info) = self.vfio_wrapper.get_irq_info(VFIO_PCI_INTX_IRQ_INDEX) { - if irq_info.count == 0 { - // A count of 0 means the INTx IRQ is not supported, therefore - // it shouldn't be initialized. - return Ok(()); - } + if let Some(irq_info) = self.vfio_wrapper.get_irq_info(VFIO_PCI_INTX_IRQ_INDEX) + && irq_info.count == 0 + { + // A count of 0 means the INTx IRQ is not supported, therefore + // it shouldn't be initialized. + return Ok(()); } if let Some(interrupt_source_group) = self.legacy_interrupt_group.clone() { @@ -1200,10 +1197,10 @@ impl VfioCommon { // INTx EOI // The guest reading from the BAR potentially means the interrupt has // been received and can be acknowledged. - if self.interrupt.intx_in_use() { - if let Err(e) = self.vfio_wrapper.unmask_irq(VFIO_PCI_INTX_IRQ_INDEX) { - error!("Failed unmasking INTx IRQ: {}", e); - } + if self.interrupt.intx_in_use() + && let Err(e) = self.vfio_wrapper.unmask_irq(VFIO_PCI_INTX_IRQ_INDEX) + { + error!("Failed unmasking INTx IRQ: {}", e); } } @@ -1228,10 +1225,10 @@ impl VfioCommon { // INTx EOI // The guest writing to the BAR potentially means the interrupt has // been received and can be acknowledged. - if self.interrupt.intx_in_use() { - if let Err(e) = self.vfio_wrapper.unmask_irq(VFIO_PCI_INTX_IRQ_INDEX) { - error!("Failed unmasking INTx IRQ: {}", e); - } + if self.interrupt.intx_in_use() + && let Err(e) = self.vfio_wrapper.unmask_irq(VFIO_PCI_INTX_IRQ_INDEX) + { + error!("Failed unmasking INTx IRQ: {}", e); } None @@ -1619,12 +1616,11 @@ impl VfioPciDevice { // Don't try to mmap the region if it contains MSI-X table or // MSI-X PBA subregion, and if we couldn't find MSIX_MAPPABLE // in the list of supported capabilities. - if let Some(msix) = self.common.interrupt.msix.as_ref() { - if (region.index == msix.cap.table_bir() || region.index == msix.cap.pba_bir()) - && !caps.contains(&VfioRegionInfoCap::MsixMappable) - { - continue; - } + if let Some(msix) = self.common.interrupt.msix.as_ref() + && (region.index == msix.cap.table_bir() || region.index == msix.cap.pba_bir()) + && !caps.contains(&VfioRegionInfoCap::MsixMappable) + { + continue; } let mmap_size = self.device.get_region_size(region.index); @@ -1713,18 +1709,17 @@ impl VfioPciDevice { for region in self.common.mmio_regions.iter() { for user_memory_region in region.user_memory_regions.iter() { // Unmap from vfio container - if !self.iommu_attached { - if let Err(e) = self + if !self.iommu_attached + && let Err(e) = self .container .vfio_dma_unmap(user_memory_region.start, user_memory_region.size) .map_err(|e| VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf)) - { - error!( - "Could not unmap mmio region from vfio container: \ + { + error!( + "Could not unmap mmio region from vfio container: \ iova 0x{:x}, size 0x{:x}: {}, ", - user_memory_region.start, user_memory_region.size, e - ); - } + user_memory_region.start, user_memory_region.size, e + ); } // Remove region @@ -1791,16 +1786,16 @@ impl Drop for VfioPciDevice { fn drop(&mut self) { self.unmap_mmio_regions(); - if let Some(msix) = &self.common.interrupt.msix { - if msix.bar.enabled() { - self.common.disable_msix(); - } + if let Some(msix) = &self.common.interrupt.msix + && msix.bar.enabled() + { + self.common.disable_msix(); } - if let Some(msi) = &self.common.interrupt.msi { - if msi.cfg.enabled() { - self.common.disable_msi() - } + if let Some(msi) = &self.common.interrupt.msi + && msi.cfg.enabled() + { + self.common.disable_msi() } if self.common.interrupt.intx_in_use() { @@ -1898,20 +1893,19 @@ impl PciDevice for VfioPciDevice { for user_memory_region in region.user_memory_regions.iter_mut() { // Unmap the old MMIO region from vfio container - if !self.iommu_attached { - if let Err(e) = self + if !self.iommu_attached + && let Err(e) = self .container .vfio_dma_unmap(user_memory_region.start, user_memory_region.size) .map_err(|e| { VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf) }) - { - error!( - "Could not unmap mmio region from vfio container: \ + { + error!( + "Could not unmap mmio region from vfio container: \ iova 0x{:x}, size 0x{:x}: {}, ", - user_memory_region.start, user_memory_region.size, e - ); - } + user_memory_region.start, user_memory_region.size, e + ); } // Remove old region diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index 7ca1d28814..e92c719964 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -505,16 +505,16 @@ impl Drop for VfioUserPciDevice { fn drop(&mut self) { self.unmap_mmio_regions(); - if let Some(msix) = &self.common.interrupt.msix { - if msix.bar.enabled() { - self.common.disable_msix(); - } + if let Some(msix) = &self.common.interrupt.msix + && msix.bar.enabled() + { + self.common.disable_msix(); } - if let Some(msi) = &self.common.interrupt.msi { - if msi.cfg.enabled() { - self.common.disable_msi() - } + if let Some(msi) = &self.common.interrupt.msi + && msi.cfg.enabled() + { + self.common.disable_msi() } if self.common.interrupt.intx_in_use() { diff --git a/rate_limiter/src/group.rs b/rate_limiter/src/group.rs index f883115641..a986a7f581 100644 --- a/rate_limiter/src/group.rs +++ b/rate_limiter/src/group.rs @@ -288,10 +288,10 @@ impl Drop for RateLimiterGroup { fn drop(&mut self) { self.kill_evt.write(1).unwrap(); - if let Some(t) = self.epoll_thread.take() { - if let Err(e) = t.join() { - error!("Error joining thread: {:?}", e); - } + if let Some(t) = self.epoll_thread.take() + && let Err(e) = t.join() + { + error!("Error joining thread: {:?}", e); } } } diff --git a/src/main.rs b/src/main.rs index 3c94f36d88..7bdcbfac8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -754,10 +754,10 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { Ok(()) })(); - if r.is_err() { - if let Err(e) = exit_evt.write(1) { - warn!("writing to exit EventFd: {e}"); - } + if r.is_err() + && let Err(e) = exit_evt.write(1) + { + warn!("writing to exit EventFd: {e}"); } if landlock_enable { diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 641c5a15ab..7dceaecce1 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -481,13 +481,14 @@ pub fn rate_limited_copy, Q: AsRef>(from: P, to: Q) -> io:: match fs::copy(&from, &to) { Err(e) => { - if let Some(errno) = e.raw_os_error() { - if errno == libc::ENOSPC { - eprintln!("Copy returned ENOSPC. Attempt {i} of 10. Sleeping."); - thread::sleep(std::time::Duration::new(60, 0)); - continue; - } + if let Some(errno) = e.raw_os_error() + && errno == libc::ENOSPC + { + eprintln!("Copy returned ENOSPC. Attempt {i} of 10. Sleeping."); + thread::sleep(std::time::Duration::new(60, 0)); + continue; } + return Err(e); } Ok(i) => return Ok(i), @@ -1094,12 +1095,11 @@ impl Guest { let vendors: Vec<&str> = vendors.split('\n').collect(); for (index, d_id) in devices.iter().enumerate() { - if *d_id == device_id { - if let Some(v_id) = vendors.get(index) { - if *v_id == vendor_id { - return Ok(true); - } - } + if *d_id == device_id + && let Some(v_id) = vendors.get(index) + && *v_id == vendor_id + { + return Ok(true); } } diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index 2c0e250a4d..6f237b5eed 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -454,12 +454,11 @@ impl EpollHelperHandler for ConsoleEpollHandler { } if self.endpoint.is_pty() { self.file_event_registered = false; - if event.events & libc::EPOLLHUP as u32 != 0 { - if let Some(pty_write_out) = &self.write_out { - if pty_write_out.load(Ordering::Acquire) { - pty_write_out.store(false, Ordering::Release); - } - } + if event.events & libc::EPOLLHUP as u32 != 0 + && let Some(pty_write_out) = &self.write_out + && pty_write_out.load(Ordering::Acquire) + { + pty_write_out.store(false, Ordering::Release); } else { // If the EPOLLHUP flag is not up on the associated event, we // can assume the other end of the PTY is connected and therefore @@ -731,10 +730,10 @@ impl VirtioDevice for Console { .acked_features .store(self.common.acked_features, Ordering::Relaxed); - if self.common.feature_acked(VIRTIO_CONSOLE_F_SIZE) { - if let Err(e) = interrupt_cb.trigger(VirtioInterruptType::Config) { - error!("Failed to signal console driver: {:?}", e); - } + if self.common.feature_acked(VIRTIO_CONSOLE_F_SIZE) + && let Err(e) = interrupt_cb.trigger(VirtioInterruptType::Config) + { + error!("Failed to signal console driver: {:?}", e); } let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index 71c10c1d0c..f2795a8cff 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -421,13 +421,12 @@ impl Request { // If any other mappings exist in the domain for other containers, // make sure to issue these mappings for the new endpoint/container if let Some(domain_mappings) = &mapping.domains.read().unwrap().get(&domain_id) + && let Some(ext_map) = ext_mapping.get(&endpoint) { - if let Some(ext_map) = ext_mapping.get(&endpoint) { - for (virt_start, addr_map) in &domain_mappings.mappings { - ext_map - .map(*virt_start, addr_map.gpa, addr_map.size) - .map_err(Error::ExternalUnmapping)?; - } + for (virt_start, addr_map) in &domain_mappings.mappings { + ext_map + .map(*virt_start, addr_map.gpa, addr_map.size) + .map_err(Error::ExternalUnmapping)?; } } @@ -654,13 +653,13 @@ fn detach_endpoint_from_domain( mapping.endpoints.write().unwrap().remove(&endpoint); // Trigger external unmapping for the endpoint if necessary. - if let Some(domain_mappings) = &mapping.domains.read().unwrap().get(&domain_id) { - if let Some(ext_map) = ext_mapping.get(&endpoint) { - for (virt_start, addr_map) in &domain_mappings.mappings { - ext_map - .unmap(*virt_start, addr_map.size) - .map_err(Error::ExternalUnmapping)?; - } + if let Some(domain_mappings) = &mapping.domains.read().unwrap().get(&domain_id) + && let Some(ext_map) = ext_mapping.get(&endpoint) + { + for (virt_start, addr_map) in &domain_mappings.mappings { + ext_map + .unmap(*virt_start, addr_map.size) + .map_err(Error::ExternalUnmapping)?; } } diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 6971f07151..7893be6b1a 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -475,11 +475,9 @@ impl MemEpollHandler { return VIRTIO_MEM_RESP_ERROR; } - if !plug { - if let Err(e) = self.discard_memory_range(offset, size) { - error!("failed discarding memory range: {:?}", e); - return VIRTIO_MEM_RESP_ERROR; - } + if !plug && let Err(e) = self.discard_memory_range(offset, size) { + error!("failed discarding memory range: {:?}", e); + return VIRTIO_MEM_RESP_ERROR; } self.blocks_state diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 4518321ecf..bbe0a8b37f 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -667,10 +667,10 @@ impl Drop for Net { } // Needed to ensure all references to tap FDs are dropped (#4868) self.common.wait_for_epoll_threads(); - if let Some(thread) = self.ctrl_queue_epoll_thread.take() { - if let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); - } + if let Some(thread) = self.ctrl_queue_epoll_thread.take() + && let Err(e) = thread.join() + { + error!("Error joining thread: {:?}", e); } } } diff --git a/virtio-devices/src/thread_helper.rs b/virtio-devices/src/thread_helper.rs index d4df6c86b6..74aaddf8d4 100644 --- a/virtio-devices/src/thread_helper.rs +++ b/virtio-devices/src/thread_helper.rs @@ -36,12 +36,12 @@ where thread::Builder::new() .name(name.to_string()) .spawn(move || { - if !seccomp_filter.is_empty() { - if let Err(e) = apply_filter(&seccomp_filter) { - error!("Error applying seccomp filter: {:?}", e); - thread_exit_evt.write(1).ok(); - return; - } + if !seccomp_filter.is_empty() + && let Err(e) = apply_filter(&seccomp_filter) + { + error!("Error applying seccomp filter: {:?}", e); + thread_exit_evt.write(1).ok(); + return; } match std::panic::catch_unwind(AssertUnwindSafe(f)) { Err(_) => { diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index c87e36e92b..549453a5d6 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -261,36 +261,28 @@ impl VirtioPciCommonConfig { let ready = value == 1; q.set_ready(ready); // Translate address of descriptor table and vrings. - if let Some(access_platform) = &self.access_platform { - if ready { - let desc_table = access_platform - .translate_gva( - q.desc_table(), - get_vring_size(VringType::Desc, q.size()), - ) - .unwrap(); - let avail_ring = access_platform - .translate_gva( - q.avail_ring(), - get_vring_size(VringType::Avail, q.size()), - ) - .unwrap(); - let used_ring = access_platform - .translate_gva(q.used_ring(), get_vring_size(VringType::Used, q.size())) - .unwrap(); - q.set_desc_table_address( - Some((desc_table & 0xffff_ffff) as u32), - Some((desc_table >> 32) as u32), - ); - q.set_avail_ring_address( - Some((avail_ring & 0xffff_ffff) as u32), - Some((avail_ring >> 32) as u32), - ); - q.set_used_ring_address( - Some((used_ring & 0xffff_ffff) as u32), - Some((used_ring >> 32) as u32), - ); - } + if ready && let Some(access_platform) = &self.access_platform { + let desc_table = access_platform + .translate_gva(q.desc_table(), get_vring_size(VringType::Desc, q.size())) + .unwrap(); + let avail_ring = access_platform + .translate_gva(q.avail_ring(), get_vring_size(VringType::Avail, q.size())) + .unwrap(); + let used_ring = access_platform + .translate_gva(q.used_ring(), get_vring_size(VringType::Used, q.size())) + .unwrap(); + q.set_desc_table_address( + Some((desc_table & 0xffff_ffff) as u32), + Some((desc_table >> 32) as u32), + ); + q.set_avail_ring_address( + Some((avail_ring & 0xffff_ffff) as u32), + Some((avail_ring >> 32) as u32), + ); + q.set_used_ring_address( + Some((used_ring & 0xffff_ffff) as u32), + Some((used_ring >> 32) as u32), + ); } }), _ => { diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index a16102218e..b05020bb47 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -968,18 +968,17 @@ impl PciDevice for VirtioPciDevice { if let Resource::PciBar { index, base, type_, .. } = resource + && index == VIRTIO_COMMON_BAR_INDEX { - if index == VIRTIO_COMMON_BAR_INDEX { - settings_bar_addr = Some(GuestAddress(base)); - use_64bit_bar = match type_ { - PciBarType::Io => { - return Err(PciDeviceError::InvalidResource(resource)); - } - PciBarType::Mmio32 => false, - PciBarType::Mmio64 => true, - }; - break; - } + settings_bar_addr = Some(GuestAddress(base)); + use_64bit_bar = match type_ { + PciBarType::Io => { + return Err(PciDeviceError::InvalidResource(resource)); + } + PciBarType::Mmio32 => false, + PciBarType::Mmio64 => true, + }; + break; } } // Error out if no resource was matching the BAR id. diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index d009280a86..7c0e20c1ac 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -213,16 +213,16 @@ impl Blk { impl Drop for Blk { fn drop(&mut self) { - if let Some(kill_evt) = self.common.kill_evt.take() { - if let Err(e) = kill_evt.write(1) { - error!("failed to kill vhost-user-blk: {:?}", e); - } + if let Some(kill_evt) = self.common.kill_evt.take() + && let Err(e) = kill_evt.write(1) + { + error!("failed to kill vhost-user-blk: {:?}", e); } self.common.wait_for_epoll_threads(); - if let Some(thread) = self.epoll_thread.take() { - if let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); - } + if let Some(thread) = self.epoll_thread.take() + && let Err(e) = thread.join() + { + error!("Error joining thread: {:?}", e); } } } @@ -267,16 +267,15 @@ impl VirtioDevice for Blk { } self.config.writeback = data[0]; - if let Some(vu) = &self.vu_common.vu { - if let Err(e) = vu + if let Some(vu) = &self.vu_common.vu + && let Err(e) = vu .lock() .unwrap() .socket_handle() .set_config(offset as u32, VhostUserConfigFlags::WRITABLE, data) .map_err(Error::VhostUserSetConfig) - { - error!("Failed setting vhost-user-blk configuration: {:?}", e); - } + { + error!("Failed setting vhost-user-blk configuration: {:?}", e); } } @@ -329,11 +328,11 @@ impl VirtioDevice for Blk { self.common.resume().ok()?; } - if let Some(vu) = &self.vu_common.vu { - if let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {:?}", e); - return None; - } + if let Some(vu) = &self.vu_common.vu + && let Err(e) = vu.lock().unwrap().reset_vhost_user() + { + error!("Failed to reset vhost-user daemon: {:?}", e); + return None; } if let Some(kill_evt) = self.common.kill_evt.take() { diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 4c70db2f1a..c420bb7a33 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -227,10 +227,10 @@ impl Drop for Fs { let _ = kill_evt.write(1); } self.common.wait_for_epoll_threads(); - if let Some(thread) = self.epoll_thread.take() { - if let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); - } + if let Some(thread) = self.epoll_thread.take() + && let Err(e) = thread.join() + { + error!("Error joining thread: {:?}", e); } } } @@ -308,11 +308,11 @@ impl VirtioDevice for Fs { self.common.resume().ok()?; } - if let Some(vu) = &self.vu_common.vu { - if let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {:?}", e); - return None; - } + if let Some(vu) = &self.vu_common.vu + && let Err(e) = vu.lock().unwrap().reset_vhost_user() + { + error!("Failed to reset vhost-user daemon: {:?}", e); + return None; } if let Some(kill_evt) = self.common.kill_evt.take() { diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 99c0f81555..0f4561bca5 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -243,23 +243,24 @@ impl Net { impl Drop for Net { fn drop(&mut self) { - if let Some(kill_evt) = self.common.kill_evt.take() { - if let Err(e) = kill_evt.write(1) { - error!("failed to kill vhost-user-net: {:?}", e); - } + if let Some(kill_evt) = self.common.kill_evt.take() + && let Err(e) = kill_evt.write(1) + { + error!("failed to kill vhost-user-net: {:?}", e); } self.common.wait_for_epoll_threads(); - if let Some(thread) = self.epoll_thread.take() { - if let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); - } + if let Some(thread) = self.epoll_thread.take() + && let Err(e) = thread.join() + { + error!("Error joining thread: {:?}", e); } - if let Some(thread) = self.ctrl_queue_epoll_thread.take() { - if let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); - } + + if let Some(thread) = self.ctrl_queue_epoll_thread.take() + && let Err(e) = thread.join() + { + error!("Error joining thread: {:?}", e); } } } @@ -382,11 +383,11 @@ impl VirtioDevice for Net { self.common.resume().ok()?; } - if let Some(vu) = &self.vu_common.vu { - if let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {:?}", e); - return None; - } + if let Some(vu) = &self.vu_common.vu + && let Err(e) = vu.lock().unwrap().reset_vhost_user() + { + error!("Failed to reset vhost-user daemon: {:?}", e); + return None; } if let Some(kill_evt) = self.common.kill_evt.take() { diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 91fb55a207..05034d0ec5 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -317,17 +317,16 @@ impl VhostUserHandle { .get_features() .map_err(Error::VhostUserGetFeatures)?; - if acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() != 0 { - if let Some(acked_protocol_features) = + if acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() != 0 + && let Some(acked_protocol_features) = VhostUserProtocolFeatures::from_bits(acked_protocol_features) - { - self.vu - .set_protocol_features(acked_protocol_features) - .map_err(Error::VhostUserSetProtocolFeatures)?; + { + self.vu + .set_protocol_features(acked_protocol_features) + .map_err(Error::VhostUserSetProtocolFeatures)?; - if acked_protocol_features.contains(VhostUserProtocolFeatures::REPLY_ACK) { - self.vu.set_hdr_flags(VhostUserHeaderFlag::NEED_REPLY); - } + if acked_protocol_features.contains(VhostUserProtocolFeatures::REPLY_ACK) { + self.vu.set_hdr_flags(VhostUserHeaderFlag::NEED_REPLY); } } diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index 58df496e20..842e02677d 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -437,10 +437,10 @@ impl VsockMuxer { if let Some(EpollListener::LocalStream(stream)) = self.listener_map.get_mut(&fd) { let port = Self::read_local_stream_port(&mut self.partial_command_map, stream); - if let Err(Error::UnixRead(ref e)) = port { - if e.kind() == ErrorKind::WouldBlock { - return; - } + if let Err(Error::UnixRead(ref e)) = port + && e.kind() == ErrorKind::WouldBlock + { + return; } let stream = match self.remove_listener(fd) { diff --git a/virtio-devices/src/vsock/unix/muxer_killq.rs b/virtio-devices/src/vsock/unix/muxer_killq.rs index 5295ab69e1..b9cf47f4d1 100644 --- a/virtio-devices/src/vsock/unix/muxer_killq.rs +++ b/virtio-devices/src/vsock/unix/muxer_killq.rs @@ -111,11 +111,12 @@ impl MuxerKillQ { /// the queue has expired. Otherwise, `None` is returned. /// pub fn pop(&mut self) -> Option { - if let Some(item) = self.q.front() { - if Instant::now() > item.kill_time { - return Some(self.q.pop_front().unwrap().key); - } + if let Some(item) = self.q.front() + && Instant::now() > item.kill_time + { + return Some(self.q.pop_front().unwrap().key); } + None } diff --git a/vm-allocator/src/address.rs b/vm-allocator/src/address.rs index 9a72afdf98..c847dd473a 100644 --- a/vm-allocator/src/address.rs +++ b/vm-allocator/src/address.rs @@ -196,10 +196,10 @@ impl AddressAllocator { /// Free an already allocated address range. /// We can only free a range if it matches exactly an already allocated range. pub fn free(&mut self, address: GuestAddress, size: GuestUsize) { - if let Some(&range_size) = self.ranges.get(&address) { - if size == range_size { - self.ranges.remove(&address); - } + if let Some(&range_size) = self.ranges.get(&address) + && size == range_size + { + self.ranges.remove(&address); } } diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 1614dc0b51..366b369e76 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1248,10 +1248,11 @@ impl DiskConfig { return Err(ValidationError::InvalidPciSegment(self.pci_segment)); } - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { - if iommu_segments.contains(&self.pci_segment) && !self.iommu { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + && !self.iommu + { + return Err(ValidationError::OnIommuSegment(self.pci_segment)); } } @@ -1260,13 +1261,13 @@ impl DiskConfig { } // Check Block device serial length - if let Some(ref serial) = self.serial { - if serial.len() > VIRTIO_BLK_ID_BYTES as usize { - return Err(ValidationError::InvalidSerialLength( - serial.len(), - VIRTIO_BLK_ID_BYTES as usize, - )); - } + if let Some(ref serial) = self.serial + && serial.len() > VIRTIO_BLK_ID_BYTES as usize + { + return Err(ValidationError::InvalidSerialLength( + serial.len(), + VIRTIO_BLK_ID_BYTES as usize, + )); } Ok(()) @@ -1496,17 +1497,18 @@ impl NetConfig { return Err(ValidationError::InvalidPciSegment(self.pci_segment)); } - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { - if iommu_segments.contains(&self.pci_segment) && !self.iommu { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + && !self.iommu + { + return Err(ValidationError::OnIommuSegment(self.pci_segment)); } } - if let Some(mtu) = self.mtu { - if mtu < virtio_devices::net::MIN_MTU { - return Err(ValidationError::InvalidMtu(mtu)); - } + if let Some(mtu) = self.mtu + && mtu < virtio_devices::net::MIN_MTU + { + return Err(ValidationError::InvalidMtu(mtu)); } if !self.offload_csum && (self.offload_tso || self.offload_ufo) { @@ -1633,12 +1635,12 @@ impl FsConfig { return Err(ValidationError::InvalidPciSegment(self.pci_segment)); } - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { - if iommu_segments.contains(&self.pci_segment) { - return Err(ValidationError::IommuNotSupportedOnSegment( - self.pci_segment, - )); - } + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + { + return Err(ValidationError::IommuNotSupportedOnSegment( + self.pci_segment, + )); } } @@ -1795,10 +1797,11 @@ impl PmemConfig { return Err(ValidationError::InvalidPciSegment(self.pci_segment)); } - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { - if iommu_segments.contains(&self.pci_segment) && !self.iommu { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + && !self.iommu + { + return Err(ValidationError::OnIommuSegment(self.pci_segment)); } } @@ -1895,17 +1898,18 @@ impl DebugConsoleConfig { return Err(Error::ParseConsoleInvalidModeGiven); } - if parser.is_set("iobase") { - if let Some(iobase_opt) = parser.get("iobase") { - if !iobase_opt.starts_with("0x") { - return Err(Error::Validation(ValidationError::InvalidIoPortHex( - iobase_opt, - ))); - } - iobase = Some(u16::from_str_radix(&iobase_opt[2..], 16).map_err(|_| { + if parser.is_set("iobase") + && let Some(iobase_opt) = parser.get("iobase") + { + if !iobase_opt.starts_with("0x") { + return Err(Error::Validation(ValidationError::InvalidIoPortHex( + iobase_opt, + ))); + } + iobase = + Some(u16::from_str_radix(&iobase_opt[2..], 16).map_err(|_| { Error::Validation(ValidationError::InvalidIoPortHex(iobase_opt)) })?); - } } Ok(Self { file, mode, iobase }) @@ -1957,10 +1961,11 @@ impl DeviceConfig { return Err(ValidationError::InvalidPciSegment(self.pci_segment)); } - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { - if iommu_segments.contains(&self.pci_segment) && !self.iommu { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + && !self.iommu + { + return Err(ValidationError::OnIommuSegment(self.pci_segment)); } } @@ -2000,12 +2005,12 @@ impl UserDeviceConfig { return Err(ValidationError::InvalidPciSegment(self.pci_segment)); } - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { - if iommu_segments.contains(&self.pci_segment) { - return Err(ValidationError::IommuNotSupportedOnSegment( - self.pci_segment, - )); - } + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + { + return Err(ValidationError::IommuNotSupportedOnSegment( + self.pci_segment, + )); } } @@ -2062,10 +2067,11 @@ impl VdpaConfig { return Err(ValidationError::InvalidPciSegment(self.pci_segment)); } - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { - if iommu_segments.contains(&self.pci_segment) && !self.iommu { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + && !self.iommu + { + return Err(ValidationError::OnIommuSegment(self.pci_segment)); } } @@ -2121,10 +2127,11 @@ impl VsockConfig { return Err(ValidationError::InvalidPciSegment(self.pci_segment)); } - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { - if iommu_segments.contains(&self.pci_segment) && !self.iommu { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + && !self.iommu + { + return Err(ValidationError::OnIommuSegment(self.pci_segment)); } } @@ -2493,10 +2500,10 @@ impl VmConfig { { let host_data_opt = &self.payload.as_ref().unwrap().host_data; - if let Some(host_data) = host_data_opt { - if host_data.len() != 64 { - return Err(ValidationError::InvalidHostData); - } + if let Some(host_data) = host_data_opt + && host_data.len() != 64 + { + return Err(ValidationError::InvalidHostData); } } // The 'conflict' check is introduced in commit 24438e0390d3 @@ -2675,10 +2682,10 @@ impl VmConfig { } } - if let Some(vsock) = &self.vsock { - if [!0, 0, 1, 2].contains(&vsock.cid) { - return Err(ValidationError::VsockSpecialCid(vsock.cid)); - } + if let Some(vsock) = &self.vsock + && [!0, 0, 1, 2].contains(&vsock.cid) + { + return Err(ValidationError::VsockSpecialCid(vsock.cid)); } if let Some(balloon) = &self.balloon { @@ -3080,11 +3087,11 @@ impl VmConfig { } // Remove if vsock device - if let Some(vsock) = self.vsock.as_ref() { - if vsock.id.as_ref().map(|id| id.as_ref()) == Some(id) { - self.vsock = None; - removed = true; - } + if let Some(vsock) = self.vsock.as_ref() + && vsock.id.as_ref().map(|id| id.as_ref()) == Some(id) + { + self.vsock = None; + removed = true; } removed diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 5ae2f6da26..f36467735b 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -601,10 +601,10 @@ impl BusDevice for CpuManager { state.removing = false; } // Trigger removal of vCPU - if data[0] & (1 << CPU_EJECT_FLAG) == 1 << CPU_EJECT_FLAG { - if let Err(e) = self.remove_vcpu(self.selected_cpu as u32) { - error!("Error removing vCPU: {:?}", e); - } + if data[0] & (1 << CPU_EJECT_FLAG) == 1 << CPU_EJECT_FLAG + && let Err(e) = self.remove_vcpu(self.selected_cpu as u32) + { + error!("Error removing vCPU: {:?}", e); } } else { warn!("Out of range vCPU id: {}", self.selected_cpu); @@ -1059,14 +1059,13 @@ impl CpuManager { } // Apply seccomp filter for vcpu thread. - if !vcpu_seccomp_filter.is_empty() { - if let Err(e) = + if !vcpu_seccomp_filter.is_empty() && let Err(e) = apply_filter(&vcpu_seccomp_filter).map_err(Error::ApplySeccompFilter) { error!("Error applying seccomp filter: {:?}", e); return; } - } + extern "C" fn handle_signal(_: i32, _: *mut siginfo_t, _: *mut c_void) {} // This uses an async signal safe handler to kill the vcpu handles. register_signal_handler(SIGRTMIN(), handle_signal) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 3bc814fd77..40a3d27cee 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -772,12 +772,13 @@ impl DeviceRelocation for AddressManager { if let Some(node) = self.device_tree.lock().unwrap().get_mut(&id) { let mut resource_updated = false; for resource in node.resources.iter_mut() { - if let Resource::PciBar { base, type_, .. } = resource { - if PciBarRegionType::from(*type_) == region_type && *base == old_base { - *base = new_base; - resource_updated = true; - break; - } + if let Resource::PciBar { base, type_, .. } = resource + && PciBarRegionType::from(*type_) == region_type + && *base == old_base + { + *base = new_base; + resource_updated = true; + break; } } @@ -814,43 +815,41 @@ impl DeviceRelocation for AddressManager { } else { let virtio_dev = virtio_pci_dev.virtio_device(); let mut virtio_dev = virtio_dev.lock().unwrap(); - if let Some(mut shm_regions) = virtio_dev.get_shm_regions() { - if shm_regions.addr.raw_value() == old_base { - let mem_region = self.vm.make_user_memory_region( - shm_regions.mem_slot, - old_base, - shm_regions.len, - shm_regions.host_addr, - false, - false, - ); - - self.vm.remove_user_memory_region(mem_region).map_err(|e| { - io::Error::other(format!("failed to remove user memory region: {e:?}")) - })?; + if let Some(mut shm_regions) = virtio_dev.get_shm_regions() + && shm_regions.addr.raw_value() == old_base + { + let mem_region = self.vm.make_user_memory_region( + shm_regions.mem_slot, + old_base, + shm_regions.len, + shm_regions.host_addr, + false, + false, + ); - // Create new mapping by inserting new region to KVM. - let mem_region = self.vm.make_user_memory_region( - shm_regions.mem_slot, - new_base, - shm_regions.len, - shm_regions.host_addr, - false, - false, - ); - - self.vm.create_user_memory_region(mem_region).map_err(|e| { - io::Error::other(format!("failed to create user memory regions: {e:?}")) - })?; + self.vm.remove_user_memory_region(mem_region).map_err(|e| { + io::Error::other(format!("failed to remove user memory region: {e:?}")) + })?; - // Update shared memory regions to reflect the new mapping. - shm_regions.addr = GuestAddress(new_base); - virtio_dev.set_shm_regions(shm_regions).map_err(|e| { - io::Error::other(format!( - "failed to update shared memory regions: {e:?}" - )) - })?; - } + // Create new mapping by inserting new region to KVM. + let mem_region = self.vm.make_user_memory_region( + shm_regions.mem_slot, + new_base, + shm_regions.len, + shm_regions.host_addr, + false, + false, + ); + + self.vm.create_user_memory_region(mem_region).map_err(|e| { + io::Error::other(format!("failed to create user memory regions: {e:?}")) + })?; + + // Update shared memory regions to reflect the new mapping. + shm_regions.addr = GuestAddress(new_base); + virtio_dev.set_shm_regions(shm_regions).map_err(|e| { + io::Error::other(format!("failed to update shared memory regions: {e:?}")) + })?; } } } @@ -1655,14 +1654,14 @@ impl DeviceManager { iommu_attached_devices.append(&mut vfio_user_iommu_device_ids); // Add all devices from forced iommu segments - if let Some(platform_config) = self.config.lock().unwrap().platform.as_ref() { - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { - for segment in iommu_segments { - for device in 0..32 { - let bdf = PciBdf::new(*segment, 0, device, 0); - if !iommu_attached_devices.contains(&bdf) { - iommu_attached_devices.push(bdf); - } + if let Some(platform_config) = self.config.lock().unwrap().platform.as_ref() + && let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + { + for segment in iommu_segments { + for device in 0..32 { + let bdf = PciBdf::new(*segment, 0, device, 0); + if !iommu_attached_devices.contains(&bdf) { + iommu_attached_devices.push(bdf); } } } @@ -4350,14 +4349,14 @@ impl DeviceManager { .add_memory_region(new_region) .map_err(DeviceManagerError::UpdateMemoryForVirtioDevice)?; - if let Some(dma_handler) = &handle.dma_handler { - if !handle.iommu { - let gpa = new_region.start_addr().0; - let size = new_region.len(); - dma_handler - .map(gpa, gpa, size) - .map_err(DeviceManagerError::VirtioDmaMap)?; - } + if let Some(dma_handler) = &handle.dma_handler + && !handle.iommu + { + let gpa = new_region.start_addr().0; + let size = new_region.len(); + dma_handler + .map(gpa, gpa, size) + .map_err(DeviceManagerError::VirtioDmaMap)?; } } @@ -4576,10 +4575,10 @@ impl DeviceManager { }; let mut iommu_attached = false; - if let Some((_, iommu_attached_devices)) = &self.iommu_attached_devices { - if iommu_attached_devices.contains(&pci_device_bdf) { - iommu_attached = true; - } + if let Some((_, iommu_attached_devices)) = &self.iommu_attached_devices + && iommu_attached_devices.contains(&pci_device_bdf) + { + iommu_attached = true; } let (pci_device, bus_device, virtio_device, remove_dma_handler) = match pci_device_handle { @@ -4610,16 +4609,16 @@ impl DeviceManager { .map_err(|e| DeviceManagerError::UnRegisterIoevent(e.into()))?; } - if let Some(dma_handler) = dev.dma_handler() { - if !iommu_attached { - for (_, zone) in self.memory_manager.lock().unwrap().memory_zones().iter() { - for region in zone.regions() { - let iova = region.start_addr().0; - let size = region.len(); - dma_handler - .unmap(iova, size) - .map_err(DeviceManagerError::VirtioDmaUnmap)?; - } + if let Some(dma_handler) = dev.dma_handler() + && !iommu_attached + { + for (_, zone) in self.memory_manager.lock().unwrap().memory_zones().iter() { + for region in zone.regions() { + let iova = region.start_addr().0; + let size = region.len(); + dma_handler + .unmap(iova, size) + .map_err(DeviceManagerError::VirtioDmaUnmap)?; } } } diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 97297a0920..03dd4d2472 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -428,11 +428,11 @@ pub fn load_igvm( let gpas_grouped = gpas .iter() .fold(Vec::>::new(), |mut acc, gpa| { - if let Some(last_vec) = acc.last_mut() { - if last_vec[0].page_type == gpa.page_type { - last_vec.push(*gpa); - return acc; - } + if let Some(last_vec) = acc.last_mut() + && last_vec[0].page_type == gpa.page_type + { + last_vec.push(*gpa); + return acc; } acc.push(vec![*gpa]); acc diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index ef23793088..76b63d10be 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -729,15 +729,14 @@ impl Vmm { thread::Builder::new() .name("vmm_signal_handler".to_string()) .spawn(move || { - if !signal_handler_seccomp_filter.is_empty() { - if let Err(e) = apply_filter(&signal_handler_seccomp_filter) + if !signal_handler_seccomp_filter.is_empty() && let Err(e) = apply_filter(&signal_handler_seccomp_filter) .map_err(Error::ApplySeccompFilter) { error!("Error applying seccomp filter: {:?}", e); exit_evt.write(1).ok(); return; } - } + if landlock_enable{ match Landlock::new() { Ok(landlock) => { @@ -1834,10 +1833,10 @@ impl RequestHandler for Vmm { if let Some(desired_ram) = desired_ram { config.memory.size = desired_ram; } - if let Some(desired_balloon) = desired_balloon { - if let Some(balloon_config) = &mut config.balloon { - balloon_config.size = desired_balloon; - } + if let Some(desired_balloon) = desired_balloon + && let Some(balloon_config) = &mut config.balloon + { + balloon_config.size = desired_balloon; } Ok(()) } @@ -2306,16 +2305,16 @@ impl RequestHandler for Vmm { error!("Migration failed: {:?}", migration_err); // Stop logging dirty pages only for non-local migrations - if !send_data_migration.local { - if let Err(e) = vm.stop_dirty_log() { - return e; - } + if !send_data_migration.local + && let Err(e) = vm.stop_dirty_log() + { + return e; } - if vm.get_state().unwrap() == VmState::Paused { - if let Err(e) = vm.resume() { - return e; - } + if vm.get_state().unwrap() == VmState::Paused + && let Err(e) = vm.resume() + { + return e; } migration_err diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 15225e9dc7..74765cf514 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -1959,23 +1959,21 @@ impl MemoryManager { } for region in memory_zone.regions() { - if snapshot { - if let Some(file_offset) = region.file_offset() { - if (region.flags() & libc::MAP_SHARED == libc::MAP_SHARED) - && Self::is_hardlink(file_offset.file()) - { - // In this very specific case, we know the memory - // region is backed by a file on the host filesystem - // that can be accessed by the user, and additionally - // the mapping is shared, which means that modifications - // to the content are written to the actual file. - // When meeting these conditions, we can skip the - // copy of the memory content for this specific region, - // as we can assume the user will have it saved through - // the backing file already. - continue; - } - } + if snapshot + && let Some(file_offset) = region.file_offset() + && (region.flags() & libc::MAP_SHARED == libc::MAP_SHARED) + && Self::is_hardlink(file_offset.file()) + { + // In this very specific case, we know the memory + // region is backed by a file on the host filesystem + // that can be accessed by the user, and additionally + // the mapping is shared, which means that modifications + // to the content are written to the actual file. + // When meeting these conditions, we can skip the + // copy of the memory content for this specific region, + // as we can assume the user will have it saved through + // the backing file already. + continue; } table.push(MemoryRange { diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index f05100b464..9c4255defa 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -432,12 +432,12 @@ impl Drop for SerialManager { if let Some(handle) = self.handle.take() { handle.join().ok(); } - if let ConsoleOutput::Socket(_) = self.in_file { - if let Some(socket_path) = self.socket_path.as_ref() { - std::fs::remove_file(socket_path.as_os_str()) - .map_err(Error::RemoveUnixSocket) - .ok(); - } + if let ConsoleOutput::Socket(_) = self.in_file + && let Some(socket_path) = self.socket_path.as_ref() + { + std::fs::remove_file(socket_path.as_os_str()) + .map_err(Error::RemoveUnixSocket) + .ok(); } } } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 0e85b1ddc1..af843f169e 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -3123,12 +3123,12 @@ impl GuestDebuggable for Vm { #[cfg(feature = "tdx")] { - if let Some(ref platform) = self.config.lock().unwrap().platform { - if platform.tdx { - return Err(GuestDebuggableError::Coredump(anyhow!( - "Coredump not possible with TDX VM" - ))); - } + if let Some(ref platform) = self.config.lock().unwrap().platform + && platform.tdx + { + return Err(GuestDebuggableError::Coredump(anyhow!( + "Coredump not possible with TDX VM" + ))); } } From 1179a1a1c92ee1aaddf4b339d873fea3000a37bd Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 26 Aug 2025 08:48:37 +0200 Subject: [PATCH 0173/1893] vmm: refactor alignment Context [0]. [0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7256#discussion_r2298538384 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/vm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index af843f169e..836feb4707 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1177,8 +1177,8 @@ impl Vm { let guest_memory = memory_manager.lock().as_ref().unwrap().guest_memory(); let mem = guest_memory.memory(); let alignment = 0x20_0000; - let aligned_kernel_addr = - (arch::layout::KERNEL_START.0 + (alignment - 1)) & !(alignment - 1); + // round up + let aligned_kernel_addr = arch::layout::KERNEL_START.0.div_ceil(alignment) * alignment; let entry_addr = { match linux_loader::loader::pe::PE::load( mem.deref(), From 4dd635a56a19ff8b5f44a4c0452fb1d84a273228 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 23:01:57 +0000 Subject: [PATCH 0174/1893] build: Bump bitflags from 2.9.3 to 2.9.4 Bumps [bitflags](https://github.com/bitflags/bitflags) from 2.9.3 to 2.9.4. - [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.9.3...2.9.4) --- updated-dependencies: - dependency-name: bitflags dependency-version: 2.9.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 30 +++++++++++++++--------------- Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ee00fa852..1152eea5f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,9 +298,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.3" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "block" @@ -526,7 +526,7 @@ dependencies = [ "anyhow", "arch", "bitfield-struct", - "bitflags 2.9.3", + "bitflags 2.9.4", "byteorder", "event_monitor", "hypervisor", @@ -639,7 +639,7 @@ version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "libc", ] @@ -828,7 +828,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d66e32caf5dd59f561be0143e413e01d651bd8498eb9aa0be8c482c81c8d31" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "log", "managed", @@ -1000,7 +1000,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "libc", ] @@ -1077,7 +1077,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b702df98508cb63ad89dd9beb9f6409761b30edca10d48e57941d3f11513a006" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "kvm-bindings", "libc", "vmm-sys-util", @@ -1112,7 +1112,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "libc", ] @@ -1293,7 +1293,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", @@ -1834,7 +1834,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1847,7 +1847,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.9.4", @@ -2260,7 +2260,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "libc", "log", "serde", @@ -2278,7 +2278,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "libc", "uuid", "vm-memory", @@ -2451,7 +2451,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.9.3", + "bitflags 2.9.4", "block", "blocking", "cfg-if", @@ -2845,7 +2845,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9f78b3f8c6..3806e8ef19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,7 +137,7 @@ serde_with = { version = "3.14.0", default-features = false } # other crates anyhow = "1.0.98" -bitflags = "2.9.3" +bitflags = "2.9.4" byteorder = "1.5.0" cfg-if = "1.0.0" clap = "4.5.47" From ad3f94638fd20ca480fe43b871bfa27f3f1e65d9 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 4 Sep 2025 21:41:06 +0000 Subject: [PATCH 0175/1893] docs: Clarify our policy on AI generated code Fix: #7162 Signed-off-by: Bo Chen --- CONTRIBUTING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cdd75e31a1..c77d3e36da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,3 +114,13 @@ Signed-off-by: Sebastien Boeuf 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 + +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. + +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. From 43a6a914b89b39d8657fd2c14dda56213892e00f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:01:59 +0000 Subject: [PATCH 0176/1893] build: Bump ssh2 from 0.9.4 to 0.9.5 Bumps [ssh2](https://github.com/alexcrichton/ssh2-rs) from 0.9.4 to 0.9.5. - [Commits](https://github.com/alexcrichton/ssh2-rs/compare/0.9.4...0.9.5) --- updated-dependencies: - dependency-name: ssh2 dependency-version: 0.9.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 57 ++++++------------------------------------- test_infra/Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1152eea5f9..37ff098e1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -555,7 +555,7 @@ dependencies = [ "backtrace", "lazy_static", "mintex", - "parking_lot 0.12.1", + "parking_lot", "rustc-hash", "serde", "serde_json", @@ -985,15 +985,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-uring" version = "0.7.10" @@ -1422,17 +1413,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -1440,21 +1420,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.9", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -1465,7 +1431,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall", "smallvec", "windows-targets 0.48.5", ] @@ -1747,15 +1713,6 @@ dependencies = [ "vmm-sys-util", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -1999,14 +1956,14 @@ dependencies = [ [[package]] name = "ssh2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fe461910559f6d5604c3731d00d2aafc4a83d1665922e280f42f9a168d5455" +checksum = "2f84d13b3b8a0d4e91a2629911e951db1bb8671512f5c09d7d4ba34500ba68c8" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.4", "libc", "libssh2-sys", - "parking_lot 0.11.2", + "parking_lot", ] [[package]] diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index fe3cba8eea..8fdefed15a 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -9,7 +9,7 @@ dirs = { workspace = true } epoll = { workspace = true } libc = { workspace = true } serde_json = { workspace = true } -ssh2 = { version = "0.9.4", features = ["vendored-openssl"] } +ssh2 = { version = "0.9.5", features = ["vendored-openssl"] } thiserror = { workspace = true } vmm-sys-util = { workspace = true } wait-timeout = { workspace = true } From 5f399fc06d5e3200f3edb84a1030d9fd7323dc36 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 11 Sep 2025 22:46:49 +0000 Subject: [PATCH 0177/1893] vmm: fix hotplugging when max_cpus > 255 Signed-off-by: Peter Oskolkov --- vmm/src/cpu.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index f36467735b..43edf5015b 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -524,7 +524,7 @@ pub struct CpuManager { #[cfg(feature = "guest_debug")] vm_debug_evt: EventFd, vcpu_states: Vec, - selected_cpu: u8, + selected_cpu: u32, vcpus: Vec>>, seccomp_action: SeccompAction, vm_ops: Arc, @@ -553,11 +553,13 @@ impl BusDevice for CpuManager { match offset { CPU_SELECTION_OFFSET => { - data[0] = self.selected_cpu; + assert!(data.len() >= core::mem::size_of::()); + data[0..core::mem::size_of::()] + .copy_from_slice(&self.selected_cpu.to_le_bytes()); } CPU_STATUS_OFFSET => { - if (self.selected_cpu as u32) < self.max_vcpus() { - let state = &self.vcpu_states[usize::from(self.selected_cpu)]; + if self.selected_cpu < self.max_vcpus() { + let state = &self.vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; if state.active() { data[0] |= 1 << CPU_ENABLE_FLAG; } @@ -583,11 +585,13 @@ impl BusDevice for CpuManager { fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { match offset { CPU_SELECTION_OFFSET => { - self.selected_cpu = data[0]; + assert!(data.len() >= core::mem::size_of::()); + self.selected_cpu = + u32::from_le_bytes(data[0..core::mem::size_of::()].try_into().unwrap()); } CPU_STATUS_OFFSET => { - if (self.selected_cpu as u32) < self.max_vcpus() { - let state = &mut self.vcpu_states[usize::from(self.selected_cpu)]; + if self.selected_cpu < self.max_vcpus() { + let state = &mut self.vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; // The ACPI code writes back a 1 to acknowledge the insertion if (data[0] & (1 << CPU_INSERTING_FLAG) == 1 << CPU_INSERTING_FLAG) && state.inserting @@ -602,7 +606,7 @@ impl BusDevice for CpuManager { } // Trigger removal of vCPU if data[0] & (1 << CPU_EJECT_FLAG) == 1 << CPU_EJECT_FLAG - && let Err(e) = self.remove_vcpu(self.selected_cpu as u32) + && let Err(e) = self.remove_vcpu(self.selected_cpu) { error!("Error removing vCPU: {:?}", e); } From 95e3c8f8fd71170807cb3a653cdfd61b0e25914c Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 11 Sep 2025 21:18:10 +0000 Subject: [PATCH 0178/1893] build: Release v48.0 Signed-off-by: Bo Chen --- Cargo.lock | 2 +- Cargo.toml | 2 +- release-notes.md | 249 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 171 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 37ff098e1b..d2f13bfd58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,7 +399,7 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cloud-hypervisor" -version = "47.0.0" +version = "48.0.0" dependencies = [ "anyhow", "api_client", diff --git a/Cargo.toml b/Cargo.toml index 3806e8ef19..f154330808 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" license = "Apache-2.0 AND BSD-3-Clause" name = "cloud-hypervisor" -version = "47.0.0" +version = "48.0.0" # Minimum buildable version: # Keep in sync with version in .github/workflows/build.yaml # Policy on MSRV (see #4318): diff --git a/release-notes.md b/release-notes.md index 0eb024c966..4dc982e838 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,57 +1,69 @@ +- [v48.0](#v480) + - [Experimental `fw_cfg` Device Support](#experimental-fw_cfg-device-support) + - [Experimental `ivshmem` Device Support](#experimental-ivshmem-device-support) + - [Firmware Boot Support on `riscv64`](#firmware-boot-support-on-riscv64) + - [Increased vCPU Limit on x86_64/kvm](#increased-vcpu-limit-on-x86_64kvm) + - [Improved Block Performance with Small Block Sizes](#improved-block-performance-with-small-block-sizes) + - [Faster VM Pause Operation](#faster-vm-pause-operation) + - [Updated Documentation on Windows Guest Support](#updated-documentation-on-windows-guest-support) + - [Policy on AI Generated Code](#policy-on-ai-generated-code) + - [Removed SGX Support](#removed-sgx-support) + - [Notable Bug Fixes](#notable-bug-fixes) + - [Contributors](#contributors) - [v47.0](#v470) - [Block Device Error Reporting to the Guest](#block-device-error-reporting-to-the-guest) - [Nice Error Messages on Exit](#nice-error-messages-on-exit) - [Alphabetically Sorted CLI Options for ch-remote](#alphabetically-sorted-cli-options-for-ch-remote) - - [Notable Bug Fixes](#notable-bug-fixes) + - [Notable Bug Fixes](#notable-bug-fixes-1) - [Deprecations](#deprecations) - - [Contributors](#contributors) + - [Contributors](#contributors-1) - [v46.0](#v460) - [File-level Locking Support with `--disk`](#file-level-locking-support-with---disk) - [Improved Error Reporting with VM Resizing](#improved-error-reporting-with-vm-resizing) - [IPv6 Address Support with `--net`](#ipv6-address-support-with---net) - [Experimental AArch64 Support with the MSHV Hypervisor](#experimental-aarch64-support-with-the-mshv-hypervisor) - [Deprecated SGX Support](#deprecated-sgx-support) - - [Notable Bug Fixes](#notable-bug-fixes-1) - - [Contributors](#contributors-1) + - [Notable Bug Fixes](#notable-bug-fixes-2) + - [Contributors](#contributors-2) - [v45.0](#v450) - [Experimental `riscv64` Architecture Support](#experimental-riscv64-architecture-support) - [Alphabetically Sorted CLI Options](#alphabetically-sorted-cli-options) - [Improved Downtime of VM Live Migration](#improved-downtime-of-vm-live-migration) - - [Notable Bug Fixes](#notable-bug-fixes-2) - - [Contributors](#contributors-2) + - [Notable Bug Fixes](#notable-bug-fixes-3) + - [Contributors](#contributors-3) - [v44.0](#v440) - [Configurable `virtio-iommu` Address Width](#configurable-virtio-iommu-address-width) - [Notable Performance Improvements](#notable-performance-improvements) - [New Fuzzers](#new-fuzzers) - - [Notable Bug Fixes](#notable-bug-fixes-3) - - [Contributors](#contributors-3) + - [Notable Bug Fixes](#notable-bug-fixes-4) + - [Contributors](#contributors-4) - [v43.0](#v430) - [Live Migration over TCP Connections](#live-migration-over-tcp-connections) - [Notable Performance Improvements](#notable-performance-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-4) - - [Contributors](#contributors-4) + - [Notable Bug Fixes](#notable-bug-fixes-5) + - [Contributors](#contributors-5) - [v42.0](#v420) - [SVE/SVE2 Support on AArch64](#svesve2-support-on-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-5) + - [Notable Bug Fixes](#notable-bug-fixes-6) - [Sponsorships](#sponsorships) - - [Contributors](#contributors-5) + - [Contributors](#contributors-6) - [v41.0](#v410) - [Experimental "Pvmemcontrol" Support](#experimental-pvmemcontrol-support) - [Sandboxing With Landlock Support](#sandboxing-with-landlock-support) - [Notable Performance Improvements](#notable-performance-improvements-2) - - [Notable Bug Fixes](#notable-bug-fixes-6) - - [Contributors](#contributors-6) -- [v40.0](#v400) - - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) - [Notable Bug Fixes](#notable-bug-fixes-7) - [Contributors](#contributors-7) +- [v40.0](#v400) + - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) + - [Notable Bug Fixes](#notable-bug-fixes-8) + - [Contributors](#contributors-8) - [v39.0](#v390) - [Variable Sizing of PCI Apertures for Segments](#variable-sizing-of-pci-apertures-for-segments) - [Direct Booting with bzImages](#direct-booting-with-bzimages) - [Support for NVIDIA GPUDirect P2P Support](#support-for-nvidia-gpudirect-p2p-support) - [Guest NMI Injection Support](#guest-nmi-injection-support) - - [Notable Bug Fixes](#notable-bug-fixes-8) - - [Contributors](#contributors-8) + - [Notable Bug Fixes](#notable-bug-fixes-9) + - [Contributors](#contributors-9) - [v38.0](#v380) - [Group Rate Limiter on Block Devices](#group-rate-limiter-on-block-devices) - [CPU Pinning Support for Block Device Worker Thread](#cpu-pinning-support-for-block-device-worker-thread) @@ -59,16 +71,16 @@ - [New 'debug-console' Device](#new-debug-console-device) - [Improved VFIO Device Support](#improved-vfio-device-support) - [Extended CPU Affinity Support](#extended-cpu-affinity-support) - - [Notable Bug Fixes](#notable-bug-fixes-9) - - [Contributors](#contributors-9) + - [Notable Bug Fixes](#notable-bug-fixes-10) + - [Contributors](#contributors-10) - [v37.0](#v370) - [Long Term Support (LTS) Release](#long-term-support-lts-release) - [Multiple PCI segments Support for 32-bit VFIO devices](#multiple-pci-segments-support-for-32-bit-vfio-devices) - [Configurable Named TAP Devices](#configurable-named-tap-devices) - [TTY Output from Both Serial Device and Virtio Console](#tty-output-from-both-serial-device-and-virtio-console) - [Faster VM Restoration from Snapshots](#faster-vm-restoration-from-snapshots) - - [Notable Bug Fixes](#notable-bug-fixes-10) - - [Contributors](#contributors-10) + - [Notable Bug Fixes](#notable-bug-fixes-11) + - [Contributors](#contributors-11) - [v36.0](#v360) - [Command Line Changes](#command-line-changes) - [Enabled Features Reported via API Endpoint and CLI](#enabled-features-reported-via-api-endpoint-and-cli) @@ -77,31 +89,31 @@ - [Unix Socket Backend for Serial Port](#unix-socket-backend-for-serial-port) - [AIO Backend for Block Devices](#aio-backend-for-block-devices) - [Documentation Improvements](#documentation-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-11) - - [Contributors](#contributors-11) + - [Notable Bug Fixes](#notable-bug-fixes-12) + - [Contributors](#contributors-12) - [v35.0](#v350) - [`virtio-vsock` Support for Linux Guest Kernel v6.3+](#virtio-vsock-support-for-linux-guest-kernel-v63) - [User Specified Serial Number for `virtio-block`](#user-specified-serial-number-for-virtio-block) - [vCPU TSC Frequency Included in Migration State](#vcpu-tsc-frequency-included-in-migration-state) - - [Notable Bug Fixes](#notable-bug-fixes-12) - - [Contributors](#contributors-12) + - [Notable Bug Fixes](#notable-bug-fixes-13) + - [Contributors](#contributors-13) - [v34.0](#v340) - [Paravirtualised Panic Device Support](#paravirtualised-panic-device-support) - [Improvements to VM Core Dump](#improvements-to-vm-core-dump) - [QCOW2 Support for Backing Files](#qcow2-support-for-backing-files) - [Minimum Host Kernel Bump](#minimum-host-kernel-bump) - - [Notable Bug Fixes](#notable-bug-fixes-13) - - [Contributors](#contributors-13) + - [Notable Bug Fixes](#notable-bug-fixes-14) + - [Contributors](#contributors-14) - [v33.0](#v330) - [D-Bus based API](#d-bus-based-api) - [Expose Host CPU Cache Details for AArch64](#expose-host-cpu-cache-details-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-14) - - [Contributors](#contributors-14) + - [Notable Bug Fixes](#notable-bug-fixes-15) + - [Contributors](#contributors-15) - [v32.0](#v320) - [Increased PCI Segment Limit](#increased-pci-segment-limit) - [API Changes](#api-changes) - - [Notable Bug Fixes](#notable-bug-fixes-15) - - [Contributors](#contributors-15) + - [Notable Bug Fixes](#notable-bug-fixes-16) + - [Contributors](#contributors-16) - [v31.1](#v311) - [v31.0](#v310) - [Update to Latest `acpi_tables`](#update-to-latest-acpi_tables) @@ -109,15 +121,15 @@ - [Improvements on Console `SIGWINCH` Handler](#improvements-on-console-sigwinch-handler) - [Remove Directory Support from `MemoryZoneConfig::file`](#remove-directory-support-from-memoryzoneconfigfile) - [Documentation Improvements](#documentation-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-16) - - [Contributors](#contributors-16) + - [Notable Bug Fixes](#notable-bug-fixes-17) + - [Contributors](#contributors-17) - [v30.0](#v300) - [Command Line Changes for Reduced Binary Size](#command-line-changes-for-reduced-binary-size) - [Basic vfio-user Server Support](#basic-vfio-user-server-support) - [Heap Profiling Support](#heap-profiling-support) - [Documentation Improvements](#documentation-improvements-2) - - [Notable Bug Fixes](#notable-bug-fixes-17) - - [Contributors](#contributors-17) + - [Notable Bug Fixes](#notable-bug-fixes-18) + - [Contributors](#contributors-18) - [v28.2](#v282) - [v29.0](#v290) - [Release Binary Supports Both MSHV and KVM](#release-binary-supports-both-mshv-and-kvm) @@ -127,10 +139,10 @@ - [`AArch64` Documentation Integration](#aarch64-documentation-integration) - [`virtio-block` Counters Enhancement](#virtio-block-counters-enhancement) - [TCP Offload Control](#tcp-offload-control) - - [Notable Bug Fixes](#notable-bug-fixes-18) + - [Notable Bug Fixes](#notable-bug-fixes-19) - [Removals](#removals) - [Deprecations](#deprecations-1) - - [Contributors](#contributors-18) + - [Contributors](#contributors-19) - [v28.1](#v281) - [v28.0](#v280) - [Community Engagement (Reminder)](#community-engagement-reminder) @@ -138,9 +150,9 @@ - [Virtualised TPM Support](#virtualised-tpm-support) - [Transparent Huge Page Support](#transparent-huge-page-support) - [README Quick Start Improved](#readme-quick-start-improved) - - [Notable Bug Fixes](#notable-bug-fixes-19) + - [Notable Bug Fixes](#notable-bug-fixes-20) - [Removals](#removals-1) - - [Contributors](#contributors-19) + - [Contributors](#contributors-20) - [v27.0](#v270) - [Community Engagement](#community-engagement) - [Prebuilt Packages](#prebuilt-packages) @@ -149,41 +161,41 @@ - [Simplified Build Feature Flags](#simplified-build-feature-flags) - [Asynchronous Kernel Loading](#asynchronous-kernel-loading) - [GDB Support for AArch64](#gdb-support-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-20) + - [Notable Bug Fixes](#notable-bug-fixes-21) - [Deprecations](#deprecations-2) - - [Contributors](#contributors-20) + - [Contributors](#contributors-21) - [v26.0](#v260) - [SMBIOS Improvements via `--platform`](#smbios-improvements-via---platform) - [Unified Binary MSHV and KVM Support](#unified-binary-mshv-and-kvm-support) - - [Notable Bug Fixes](#notable-bug-fixes-21) + - [Notable Bug Fixes](#notable-bug-fixes-22) - [Deprecations](#deprecations-3) - [Removals](#removals-2) - - [Contributors](#contributors-21) + - [Contributors](#contributors-22) - [v25.0](#v250) - [`ch-remote` Improvements](#ch-remote-improvements-1) - [VM "Coredump" Support](#vm-coredump-support) - - [Notable Bug Fixes](#notable-bug-fixes-22) + - [Notable Bug Fixes](#notable-bug-fixes-23) - [Removals](#removals-3) - - [Contributors](#contributors-22) + - [Contributors](#contributors-23) - [v24.0](#v240) - [Bypass Mode for `virtio-iommu`](#bypass-mode-for-virtio-iommu) - [Ensure Identifiers Uniqueness](#ensure-identifiers-uniqueness) - [Sparse Mmap support](#sparse-mmap-support) - [Expose Platform Serial Number](#expose-platform-serial-number) - - [Notable Bug Fixes](#notable-bug-fixes-23) + - [Notable Bug Fixes](#notable-bug-fixes-24) - [Notable Improvements](#notable-improvements) - [Deprecations](#deprecations-4) - [New on the Website](#new-on-the-website) - - [Contributors](#contributors-23) + - [Contributors](#contributors-24) - [v23.1](#v231) - [v23.0](#v230) - [vDPA Support](#vdpa-support) - [Updated OS Support list](#updated-os-support-list) - [`AArch64` Memory Map Improvements](#aarch64-memory-map-improvements) - [`AMX` Support](#amx-support) - - [Notable Bug Fixes](#notable-bug-fixes-24) + - [Notable Bug Fixes](#notable-bug-fixes-25) - [Deprecations](#deprecations-5) - - [Contributors](#contributors-24) + - [Contributors](#contributors-25) - [v22.1](#v221) - [v22.0](#v220) - [GDB Debug Stub Support](#gdb-debug-stub-support) @@ -194,13 +206,13 @@ - [PMU Support for AArch64](#pmu-support-for-aarch64) - [Documentation Under CC-BY-4.0 License](#documentation-under-cc-by-40-license) - [Deprecation of "Classic" `virtiofsd`](#deprecation-of-classic-virtiofsd) - - [Notable Bug Fixes](#notable-bug-fixes-25) - - [Contributors](#contributors-25) + - [Notable Bug Fixes](#notable-bug-fixes-26) + - [Contributors](#contributors-26) - [v21.0](#v210) - [Efficient Local Live Migration (for Live Upgrade)](#efficient-local-live-migration-for-live-upgrade) - [Recommended Kernel is Now 5.15](#recommended-kernel-is-now-515) - - [Notable Bug fixes](#notable-bug-fixes-26) - - [Contributors](#contributors-26) + - [Notable Bug fixes](#notable-bug-fixes-27) + - [Contributors](#contributors-27) - [v20.2](#v202) - [v20.1](#v201) - [v20.0](#v200) @@ -209,8 +221,8 @@ - [Improved VFIO support](#improved-vfio-support) - [Safer code](#safer-code) - [Extended documentation](#extended-documentation) - - [Notable bug fixes](#notable-bug-fixes-27) - - [Contributors](#contributors-27) + - [Notable bug fixes](#notable-bug-fixes-28) + - [Contributors](#contributors-28) - [v19.0](#v190) - [Improved PTY handling for serial and `virtio-console`](#improved-pty-handling-for-serial-and-virtio-console) - [PCI boot time optimisations](#pci-boot-time-optimisations) @@ -218,8 +230,8 @@ - [Live migration enhancements](#live-migration-enhancements) - [`virtio-mem` support with `vfio-user`](#virtio-mem-support-with-vfio-user) - [AArch64 for `virtio-iommu`](#aarch64-for-virtio-iommu) - - [Notable bug fixes](#notable-bug-fixes-28) - - [Contributors](#contributors-28) + - [Notable bug fixes](#notable-bug-fixes-29) + - [Contributors](#contributors-29) - [v18.0](#v180) - [Experimental User Device (`vfio-user`) support](#experimental-user-device-vfio-user-support) - [Migration support for `vhost-user` devices](#migration-support-for-vhost-user-devices) @@ -229,23 +241,23 @@ - [Live migration on MSHV hypervisor](#live-migration-on-mshv-hypervisor) - [AArch64 CPU topology support](#aarch64-cpu-topology-support) - [Power button support on AArch64](#power-button-support-on-aarch64) - - [Notable bug fixes](#notable-bug-fixes-29) - - [Contributors](#contributors-29) + - [Notable bug fixes](#notable-bug-fixes-30) + - [Contributors](#contributors-30) - [v17.0](#v170) - [ARM64 NUMA support using ACPI](#arm64-numa-support-using-acpi) - [`Seccomp` support for MSHV backend](#seccomp-support-for-mshv-backend) - [Hotplug of `macvtap` devices](#hotplug-of-macvtap-devices) - [Improved SGX support](#improved-sgx-support) - [Inflight tracking for `vhost-user` devices](#inflight-tracking-for-vhost-user-devices) - - [Notable bug fixes](#notable-bug-fixes-30) - - [Contributors](#contributors-30) + - [Notable bug fixes](#notable-bug-fixes-31) + - [Contributors](#contributors-31) - [v16.0](#v160) - [Improved live migration support](#improved-live-migration-support) - [Improved `vhost-user` support](#improved-vhost-user-support) - [ARM64 ACPI and UEFI support](#arm64-acpi-and-uefi-support) - - [Notable bug fixes](#notable-bug-fixes-31) + - [Notable bug fixes](#notable-bug-fixes-32) - [Removed functionality](#removed-functionality) - - [Contributors](#contributors-31) + - [Contributors](#contributors-32) - [v15.0](#v150) - [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees) - [Network device rate limiting](#network-device-rate-limiting) @@ -253,7 +265,7 @@ - [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter) - [Bug fixes](#bug-fixes) - [Deprecations](#deprecations-6) - - [Contributors](#contributors-32) + - [Contributors](#contributors-33) - [v0.14.1](#v0141) - [v0.14.0](#v0140) - [Structured event monitoring](#structured-event-monitoring) @@ -263,7 +275,7 @@ - [PTY control for serial and `virtio-console`](#pty-control-for-serial-and-virtio-console) - [Block device rate limiting](#block-device-rate-limiting) - [Deprecations](#deprecations-7) - - [Contributors](#contributors-33) + - [Contributors](#contributors-34) - [v0.13.0](#v0130) - [Wider VFIO device support](#wider-vfio-device-support) - [Improved huge page support](#improved-huge-page-support) @@ -271,13 +283,13 @@ - [VHD disk image support](#vhd-disk-image-support) - [Improved Virtio device threading](#improved-virtio-device-threading) - [Clean shutdown support via synthetic power button](#clean-shutdown-support-via-synthetic-power-button) - - [Contributors](#contributors-34) + - [Contributors](#contributors-35) - [v0.12.0](#v0120) - [ARM64 enhancements](#arm64-enhancements) - [Removal of `vhost-user-net` and `vhost-user-block` self spawning](#removal-of-vhost-user-net-and-vhost-user-block-self-spawning) - [Migration of `vhost-user-fs` backend](#migration-of-vhost-user-fs-backend) - [Enhanced "info" API](#enhanced-info-api) - - [Contributors](#contributors-35) + - [Contributors](#contributors-36) - [v0.11.0](#v0110) - [`io_uring` support by default for `virtio-block`](#io_uring-support-by-default-for-virtio-block) - [Windows Guest Support](#windows-guest-support) @@ -289,15 +301,15 @@ - [Default Log Level Changed](#default-log-level-changed) - [New `--balloon` Parameter Added](#new---balloon-parameter-added) - [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support) - - [Notable Bug Fixes](#notable-bug-fixes-32) - - [Contributors](#contributors-36) + - [Notable Bug Fixes](#notable-bug-fixes-33) + - [Contributors](#contributors-37) - [v0.10.0](#v0100) - [`virtio-block` Support for Multiple Descriptors](#virtio-block-support-for-multiple-descriptors) - [Memory Zones](#memory-zones) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements) - [Preliminary KVM HyperV Emulation Control](#preliminary-kvm-hyperv-emulation-control) - - [Notable Bug Fixes](#notable-bug-fixes-33) - - [Contributors](#contributors-37) + - [Notable Bug Fixes](#notable-bug-fixes-34) + - [Contributors](#contributors-38) - [v0.9.0](#v090) - [`io_uring` Based Block Device Support](#io_uring-based-block-device-support) - [Block and Network Device Statistics](#block-and-network-device-statistics) @@ -310,17 +322,17 @@ - [Enhancements to ARM64 Support](#enhancements-to-arm64-support) - [Intel SGX Support](#intel-sgx-support) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-34) - - [Contributors](#contributors-38) + - [Notable Bug Fixes](#notable-bug-fixes-35) + - [Contributors](#contributors-39) - [v0.8.0](#v080) - [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support) - [Experimental ARM64 Support](#experimental-arm64-support) - [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests) - [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices) - [`vhost_user_fs` Improvements](#vhost_user_fs-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-35) + - [Notable Bug Fixes](#notable-bug-fixes-36) - [Command Line and API Changes](#command-line-and-api-changes) - - [Contributors](#contributors-39) + - [Contributors](#contributors-40) - [v0.7.0](#v070) - [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug) - [Alternative `libc` Support](#alternative-libc-support) @@ -330,14 +342,14 @@ - [`Seccomp` Sandboxing](#seccomp-sandboxing) - [Updated Distribution Support](#updated-distribution-support) - [Command Line and API Changes](#command-line-and-api-changes-1) - - [Contributors](#contributors-40) + - [Contributors](#contributors-41) - [v0.6.0](#v060) - [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug) - [Shared Filesystem Improvements](#shared-filesystem-improvements) - [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading) - [Command Line Interface](#command-line-interface) - [PVH Boot](#pvh-boot) - - [Contributors](#contributors-41) + - [Contributors](#contributors-42) - [v0.5.1](#v051) - [v0.5.0](#v050) - [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing) @@ -345,7 +357,7 @@ - [New Interrupt Management Framework](#new-interrupt-management-framework) - [Development Tools](#development-tools) - [Kata Containers Integration](#kata-containers-integration) - - [Contributors](#contributors-42) + - [Contributors](#contributors-43) - [v0.4.0](#v040) - [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition) - [Programmatic firmware tables generation](#programmatic-firmware-tables-generation) @@ -354,7 +366,7 @@ - [Userspace IOAPIC by default](#userspace-ioapic-by-default) - [PCI BAR reprogramming](#pci-bar-reprogramming) - [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization) - - [Contributors](#contributors-43) + - [Contributors](#contributors-44) - [v0.3.0](#v030) - [Block device offloading](#block-device-offloading) - [Network device backend](#network-device-backend) @@ -381,6 +393,83 @@ - [Unit testing](#unit-testing) - [Integration tests parallelization](#integration-tests-parallelization) +# v48.0 + +This release has been tracked in [v48.0 +group](https://github.com/orgs/cloud-hypervisor/projects/6/views/4?filterQuery=release%3A%22Release+48%22) +of our [roadmap project](https://github.com/orgs/cloud-hypervisor/projects/6/). + +### Experimental `fw_cfg` Device Support + +This feature enables passing configuration data and files, such as VM +boot configurations (kernel, kernel cmdline, e820 memory map, and ACPI +tables), from the host to the guest. (#7117) + +### Experimental `ivshmem` Device Support + +Support for inter-VM shared memory has been added. For more information, +please refer to the [ivshmem documentation](docs/ivshmem.md). (#6703) + +### Firmware Boot Support on `riscv64` + +In addition to direct kernel boot, firmware boot support has been added +on `riscv64` hosts. (#7249) + +### Increased vCPU Limit on x86_64/kvm + +The maximum number of supported vCPUs on x86_64 hosts using KVM has been +raised from 254 to 8192. (#7299) + +### Improved Block Performance with Small Block Sizes + +Performance for `virtio-blk` with small block sizes (16KB and below) +is enhanced via submitting async IO requests in batches. (#7146) + +### Faster VM Pause Operation + +The VM pause operation now is significantly faster particularly for VMs +with a large number of vCPUs. (#7290) + +### Updated Documentation on Windows Guest Support + +Our Windows documentation now includes instructions to run Windows 11 +guests, in addition to Windows Server guests. (#7218) + +### Policy on AI Generated Code + +We will decline any contributions known to contain contents generated or +derived from using Large Language Models (LLMs). Details can be found +in our [contributing documentation](CONTRIBUTING.md). (#7162) + +### Removed SGX Support + +The SGX support has been removed, as announced in the deprecation notice two +release cycles ago. (#7093) + +### Notable Bug Fixes + +* Seccomp filter fixes with glibc v2.42 (#7327) +* Various fixes related to (#7331, #7334, #7335) + +### Contributors + +Many thanks to everyone who has contributed to our release: + +* Alex Orozco +* Alyssa Ross +* Anirudh Rayabharam +* Bo Chen +* Demi Marie Obenour +* Lucas Grosche +* Muminul Islam +* Oliver Anderson +* Peter Oskolkov +* Philipp Schuster +* Ruoqing He +* Shubham Chakrawar +* Songqian Li +* Wei Liu + # v47.0 This release has been tracked in [v47.0 From d002090afe026be92de03f799fd715e832854006 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 23:01:52 +0000 Subject: [PATCH 0179/1893] build: Bump anyhow from 1.0.98 to 1.0.99 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.98 to 1.0.99. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.98...1.0.99) --- updated-dependencies: - dependency-name: anyhow dependency-version: 1.0.99 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2f13bfd58..14d441e388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,9 +85,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "api_client" diff --git a/Cargo.toml b/Cargo.toml index f154330808..e68233b059 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,7 +136,7 @@ serde_json = "1.0.143" serde_with = { version = "3.14.0", default-features = false } # other crates -anyhow = "1.0.98" +anyhow = "1.0.99" bitflags = "2.9.4" byteorder = "1.5.0" cfg-if = "1.0.0" From 8fd852304217be179e5581743e39219ba3f5c31b Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 27 Jun 2025 13:20:10 +0200 Subject: [PATCH 0180/1893] vmm: add info! message on graceful shutdown This message makes it easier to confirm a successful shutdown when scanning logs during development and in production. If the message is missing and the last log entry is not an error, the most likely cause is a livelock (e.g. contention on a lock) introduced while developing new Cloud Hypervisor features. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 7bdcbfac8b..971d743142 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use std::{env, io}; use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command}; use event_monitor::event; use libc::EFD_NONBLOCK; -use log::{LevelFilter, error, warn}; +use log::{LevelFilter, error, info, warn}; use option_parser::OptionParser; use seccompiler::SeccompAction; use signal_hook::consts::SIGSYS; @@ -891,6 +891,7 @@ fn main() { let exit_code = match start_vmm(cmd_arguments) { Ok(path) => { path.map(|s| std::fs::remove_file(s).ok()); + info!("Cloud Hypervisor exited successfully"); 0 } Err(top_error) => { From ad9a1878bfb79a9a64607bfa613ae916169e1bc2 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 13 Sep 2025 00:35:49 -0700 Subject: [PATCH 0181/1893] hypervisor: remove invalid url Reference link to set_device_attribut for aarch64 is invalid. Looks like the code browsing does not have the reference anymore. Signed-off-by: Muminul Islam --- hypervisor/src/kvm/aarch64/gic/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hypervisor/src/kvm/aarch64/gic/mod.rs b/hypervisor/src/kvm/aarch64/gic/mod.rs index cf4619bd7c..8bb79be2b5 100644 --- a/hypervisor/src/kvm/aarch64/gic/mod.rs +++ b/hypervisor/src/kvm/aarch64/gic/mod.rs @@ -216,9 +216,7 @@ impl KvmGicV3Its { 0, )?; - /* Finalize the GIC. - * See https://code.woboq.org/linux/linux/virt/kvm/arm/vgic/vgic-kvm-device.c.html#211. - */ + // Finalize the GIC. Self::set_device_attribute( &self.device, kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL, From 72dc280a0c9279c151f0fdb45bf5da190a7813cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:09:34 +0000 Subject: [PATCH 0182/1893] build: Bump async-process from 2.4.0 to 2.5.0 Bumps [async-process](https://github.com/smol-rs/async-process) from 2.4.0 to 2.5.0. - [Release notes](https://github.com/smol-rs/async-process/releases) - [Changelog](https://github.com/smol-rs/async-process/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/async-process/compare/v2.4.0...v2.5.0) --- updated-dependencies: - dependency-name: async-process dependency-version: 2.5.0 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14d441e388..eac582bb18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,9 +190,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ "async-channel", "async-io", From 79fe396da99cebdab6ff2866305c3a1dbb9258b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:31:07 +0000 Subject: [PATCH 0183/1893] build: Bump tj-actions/changed-files from 46 to 47 Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 46 to 47. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v46...v47) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-version: '47' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lychee.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lychee.yaml b/.github/workflows/lychee.yaml index 191d53e6a3..8d1d3927c0 100644 --- a/.github/workflows/lychee.yaml +++ b/.github/workflows/lychee.yaml @@ -12,7 +12,7 @@ jobs: fetch-depth: 0 - name: Get changed files in PR id: changed-files - uses: tj-actions/changed-files@v46 # Using a dedicated action for robustness + 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 }} From 90b0dc911cfbdb2185eb02900925d49f029b2554 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 23:01:51 +0000 Subject: [PATCH 0184/1893] build: Bump epoll from 4.3.3 to 4.4.0 Bumps [epoll](https://github.com/nathansizemore/epoll) from 4.3.3 to 4.4.0. - [Release notes](https://github.com/nathansizemore/epoll/releases) - [Commits](https://github.com/nathansizemore/epoll/compare/4.3.3...4.4.0) --- updated-dependencies: - dependency-name: epoll dependency-version: 4.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eac582bb18..049b8b2eff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,9 +635,9 @@ dependencies = [ [[package]] name = "epoll" -version = "4.3.3" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" +checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e" dependencies = [ "bitflags 2.9.4", "libc", diff --git a/Cargo.toml b/Cargo.toml index e68233b059..266379ae28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,7 +144,7 @@ clap = "4.5.47" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" -epoll = "4.3.3" +epoll = "4.4.0" flume = "0.11.1" libc = "0.2.167" log = "0.4.22" From 10fb713218b328b712df5243f75d013934def54b Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Wed, 30 Jul 2025 11:04:24 +0000 Subject: [PATCH 0185/1893] hypervisor: mshv: handle reset intercepts for arm64 Handle MSHV reset intercepts on arm64 such that guests can gracefully shutdown/reboot instead of panicking due to unhandled intercept. Signed-off-by: Anirudh Rayabharam --- hypervisor/src/mshv/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index bc03c44dab..373e338f1f 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -640,6 +640,21 @@ impl cpu::Vcpu for MshvVcpu { debug!("HALT"); Ok(cpu::VmExit::Reset) } + #[cfg(target_arch = "aarch64")] + hv_message_type_HVMSG_ARM64_RESET_INTERCEPT => { + let reset_msg = x.to_reset_intercept_msg().unwrap(); + + match reset_msg.reset_type { + hv_arm64_reset_type_HV_ARM64_RESET_TYPE_REBOOT => Ok(cpu::VmExit::Reset), + hv_arm64_reset_type_HV_ARM64_RESET_TYPE_POWER_OFF => { + Ok(cpu::VmExit::Shutdown) + } + _ => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( + "Unhandled VCPU exit (RESET_INTERCEPT): reset type: {:?}", + reset_msg.reset_type + ))), + } + } hv_message_type_HVMSG_UNRECOVERABLE_EXCEPTION => { warn!("TRIPLE FAULT"); Ok(cpu::VmExit::Shutdown) From f122398262fb5e6ebf0e478401822b891ae60292 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Wed, 17 Sep 2025 00:01:47 +0000 Subject: [PATCH 0186/1893] misc: Fix beta clippy issues Fixed with 'cargo clippy --fix' mostly. Signed-off-by: Bo Chen --- src/main.rs | 10 ++++------ test_infra/src/lib.rs | 8 ++------ vm-migration/src/protocol.rs | 18 ++++-------------- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/src/main.rs b/src/main.rs index 971d743142..92a4b085da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1094,8 +1094,7 @@ mod unit_tests { #[test] fn test_valid_vm_config_memory() { - vec![ - ( + [( vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"], r#"{ "payload": {"kernel": "/path/to/kernel"}, @@ -1150,8 +1149,7 @@ mod unit_tests { "memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824} }"#, true, - ), - ] + )] .iter() .for_each(|(cli, openapi, equal)| { compare_vm_config_cli_vs_json(cli, openapi, *equal); @@ -1302,7 +1300,7 @@ mod unit_tests { #[test] fn test_valid_vm_config_net() { - vec![ + [ // This test is expected to fail because the default MAC address is // randomly generated. There's no way we can have twice the same // default value. @@ -1780,7 +1778,7 @@ mod unit_tests { #[test] #[cfg(target_arch = "x86_64")] fn test_valid_vm_config_devices() { - vec![ + [ ( vec![ "cloud-hypervisor", diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 7dceaecce1..8b260a0a63 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1229,18 +1229,14 @@ impl Guest { } } +#[derive(Default)] pub enum VerbosityLevel { + #[default] Warn, Info, Debug, } -impl Default for VerbosityLevel { - fn default() -> Self { - Self::Warn - } -} - impl Display for VerbosityLevel { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use VerbosityLevel::*; diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index 094a8c1a30..2ed782ae39 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -50,8 +50,9 @@ use crate::MigratableError; // The source can at any time send an "abandon request" to cancel #[repr(u16)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default)] pub enum Command { + #[default] Invalid, Start, Config, @@ -62,12 +63,6 @@ pub enum Command { MemoryFd, } -impl Default for Command { - fn default() -> Self { - Self::Invalid - } -} - #[repr(C)] #[derive(Default, Copy, Clone)] pub struct Request { @@ -139,19 +134,14 @@ impl Request { } #[repr(u16)] -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Default)] pub enum Status { + #[default] Invalid, Ok, Error, } -impl Default for Status { - fn default() -> Self { - Self::Invalid - } -} - #[repr(C)] #[derive(Default, Copy, Clone)] pub struct Response { From 1e8996f94f512780363b368053001774839d25fa Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 12 Sep 2025 17:30:26 -0700 Subject: [PATCH 0187/1893] vmm: hypervisor: simplify VM creation API For MSHV customers don't want to make everything default during partition creation. For example nested support, some synthetic features could be controlled from CLI through platform argument. Create_vm API getting messy after adding more flags. This patch introduces common data struct to be passed from vmm crate to hypervisor crate during partition creation. Signed-off-by: Muminul Islam --- hypervisor/src/hypervisor.rs | 22 +--- hypervisor/src/kvm/aarch64/gic/mod.rs | 11 +- hypervisor/src/kvm/mod.rs | 95 ++++++-------- hypervisor/src/kvm/riscv64/aia.rs | 3 +- hypervisor/src/lib.rs | 10 ++ hypervisor/src/mshv/mod.rs | 174 +++++++++++--------------- vmm/src/cpu.rs | 32 +++-- vmm/src/vm.rs | 36 +++--- 8 files changed, 167 insertions(+), 216 deletions(-) diff --git a/hypervisor/src/hypervisor.rs b/hypervisor/src/hypervisor.rs index 13d00fa009..5db46d9908 100644 --- a/hypervisor/src/hypervisor.rs +++ b/hypervisor/src/hypervisor.rs @@ -13,7 +13,6 @@ use std::sync::Arc; use thiserror::Error; -use crate::HypervisorType; #[cfg(target_arch = "x86_64")] use crate::arch::x86::CpuIdEntry; #[cfg(target_arch = "x86_64")] @@ -21,6 +20,7 @@ use crate::cpu::CpuVendor; #[cfg(feature = "tdx")] use crate::kvm::TdxCapabilities; use crate::vm::Vm; +use crate::{HypervisorType, HypervisorVmConfig}; #[derive(Error, Debug)] pub enum HypervisorError { @@ -110,25 +110,7 @@ pub trait Hypervisor: Send + Sync { /// Create a Vm using the underlying hypervisor /// Return a hypervisor-agnostic Vm trait object /// - fn create_vm(&self) -> Result>; - /// - /// Create a Vm of a specific type using the underlying hypervisor - /// Return a hypervisor-agnostic Vm trait object - /// - fn create_vm_with_type(&self, _vm_type: u64) -> Result> { - unreachable!() - } - /// - /// Create a Vm of a specific type using the underlying hypervisor, passing memory size - /// Return a hypervisor-agnostic Vm trait object - /// - fn create_vm_with_type_and_memory( - &self, - _vm_type: u64, - #[cfg(feature = "sev_snp")] _mem_size: u64, - ) -> Result> { - unreachable!() - } + fn create_vm(&self, config: HypervisorVmConfig) -> Result>; #[cfg(target_arch = "x86_64")] /// /// Get the supported CpuID diff --git a/hypervisor/src/kvm/aarch64/gic/mod.rs b/hypervisor/src/kvm/aarch64/gic/mod.rs index 8bb79be2b5..8bd0ed2403 100644 --- a/hypervisor/src/kvm/aarch64/gic/mod.rs +++ b/hypervisor/src/kvm/aarch64/gic/mod.rs @@ -482,6 +482,7 @@ impl Vgic for KvmGicV3Its { #[cfg(test)] mod tests { + use crate::HypervisorVmConfig; use crate::aarch64::gic::{ get_dist_regs, get_icc_regs, get_redist_regs, set_dist_regs, set_icc_regs, set_redist_regs, }; @@ -506,7 +507,7 @@ mod tests { #[test] fn test_create_gic() { let hv = crate::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); KvmGicV3Its::new(&*vm, create_test_vgic_config()).unwrap(); } @@ -514,7 +515,7 @@ mod tests { #[test] fn test_get_set_dist_regs() { let hv = crate::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _ = vm.create_vcpu(0, None).unwrap(); let gic = KvmGicV3Its::new(&*vm, create_test_vgic_config()).expect("Cannot create gic"); @@ -527,7 +528,7 @@ mod tests { #[test] fn test_get_set_redist_regs() { let hv = crate::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _ = vm.create_vcpu(0, None).unwrap(); let gic = KvmGicV3Its::new(&*vm, create_test_vgic_config()).expect("Cannot create gic"); @@ -542,7 +543,7 @@ mod tests { #[test] fn test_get_set_icc_regs() { let hv = crate::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _ = vm.create_vcpu(0, None).unwrap(); let gic = KvmGicV3Its::new(&*vm, create_test_vgic_config()).expect("Cannot create gic"); @@ -557,7 +558,7 @@ mod tests { #[test] fn test_save_data_tables() { let hv = crate::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _ = vm.create_vcpu(0, None).unwrap(); let gic = vm .create_vgic(create_test_vgic_config()) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 6becd0e411..8e516bb76f 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -46,7 +46,7 @@ pub use crate::riscv64::{ #[cfg(target_arch = "riscv64")] use crate::riscv64_reg_id; use crate::vm::{self, InterruptSourceConfig, VmOps}; -use crate::{HypervisorType, cpu, hypervisor}; +use crate::{HypervisorType, HypervisorVmConfig, cpu, hypervisor}; // x86_64 dependencies #[cfg(target_arch = "x86_64")] pub mod x86_64; @@ -103,7 +103,7 @@ use kvm_bindings::{ #[cfg(target_arch = "riscv64")] use kvm_bindings::{KVM_REG_RISCV_CORE, kvm_riscv_core}; #[cfg(feature = "tdx")] -use kvm_bindings::{KVMIO, kvm_run__bindgen_ty_1}; +use kvm_bindings::{KVM_X86_DEFAULT_VM, KVM_X86_SW_PROTECTED_VM, KVMIO, kvm_run__bindgen_ty_1}; pub use kvm_ioctls::{Cap, Kvm, VcpuExit}; use thiserror::Error; use vfio_ioctls::VfioDeviceFd; @@ -539,10 +539,11 @@ impl KvmVm { /// /// ``` /// # use hypervisor::kvm::KvmHypervisor; +/// # use hypervisor::HypervisorVmConfig; /// # use std::sync::Arc; /// let kvm = KvmHypervisor::new().unwrap(); /// let hypervisor = Arc::new(kvm); -/// let vm = hypervisor.create_vm().expect("new VM fd creation failed"); +/// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// ``` impl vm::Vm for KvmVm { #[cfg(target_arch = "x86_64")] @@ -1161,10 +1162,11 @@ impl KvmHypervisor { /// /// ``` /// # use hypervisor::kvm::KvmHypervisor; +/// # use hypervisor::HypervisorVmConfig; /// # use std::sync::Arc; /// let kvm = KvmHypervisor::new().unwrap(); /// let hypervisor = Arc::new(kvm); -/// let vm = hypervisor.create_vm().expect("new VM fd creation failed"); +/// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// ``` impl hypervisor::Hypervisor for KvmHypervisor { /// @@ -1174,38 +1176,39 @@ impl hypervisor::Hypervisor for KvmHypervisor { HypervisorType::Kvm } - /// - /// Create a Vm of a specific type using the underlying hypervisor, passing memory size - /// Return a hypervisor-agnostic Vm trait object - /// - /// # Examples - /// - /// ``` - /// # use hypervisor::kvm::KvmHypervisor; - /// use hypervisor::kvm::KvmVm; - /// let hypervisor = KvmHypervisor::new().unwrap(); - /// let vm = hypervisor.create_vm_with_type_and_memory(0).unwrap(); - /// ``` - fn create_vm_with_type_and_memory( - &self, - vm_type: u64, - #[cfg(feature = "sev_snp")] _mem_size: u64, - ) -> hypervisor::Result> { - self.create_vm_with_type(vm_type) - } - /// Create a KVM vm object of a specific VM type and return the object as Vm trait object /// /// # Examples /// /// ``` /// # use hypervisor::kvm::KvmHypervisor; - /// use hypervisor::kvm::KvmVm; + /// # use hypervisor::kvm::KvmVm; + /// # use hypervisor::HypervisorVmConfig; /// let hypervisor = KvmHypervisor::new().unwrap(); - /// let vm = hypervisor.create_vm_with_type(0).unwrap(); + /// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).unwrap(); /// ``` - fn create_vm_with_type(&self, vm_type: u64) -> hypervisor::Result> { + fn create_vm(&self, _config: HypervisorVmConfig) -> hypervisor::Result> { let fd: VmFd; + + #[allow(unused_mut)] + #[allow(unused_assignments)] + let mut vm_type: u64 = 0; // Create with default platform type + + // When KVM supports Cap::ArmVmIPASize, it is better to get the IPA + // size from the host and use that when creating the VM, which may + // avoid unnecessary VM creation failures. + #[cfg(target_arch = "aarch64")] + if self.kvm.check_extension(Cap::ArmVmIPASize) { + vm_type = self.kvm.get_host_ipa_limit().try_into().unwrap(); + } + + #[cfg(feature = "tdx")] + if _config.tdx_enabled { + vm_type = KVM_X86_SW_PROTECTED_VM.into(); + } else { + vm_type = KVM_X86_DEFAULT_VM.into(); + }; + loop { match self.kvm.create_vm_with_type(vm_type) { Ok(res) => fd = res, @@ -1256,31 +1259,6 @@ impl hypervisor::Hypervisor for KvmHypervisor { } } - /// Create a KVM vm object and return the object as Vm trait object - /// - /// # Examples - /// - /// ``` - /// # use hypervisor::kvm::KvmHypervisor; - /// use hypervisor::kvm::KvmVm; - /// let hypervisor = KvmHypervisor::new().unwrap(); - /// let vm = hypervisor.create_vm().unwrap(); - /// ``` - fn create_vm(&self) -> hypervisor::Result> { - #[allow(unused_mut)] - let mut vm_type: u64 = 0; // Create with default platform type - - // When KVM supports Cap::ArmVmIPASize, it is better to get the IPA - // size from the host and use that when creating the VM, which may - // avoid unnecessary VM creation failures. - #[cfg(target_arch = "aarch64")] - if self.kvm.check_extension(Cap::ArmVmIPASize) { - vm_type = self.kvm.get_host_ipa_limit().try_into().unwrap(); - } - - self.create_vm_with_type(vm_type) - } - fn check_required_extensions(&self) -> hypervisor::Result<()> { check_required_kvm_extensions(&self.kvm) .map_err(|e| hypervisor::HypervisorError::CheckExtensions(e.into())) @@ -1367,10 +1345,11 @@ pub struct KvmVcpu { /// /// ``` /// # use hypervisor::kvm::KvmHypervisor; +/// # use hypervisor::HypervisorVmConfig; /// # use std::sync::Arc; /// let kvm = KvmHypervisor::new().unwrap(); /// let hypervisor = Arc::new(kvm); -/// let vm = hypervisor.create_vm().expect("new VM fd creation failed"); +/// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// let vcpu = vm.create_vcpu(0, None).unwrap(); /// ``` impl cpu::Vcpu for KvmVcpu { @@ -2449,9 +2428,10 @@ impl cpu::Vcpu for KvmVcpu { /// ```rust /// # use hypervisor::kvm::KvmHypervisor; /// # use std::sync::Arc; + /// # use hypervisor::HypervisorVmConfig; /// let kvm = KvmHypervisor::new().unwrap(); /// let hv = Arc::new(kvm); - /// let vm = hv.create_vm().expect("new VM fd creation failed"); + /// let vm = hv.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// vm.enable_split_irq().unwrap(); /// let vcpu = vm.create_vcpu(0, None).unwrap(); /// let state = vcpu.state().unwrap(); @@ -2686,10 +2666,11 @@ impl cpu::Vcpu for KvmVcpu { /// /// ```rust /// # use hypervisor::kvm::KvmHypervisor; + /// # use hypervisor::HypervisorVmConfig; /// # use std::sync::Arc; /// let kvm = KvmHypervisor::new().unwrap(); /// let hv = Arc::new(kvm); - /// let vm = hv.create_vm().expect("new VM fd creation failed"); + /// let vm = hv.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// vm.enable_split_irq().unwrap(); /// let vcpu = vm.create_vcpu(0, None).unwrap(); /// let state = vcpu.state().unwrap(); @@ -3069,7 +3050,9 @@ mod tests { let kvm = KvmHypervisor::new().unwrap(); let hypervisor = Arc::new(kvm); - let vm = hypervisor.create_vm().expect("new VM fd creation failed"); + let vm = hypervisor + .create_vm(HypervisorVmConfig::default()) + .expect("new VM fd creation failed"); let vcpu0 = vm.create_vcpu(0, None).unwrap(); let core_regs = StandardRegisters::from(kvm_riscv_core { diff --git a/hypervisor/src/kvm/riscv64/aia.rs b/hypervisor/src/kvm/riscv64/aia.rs index 1aebbafbe4..88ca061c05 100644 --- a/hypervisor/src/kvm/riscv64/aia.rs +++ b/hypervisor/src/kvm/riscv64/aia.rs @@ -251,6 +251,7 @@ impl Vaia for KvmAiaImsics { #[cfg(test)] mod tests { + use crate::HypervisorVmConfig; use crate::arch::riscv64::aia::VaiaConfig; use crate::kvm::KvmAiaImsics; @@ -266,7 +267,7 @@ mod tests { #[test] fn test_create_aia() { let hv = crate::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _vcpu = vm.create_vcpu(0, None).unwrap(); assert!(KvmAiaImsics::new(&*vm, create_test_vaia_config()).is_ok()); diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 205691a421..d9448c8c61 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -188,6 +188,16 @@ impl ClockData { } } +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct HypervisorVmConfig { + #[cfg(feature = "tdx")] + pub tdx_enabled: bool, + #[cfg(feature = "sev_snp")] + pub sev_snp_enabled: bool, + #[cfg(feature = "sev_snp")] + pub mem_size: u64, +} + #[derive(Copy, Clone)] pub enum IrqRoutingEntry { #[cfg(feature = "kvm")] diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 373e338f1f..385a444623 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -32,7 +32,7 @@ use crate::arch::x86::emulator::Emulator; use crate::mshv::aarch64::emulator; use crate::mshv::emulator::MshvEmulatorContext; use crate::vm::{self, InterruptSourceConfig, VmOps}; -use crate::{HypervisorType, cpu, hypervisor, vec_with_array_field}; +use crate::{HypervisorType, HypervisorVmConfig, cpu, hypervisor, vec_with_array_field}; #[cfg(feature = "sev_snp")] mod snp_constants; // x86_64 dependencies @@ -269,16 +269,74 @@ impl MshvHypervisor { .get_msr_index_list() .map_err(|e| hypervisor::HypervisorError::GetMsrList(e.into())) } +} + +impl MshvHypervisor { + /// Create a hypervisor based on Mshv + #[allow(clippy::new_ret_no_self)] + pub fn new() -> hypervisor::Result> { + let mshv_obj = + Mshv::new().map_err(|e| hypervisor::HypervisorError::HypervisorCreate(e.into()))?; + Ok(Arc::new(MshvHypervisor { mshv: mshv_obj })) + } + /// Check if the hypervisor is available + pub fn is_available() -> hypervisor::Result { + match std::fs::metadata("/dev/mshv") { + Ok(_) => Ok(true), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false), + Err(err) => Err(hypervisor::HypervisorError::HypervisorAvailableCheck( + err.into(), + )), + } + } +} + +/// Implementation of Hypervisor trait for Mshv +/// +/// # Examples +/// +/// ``` +/// use hypervisor::mshv::MshvHypervisor; +/// # use hypervisor::HypervisorVmConfig; +/// use std::sync::Arc; +/// let mshv = MshvHypervisor::new().unwrap(); +/// let hypervisor = Arc::new(mshv); +/// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); +/// ``` +impl hypervisor::Hypervisor for MshvHypervisor { + /// + /// Returns the type of the hypervisor + /// + fn hypervisor_type(&self) -> HypervisorType { + HypervisorType::Mshv + } + + /// Create a mshv vm object and return the object as Vm trait object + /// + /// # Examples + /// + /// ``` + /// # extern crate hypervisor; + /// use hypervisor::mshv::MshvHypervisor; + /// use hypervisor::mshv::MshvVm; + /// use hypervisor::HypervisorVmConfig; + /// let config = HypervisorVmConfig::default(); + /// let hypervisor = MshvHypervisor::new().unwrap(); + /// let vm = hypervisor.create_vm(config).unwrap(); + /// ``` + fn create_vm(&self, _config: HypervisorVmConfig) -> hypervisor::Result> { + #[allow(unused_mut)] + #[allow(unused_assignments)] + let mut mshv_vm_type = VmType::Normal; // Create with default platform type + #[cfg(feature = "sev_snp")] + { + mshv_vm_type = if _config.sev_snp_enabled { + VmType::Snp + } else { + VmType::Normal + }; + } - fn create_vm_with_type_and_memory_int( - &self, - vm_type: u64, - #[cfg(feature = "sev_snp")] _mem_size: Option, - ) -> hypervisor::Result> { - let mshv_vm_type: VmType = match VmType::try_from(vm_type) { - Ok(vm_type) => vm_type, - Err(_) => return Err(hypervisor::HypervisorError::UnsupportedVmType()), - }; let fd: VmFd; loop { match self.mshv.create_vm_with_type(mshv_vm_type) { @@ -321,7 +379,7 @@ impl MshvHypervisor { #[cfg(feature = "sev_snp")] host_access_pages: ArcSwap::new( AtomicBitmap::new( - _mem_size.unwrap_or_default() as usize, + _config.mem_size as usize, NonZeroUsize::new(HV_PAGE_SIZE).unwrap(), ) .into(), @@ -337,94 +395,6 @@ impl MshvHypervisor { })) } } -} - -impl MshvHypervisor { - /// Create a hypervisor based on Mshv - #[allow(clippy::new_ret_no_self)] - pub fn new() -> hypervisor::Result> { - let mshv_obj = - Mshv::new().map_err(|e| hypervisor::HypervisorError::HypervisorCreate(e.into()))?; - Ok(Arc::new(MshvHypervisor { mshv: mshv_obj })) - } - /// Check if the hypervisor is available - pub fn is_available() -> hypervisor::Result { - match std::fs::metadata("/dev/mshv") { - Ok(_) => Ok(true), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false), - Err(err) => Err(hypervisor::HypervisorError::HypervisorAvailableCheck( - err.into(), - )), - } - } -} - -/// Implementation of Hypervisor trait for Mshv -/// -/// # Examples -/// -/// ``` -/// use hypervisor::mshv::MshvHypervisor; -/// use std::sync::Arc; -/// let mshv = MshvHypervisor::new().unwrap(); -/// let hypervisor = Arc::new(mshv); -/// let vm = hypervisor.create_vm().expect("new VM fd creation failed"); -/// ``` -impl hypervisor::Hypervisor for MshvHypervisor { - /// - /// Returns the type of the hypervisor - /// - fn hypervisor_type(&self) -> HypervisorType { - HypervisorType::Mshv - } - - /// - /// Create a Vm of a specific type using the underlying hypervisor, passing memory size - /// Return a hypervisor-agnostic Vm trait object - /// - /// # Examples - /// - /// ``` - /// use hypervisor::kvm::KvmHypervisor; - /// use hypervisor::kvm::KvmVm; - /// let hypervisor = KvmHypervisor::new().unwrap(); - /// let vm = hypervisor.create_vm_with_type(0, 512*1024*1024).unwrap(); - /// ``` - fn create_vm_with_type_and_memory( - &self, - vm_type: u64, - #[cfg(feature = "sev_snp")] _mem_size: u64, - ) -> hypervisor::Result> { - self.create_vm_with_type_and_memory_int( - vm_type, - #[cfg(feature = "sev_snp")] - Some(_mem_size), - ) - } - - fn create_vm_with_type(&self, vm_type: u64) -> hypervisor::Result> { - self.create_vm_with_type_and_memory_int( - vm_type, - #[cfg(feature = "sev_snp")] - None, - ) - } - - /// Create a mshv vm object and return the object as Vm trait object - /// - /// # Examples - /// - /// ``` - /// # extern crate hypervisor; - /// use hypervisor::mshv::MshvHypervisor; - /// use hypervisor::mshv::MshvVm; - /// let hypervisor = MshvHypervisor::new().unwrap(); - /// let vm = hypervisor.create_vm().unwrap(); - /// ``` - fn create_vm(&self) -> hypervisor::Result> { - let vm_type = 0; - self.create_vm_with_type(vm_type) - } #[cfg(target_arch = "x86_64")] /// /// Get the supported CpuID @@ -508,10 +478,11 @@ pub struct MshvVcpu { /// /// ``` /// use hypervisor::mshv::MshvHypervisor; +/// use hypervisor::HypervisorVmConfig; /// use std::sync::Arc; /// let mshv = MshvHypervisor::new().unwrap(); /// let hypervisor = Arc::new(mshv); -/// let vm = hypervisor.create_vm().expect("new VM fd creation failed"); +/// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// let vcpu = vm.create_vcpu(0, None).unwrap(); /// ``` impl cpu::Vcpu for MshvVcpu { @@ -1787,10 +1758,11 @@ impl MshvVm { /// ``` /// extern crate hypervisor; /// use hypervisor::mshv::MshvHypervisor; +/// use hypervisor::HypervisorVmConfig; /// use std::sync::Arc; /// let mshv = MshvHypervisor::new().unwrap(); /// let hypervisor = Arc::new(mshv); -/// let vm = hypervisor.create_vm().expect("new VM fd creation failed"); +/// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// ``` impl vm::Vm for MshvVm { #[cfg(target_arch = "x86_64")] diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 43edf5015b..2b52f950c0 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -2861,14 +2861,16 @@ mod tests { use arch::layout::{BOOT_STACK_POINTER, ZERO_PAGE_START}; use arch::x86_64::interrupts::*; use arch::x86_64::regs::*; - use hypervisor::StandardRegisters; use hypervisor::arch::x86::{FpuState, LapicState}; + use hypervisor::{HypervisorVmConfig, StandardRegisters}; use linux_loader::loader::bootparam::setup_header; #[test] fn test_setlint() { let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().expect("new VM fd creation failed"); + let vm = hv + .create_vm(HypervisorVmConfig::default()) + .expect("new VM fd creation failed"); hv.check_required_extensions().unwrap(); // Calling get_lapic will fail if there is no irqchip before hand. vm.create_irq_chip().unwrap(); @@ -2894,7 +2896,9 @@ mod tests { #[test] fn test_setup_fpu() { let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().expect("new VM fd creation failed"); + let vm = hv + .create_vm(HypervisorVmConfig::default()) + .expect("new VM fd creation failed"); let vcpu = vm.create_vcpu(0, None).unwrap(); setup_fpu(&vcpu).unwrap(); @@ -2918,7 +2922,9 @@ mod tests { use hypervisor::arch::x86::{MsrEntry, msr_index}; let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().expect("new VM fd creation failed"); + let vm = hv + .create_vm(HypervisorVmConfig::default()) + .expect("new VM fd creation failed"); let vcpu = vm.create_vcpu(0, None).unwrap(); setup_msrs(&vcpu).unwrap(); @@ -2944,7 +2950,9 @@ mod tests { #[test] fn test_setup_regs_for_pvh() { let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().expect("new VM fd creation failed"); + let vm = hv + .create_vm(HypervisorVmConfig::default()) + .expect("new VM fd creation failed"); let vcpu = vm.create_vcpu(0, None).unwrap(); let mut expected_regs: StandardRegisters = vcpu.create_standard_regs(); @@ -2968,7 +2976,9 @@ mod tests { #[test] fn test_setup_regs_for_bzimage() { let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().expect("new VM fd creation failed"); + let vm = hv + .create_vm(HypervisorVmConfig::default()) + .expect("new VM fd creation failed"); let vcpu = vm.create_vcpu(0, None).unwrap(); let mut expected_regs: StandardRegisters = vcpu.create_standard_regs(); @@ -3000,7 +3010,6 @@ mod tests { use std::{mem, mem::offset_of}; use arch::layout; - use hypervisor::HypervisorCpuError; use hypervisor::arch::aarch64::regs::MPIDR_EL1; #[cfg(feature = "kvm")] use hypervisor::arm64_core_reg_id; @@ -3010,11 +3019,12 @@ mod tests { use hypervisor::kvm::kvm_bindings::{ KVM_REG_ARM_CORE, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_SIZE_U64, user_pt_regs, }; + use hypervisor::{HypervisorCpuError, HypervisorVmConfig}; #[test] fn test_setup_regs() { let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let vcpu = vm.create_vcpu(0, None).unwrap(); // Must fail when vcpu is not initialized yet. @@ -3030,7 +3040,7 @@ mod tests { #[test] fn test_read_mpidr() { let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let vcpu = vm.create_vcpu(0, None).unwrap(); let mut kvi = vcpu.create_vcpu_init(); vm.get_preferred_target(&mut kvi).unwrap(); @@ -3055,7 +3065,7 @@ mod tests { #[test] fn test_save_restore_core_regs() { let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let vcpu = vm.create_vcpu(0, None).unwrap(); let mut kvi = vcpu.create_vcpu_init(); vm.get_preferred_target(&mut kvi).unwrap(); @@ -3105,7 +3115,7 @@ mod tests { #[test] fn test_get_set_mpstate() { let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let vcpu = vm.create_vcpu(0, None).unwrap(); let mut kvi = vcpu.create_vcpu_init(); vm.get_preferred_target(&mut kvi).unwrap(); diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 836feb4707..f6199fdf19 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -45,7 +45,7 @@ use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs; use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs; #[cfg(target_arch = "aarch64")] use hypervisor::arch::aarch64::regs::AARCH64_PMU_IRQ; -use hypervisor::{HypervisorVmError, VmOps}; +use hypervisor::{HypervisorVmConfig, HypervisorVmError, VmOps}; use libc::{SIGWINCH, termios}; use linux_loader::cmdline::Cmdline; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] @@ -1084,26 +1084,16 @@ impl Vm { #[cfg(feature = "sev_snp")] mem_size: u64, ) -> Result> { hypervisor.check_required_extensions().unwrap(); + let config = HypervisorVmConfig { + #[cfg(feature = "tdx")] + tdx_enabled, + #[cfg(feature = "sev_snp")] + sev_snp_enabled, + #[cfg(feature = "sev_snp")] + mem_size, + }; - cfg_if::cfg_if! { - if #[cfg(feature = "tdx")] { - // Passing KVM_X86_TDX_VM: 1 if tdx_enabled is true - // Otherwise KVM_X86_LEGACY_VM: 0 - // value of tdx_enabled is mapped to KVM_X86_TDX_VM or KVM_X86_LEGACY_VM - let vm = hypervisor - .create_vm_with_type(u64::from(tdx_enabled)) - .unwrap(); - } else if #[cfg(feature = "sev_snp")] { - // Passing SEV_SNP_ENABLED: 1 if sev_snp_enabled is true - // Otherwise SEV_SNP_DISABLED: 0 - // value of sev_snp_enabled is mapped to SEV_SNP_ENABLED for true or SEV_SNP_DISABLED for false - let vm = hypervisor - .create_vm_with_type_and_memory(u64::from(sev_snp_enabled), mem_size) - .unwrap(); - } else { - let vm = hypervisor.create_vm().unwrap(); - } - } + let vm = hypervisor.create_vm(config).unwrap(); #[cfg(target_arch = "x86_64")] { @@ -3497,7 +3487,7 @@ mod tests { .collect(); let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().unwrap(); + let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let gic = vm .create_vgic(Gic::create_default_config(1)) .expect("Cannot create gic"); @@ -3539,7 +3529,9 @@ pub fn test_vm() { let mem = GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap(); let hv = hypervisor::new().unwrap(); - let vm = hv.create_vm().expect("new VM creation failed"); + let vm = hv + .create_vm(HypervisorVmConfig::default()) + .expect("new VM creation failed"); for (index, region) in mem.iter().enumerate() { let mem_region = vm.make_user_memory_region( From d28d9eb34e071d30bddf4b9bba6b8772a70df87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20G=C3=BCntner?= Date: Sun, 14 Sep 2025 00:29:14 +0200 Subject: [PATCH 0188/1893] virtio-devices: refactor VSOCK "connect" parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function `read_local_stream_port` had no proper handling for unexpected or incomplete input. When the control socket of the VSOCK device was closed without sending the expected `CONNECT \n` statement completely, the thread got stuck in an infinite loop as it attempted to read from a closed socket over and over again which never returned any data. This resulted in the thread responsible for `epoll` being completely blocked. New VSOCK connections could not be established and existing ones became defunct, effectively leading to a Denial of Service of the entire VSOCK device. The issue can be reproduced by opening a socket and immediately closing it. ``` socat - UNIX-CONNECT:/socket.vsock ``` Instead of applying a quick fix by handling the `EPOLLHUP` event before reading, the function is refactored to remove the error-prone `while` loop and multiple `read`s. Notably, we now check if the number of bytes read is zero, which occurs when `event_set == EPOLLHUP | EPOLLIN`, indicating that the socket has been closed by the client. Additionally, the actual parsing code is now extracted into a dedicated function that is tested. Fixes: #6798 Signed-off-by: Maximilian Güntner --- virtio-devices/src/vsock/unix/muxer.rs | 253 ++++++++++++++++++++----- 1 file changed, 209 insertions(+), 44 deletions(-) diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index 842e02677d..5ea8efb9ba 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -38,6 +38,7 @@ //! To route all these events to their handlers, the muxer uses another `HashMap` object, //! mapping `RawFd`s to `EpollListener`s. +use std::cmp::max; use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{self, ErrorKind, Read}; @@ -90,11 +91,13 @@ enum EpollListener { LocalStream(UnixStream), } +const PARTIALLY_READ_COMMAND_BUF_SIZE: usize = 32; + /// A partially read "CONNECT" command. #[derive(Default)] struct PartiallyReadCommand { /// The bytes of the command that have been read so far. - buf: [u8; 32], + buf: [u8; PARTIALLY_READ_COMMAND_BUF_SIZE], /// How much of `buf` has been used. len: usize, } @@ -435,7 +438,11 @@ impl VsockMuxer { // "connect" command that we're expecting. Some(EpollListener::LocalStream(_)) => { if let Some(EpollListener::LocalStream(stream)) = self.listener_map.get_mut(&fd) { - let port = Self::read_local_stream_port(&mut self.partial_command_map, stream); + let command = self + .partial_command_map + .entry(stream.as_raw_fd()) + .or_default(); + let port = Self::read_local_stream_port(command, stream); if let Err(Error::UnixRead(ref e)) = port && e.kind() == ErrorKind::WouldBlock @@ -443,6 +450,11 @@ impl VsockMuxer { return; } + // either we have `Ok(port)` or a fatal Error such as + // Error::InvalidPortRequest, either way we must remove + // the command from the map + self.partial_command_map.remove(&stream.as_raw_fd()); + let stream = match self.remove_listener(fd) { Some(EpollListener::LocalStream(s)) => s, _ => unreachable!(), @@ -480,55 +492,73 @@ impl VsockMuxer { } } - /// Parse a host "connect" command, and extract the destination vsock port. - /// - fn read_local_stream_port( - partial_command_map: &mut HashMap, - stream: &mut UnixStream, - ) -> Result { - let command = partial_command_map.entry(stream.as_raw_fd()).or_default(); + fn parse_port_from_read_command(command: &PartiallyReadCommand) -> Result { + // normally followed by the port and a `\n` + let connect_prefix: &str = "connect "; - // This is the minimum number of bytes that we should be able to read, when parsing a - // valid connection request. I.e. `b"connect 0\n".len()`. - const MIN_COMMAND_LEN: usize = 10; + let opt_new_line_position = command.buf[..command.len].iter().position(|x| *x == b'\n'); - // Bring in the minimum number of bytes that we should be able to read. - if command.len < MIN_COMMAND_LEN { - command.len += stream - .read(&mut command.buf[command.len..MIN_COMMAND_LEN]) - .map_err(Error::UnixRead)?; + // we need to read more to get a `connect ` statement + if command.len < connect_prefix.len() { + return match opt_new_line_position { + Some(_) => Err(Error::InvalidPortRequest), + None => Err(Error::UnixRead(std::io::ErrorKind::WouldBlock.into())), + }; } - // Now, finish reading the destination port number, by bringing in one byte at a time, - // until we reach an EOL terminator (or our buffer space runs out). Yeah, not - // particularly proud of this approach, but it will have to do for now. - while command.len.checked_sub(1).map(|n| command.buf[n]) != Some(b'\n') - && command.len < command.buf.len() - { - command.len += stream - .read(&mut command.buf[command.len..=command.len]) - .map_err(Error::UnixRead)?; + // check for both upper and lower case connect statements + if !command.buf[..connect_prefix.len()].eq_ignore_ascii_case(connect_prefix.as_bytes()) { + return Err(Error::InvalidPortRequest); } - let command = partial_command_map.remove(&stream.as_raw_fd()).unwrap(); + // we filled our buffer + if command.buf.len() == command.len && opt_new_line_position.is_none() { + return Err(Error::InvalidPortRequest); + } - let mut word_iter = std::str::from_utf8(&command.buf[..command.len]) - .map_err(Error::ConvertFromUtf8)? - .split_whitespace(); + // we parsed correctly `connect ` but need to wait for `\n` + let new_line_position = + opt_new_line_position.ok_or(Error::UnixRead(std::io::ErrorKind::WouldBlock.into()))?; - word_iter - .next() - .ok_or(Error::InvalidPortRequest) - .and_then(|word| { - if word.to_lowercase() == "connect" { - Ok(()) - } else { - Err(Error::InvalidPortRequest) - } - }) - .and_then(|_| word_iter.next().ok_or(Error::InvalidPortRequest)) - .and_then(|word| word.parse::().map_err(Error::ParseInteger)) - .map_err(|e| Error::ReadStreamPort(Box::new(e))) + // we now have the newline, we will treat everything in between as the port + let port_string_as_bytes = &command.buf[connect_prefix.len()..new_line_position]; + + std::str::from_utf8(port_string_as_bytes) + .map_err(|_| Error::InvalidPortRequest)? + .trim() + .parse::() + .map_err(|_| Error::InvalidPortRequest) + } + + /// Parse a host "connect" command, and extract the destination vsock port. + /// + fn read_local_stream_port( + command: &mut PartiallyReadCommand, + stream: &mut UnixStream, + ) -> Result { + // the minimum connect statement that is still valid + let connect_min_statement: &str = "connect 0\n"; + + // read the amount of bytes that are required for a valid connect + // with the minimum length (`connect_min_statement`). + // Then, continue with reading a single byte at a time, this is + // really inefficient but prevents us to read past the `\n` character + // which might swallow actual application data + // alternative: the bytes that might have been read beyond `\n` would need + // to be sent somehow via `MuxerConnection` prior to reading from `stream` again + // Another, currently unstable alternative: use UnixStream::peak to read the + // data without removing it from the queue. + // Issue: https://github.com/rust-lang/rust/issues/76923 + let read_bytes = stream + .read(&mut command.buf[command.len..max(connect_min_statement.len(), command.len + 1)]) + .map_err(Error::UnixRead)?; + + if read_bytes == 0 { + return Err(Error::InvalidPortRequest); + } + + command.len += read_bytes; + Self::parse_port_from_read_command(command) } /// Add a new connection to the active connection pool. @@ -850,6 +880,7 @@ impl VsockMuxer { #[cfg(test)] mod tests { + use std::cmp::min; use std::io::Write; use std::path::{Path, PathBuf}; @@ -859,6 +890,18 @@ mod tests { use super::super::super::tests::TestContext as VsockTestContext; use super::*; + impl PartiallyReadCommand { + /// used to construct `PartiallyReadCommand` for tests + fn from_str(s: &str) -> Self { + let input_bytes = s.as_bytes(); + let mut command = PartiallyReadCommand::default(); + let len_to_copy = min(input_bytes.len(), PARTIALLY_READ_COMMAND_BUF_SIZE); + command.buf[..len_to_copy].copy_from_slice(&input_bytes[..len_to_copy]); + command.len = len_to_copy; + command + } + } + const PEER_CID: u32 = 3; const PEER_BUF_ALLOC: u32 = 64 * 1024; @@ -971,7 +1014,10 @@ mod tests { stream.write_all(buf.as_bytes()).unwrap(); // The muxer would now get notified that data is available for reading from the locally // initiated connection. - self.notify_muxer(); + // this needs to happen multiple times because the command may not be read at once + for _ in 0..buf.len() { + self.notify_muxer(); + } // Successfully reading and parsing the connection request should have removed the // LocalStream epoll listener and added a Connection epoll listener. @@ -1454,4 +1500,123 @@ mod tests { // not be any pending RX in the muxer. assert!(!ctx.muxer.has_pending_rx()); } + + #[test] + fn test_parse_command() { + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str("")), + Err(Error::UnixRead(_)) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str("\n")), + Err(Error::InvalidPortRequest) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str("CONN\n")), + Err(Error::InvalidPortRequest) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str("FOO ")), + Err(Error::UnixRead(_)) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str("FOOFOOX ")), + Err(Error::InvalidPortRequest) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str("CONNECT ")), + Err(Error::UnixRead(_)) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str("connect ")), + Err(Error::UnixRead(_)) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str("connect \n")), + Err(Error::InvalidPortRequest) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "connect 1337" + )), + Err(Error::UnixRead(_)) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "connect -1337\n" + )), + Err(Error::InvalidPortRequest) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "connect 8589934592\n" + )), + Err(Error::InvalidPortRequest) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "CONNECT 👾\n" + )), + Err(Error::InvalidPortRequest) + )); + let max_buf_length_no_newline = "CONNECT 1"; + assert_eq!( + max_buf_length_no_newline.len(), + PARTIALLY_READ_COMMAND_BUF_SIZE + ); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + max_buf_length_no_newline + )), + Err(Error::InvalidPortRequest) + )); + let max_buf_length_correct = "CONNECT 1\n"; + assert_eq!( + max_buf_length_correct.len(), + PARTIALLY_READ_COMMAND_BUF_SIZE + ); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + max_buf_length_correct + )), + Ok(1) + )); + + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "connect 0\n" + )), + Ok(0) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "connect 1337\n" + )), + Ok(1337) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "CONNECT 1337\n" + )), + Ok(1337) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "CONNECT 1337\n" + )), + Ok(1337) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "CONNECT 1337 \n" + )), + Ok(1337) + )); + assert!(matches!( + VsockMuxer::parse_port_from_read_command(&PartiallyReadCommand::from_str( + "CONNECT 1337 \n" + )), + Ok(1337) + )); + } } From 66aa0743f087f9598835a0c13bab76535e1ae5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20G=C3=BCntner?= Date: Mon, 22 Sep 2025 10:43:03 +0200 Subject: [PATCH 0189/1893] vmm: allow net devices without ip and mask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change enables easier integration with third-party tools by removing the requirement for a dummy IP address when configuring tap devices. The modification applies to both CLI and API interactions. Previously, cloud-hypervisor would automatically set a default static IP address (192.168.249.1) if none was provided. This could lead to: * multiple devices without explicit IP configurations would end up with the same default IP * unnecessary inclusion of this IP in firewall rules * the IP address could clash with host networking and routing This introduces a new constraint: When providing an IP, the mask must also be provided. Removes warnings introduced in #7179. Closes issue #7083. Signed-off-by: Maximilian Güntner --- option_parser/src/lib.rs | 2 +- src/main.rs | 14 --------- vmm/src/config.rs | 63 +++++++++++++++++++++++++++++++-------- vmm/src/device_manager.rs | 8 ++--- vmm/src/vm_config.rs | 22 ++------------ 5 files changed, 58 insertions(+), 51 deletions(-) diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index ff3d9ffffd..d28c2db5ed 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -36,7 +36,7 @@ struct OptionParserValue { requires_value: bool, } -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum OptionParserError { #[error("unknown option: {0}")] UnknownOption(String), diff --git a/src/main.rs b/src/main.rs index 92a4b085da..9bbc4a05ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1336,20 +1336,6 @@ mod unit_tests { }"#, true, ), - ( - vec![ - "cloud-hypervisor", "--kernel", "/path/to/kernel", - "--net", - "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4", - ], - r#"{ - "payload": {"kernel": "/path/to/kernel"}, - "net": [ - {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"} - ] - }"#, - true, - ), ( vec![ "cloud-hypervisor", "--kernel", "/path/to/kernel", diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 366b369e76..d04536f60c 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -357,6 +357,10 @@ pub enum ValidationError { InvalidIvshmemPath, #[error("Payload configuration is not bootable")] PayloadError(#[from] PayloadConfigError), + #[error("Mask provided without an IP")] + MaskProvidedWithoutIp, + #[error("IP provided without a mask")] + IpProvidedWithoutMask, } type ValidationResult = std::result::Result; @@ -1331,14 +1335,9 @@ impl NetConfig { parser.parse(net).map_err(Error::ParseNetwork)?; let tap = parser.get("tap"); - let ip = parser - .convert("ip") - .map_err(Error::ParseNetwork)? - .unwrap_or_else(default_netconfig_ip); - let mask = parser - .convert("mask") - .map_err(Error::ParseNetwork)? - .unwrap_or_else(default_netconfig_mask); + let ip = parser.convert("ip").map_err(Error::ParseNetwork)?; + let mask = parser.convert("mask").map_err(Error::ParseNetwork)?; + let mac = parser .convert("mac") .map_err(Error::ParseNetwork)? @@ -1515,6 +1514,14 @@ impl NetConfig { return Err(ValidationError::NoHardwareChecksumOffload); } + if self.mask.is_some() && self.ip.is_none() { + return Err(ValidationError::MaskProvidedWithoutIp); + } + + if self.ip.is_some() && self.mask.is_none() { + return Err(ValidationError::IpProvidedWithoutMask); + } + Ok(()) } } @@ -3177,7 +3184,6 @@ impl Drop for VmConfig { #[cfg(test)] mod tests { use std::fs::File; - use std::net::{IpAddr, Ipv4Addr}; use std::os::unix::io::AsRawFd; use net_util::MacAddr; @@ -3507,8 +3513,8 @@ mod tests { fn net_fixture() -> NetConfig { NetConfig { tap: None, - ip: IpAddr::V4(Ipv4Addr::new(192, 168, 249, 1)), - mask: IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0)), + ip: None, + mask: None, mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), mtu: None, @@ -3550,8 +3556,8 @@ mod tests { )?, NetConfig { tap: Some("tap0".to_owned()), - ip: "192.168.100.1".parse().unwrap(), - mask: "255.255.255.128".parse().unwrap(), + ip: Some("192.168.100.1".parse().unwrap()), + mask: Some("255.255.255.128".parse().unwrap()), ..net_fixture() } ); @@ -3589,6 +3595,15 @@ mod tests { } ); + assert_eq!( + NetConfig::parse("mac=de:ad:be:ef:12:34,mask=255.255.255.0")?, + NetConfig { + mask: Some("255.255.255.0".parse().unwrap()), + host_mac: None, + ..net_fixture() + } + ); + Ok(()) } @@ -4289,6 +4304,28 @@ mod tests { Err(ValidationError::NoHardwareChecksumOffload) ); + let mut invalid_config = valid_config.clone(); + invalid_config.net = Some(vec![NetConfig { + ip: None, + mask: Some("255.255.255.0".parse().unwrap()), + ..net_fixture() + }]); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::MaskProvidedWithoutIp) + ); + + let mut invalid_config = valid_config.clone(); + invalid_config.net = Some(vec![NetConfig { + ip: Some("192.1.33.7".parse().unwrap()), + mask: None, + ..net_fixture() + }]); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::IpProvidedWithoutMask) + ); + let mut invalid_config = valid_config.clone(); invalid_config.fs = Some(vec![fs_fixture()]); assert_eq!( diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 40a3d27cee..7a5adb1c3f 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2904,8 +2904,8 @@ impl DeviceManager { virtio_devices::Net::new( id.clone(), Some(tap_if_name), - Some(net_cfg.ip), - Some(net_cfg.mask), + net_cfg.ip, + net_cfg.mask, Some(net_cfg.mac), &mut net_cfg.host_mac, net_cfg.mtu, @@ -2955,8 +2955,8 @@ impl DeviceManager { virtio_devices::Net::new( id.clone(), None, - Some(net_cfg.ip), - Some(net_cfg.mask), + net_cfg.ip, + net_cfg.mask, Some(net_cfg.mac), &mut net_cfg.host_mac, net_cfg.mtu, diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 7a581307e7..76fe2d2b2a 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 // -use std::net::{IpAddr, Ipv4Addr}; +use std::net::IpAddr; use std::path::{Path, PathBuf}; #[cfg(feature = "fw_cfg")] use std::str::FromStr; @@ -303,10 +303,8 @@ pub fn default_diskconfig_queue_size() -> u16 { pub struct NetConfig { #[serde(default = "default_netconfig_tap")] pub tap: Option, - #[serde(default = "default_netconfig_ip")] - pub ip: IpAddr, - #[serde(default = "default_netconfig_mask")] - pub mask: IpAddr, + pub ip: Option, + pub mask: Option, #[serde(default = "default_netconfig_mac")] pub mac: MacAddr, #[serde(default)] @@ -352,20 +350,6 @@ pub fn default_netconfig_tap() -> Option { None } -pub fn default_netconfig_ip() -> IpAddr { - warn!( - "Deprecation warning: No IP address provided. A default IP address is assigned. This behavior will be deprecated soon." - ); - IpAddr::V4(Ipv4Addr::new(192, 168, 249, 1)) -} - -pub fn default_netconfig_mask() -> IpAddr { - warn!( - "Deprecation warning: No network mask provided. A default network mask is assigned. This behavior will be deprecated soon." - ); - IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0)) -} - pub fn default_netconfig_mac() -> MacAddr { MacAddr::local_random() } From 78a16227d20183159dca976dcdbf9e3c65dd18ab Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 12 Sep 2025 20:30:57 +0000 Subject: [PATCH 0190/1893] scripts: Exit create-cloud-init.sh on error Signed-off-by: Wei Liu --- scripts/create-cloud-init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/create-cloud-init.sh b/scripts/create-cloud-init.sh index d86b672c22..6ff418fe96 100755 --- a/scripts/create-cloud-init.sh +++ b/scripts/create-cloud-init.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -x +set -ex rm -f /tmp/ubuntu-cloudinit.img mkdosfs -n CIDATA -C /tmp/ubuntu-cloudinit.img 8192 From e5c9f7d6158cc15a78491c58efe5d696224e3cb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:02:12 +0000 Subject: [PATCH 0191/1893] build: Bump igvm_defs from `dff4ebc` to `9b67b6f` Bumps [igvm_defs](https://github.com/microsoft/igvm) from `dff4ebc` to `9b67b6f`. - [Release notes](https://github.com/microsoft/igvm/releases) - [Commits](https://github.com/microsoft/igvm/compare/dff4ebc9c5bd16707ff75de26ccabe2d4dfdbcd8...9b67b6fa774f4a842718c5c70645973fe73ef616) --- updated-dependencies: - dependency-name: igvm_defs dependency-version: 9b67b6fa774f4a842718c5c70645973fe73ef616 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 049b8b2eff..a9d515952a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,7 +950,7 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "igvm" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#dff4ebc9c5bd16707ff75de26ccabe2d4dfdbcd8" +source = "git+https://github.com/microsoft/igvm?branch=main#9b67b6fa774f4a842718c5c70645973fe73ef616" dependencies = [ "bitfield-struct", "crc32fast", @@ -967,7 +967,7 @@ dependencies = [ [[package]] name = "igvm_defs" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#dff4ebc9c5bd16707ff75de26ccabe2d4dfdbcd8" +source = "git+https://github.com/microsoft/igvm?branch=main#9b67b6fa774f4a842718c5c70645973fe73ef616" dependencies = [ "bitfield-struct", "open-enum", From 7a5781e5a26264c7b365e6faa1bf1edebcb3aa65 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 26 Aug 2025 16:23:24 +0200 Subject: [PATCH 0192/1893] vmm: logger: improve output of timestamp Streamline the output format of the timestamp of a message. The old format doesn't nicely align across multiple lines. # Example (old style) ``` cloud-hypervisor: 858.465660ms: DEBUG:devices/src/ioapic.rs:154 -- IOAPIC_R @ offset 0x10 cloud-hypervisor: 858.507342ms: DEBUG:devices/src/ioapic.rs:298 -- IOAPIC_R reg 0x1 cloud-hypervisor: 1.010001s: DEBUG:devices/src/ioapic.rs:174 -- IOAPIC_W @ offset 0x0 cloud-hypervisor: 1.010067s: DEBUG:devices/src/ioapic.rs:154 -- IOAPIC_R @ offset 0x10 ``` # Example (new style) ``` cloud-hypervisor: 0.731399s: DEBUG:devices/src/ioapic.rs:174 -- IOAPIC_W @ offset 0x10 cloud-hypervisor: 0.731420s: DEBUG:devices/src/ioapic.rs:252 -- IOAPIC_W reg 0x2a, val 0x10000 cloud-hypervisor: 17.026073s: INFO:vmm/src/api/mod.rs:898 -- API request event: VmInfo cloud-hypervisor: 17.263210s: INFO:vmm/src/api/mod.rs:898 -- API request event: VmInfo cloud-hypervisor: 17.547915s: INFO:vmm/src/api/mod.rs:898 -- API request event: VmInfo ``` Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- src/main.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9bbc4a05ad..020cd35161 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,12 +133,14 @@ impl log::Log for Logger { let now = std::time::Instant::now(); let duration = now.duration_since(self.start); + let duration_s = duration.as_secs_f32(); if record.file().is_some() && record.line().is_some() { write!( *(*(self.output.lock().unwrap())), - "cloud-hypervisor: {:.6?}: <{}> {}:{}:{} -- {}\r\n", - duration, + // 10: 6 decimal places + sep => whole seconds in range `0..=999` properly aligned + "cloud-hypervisor: {:>10.6?}s: <{}> {}:{}:{} -- {}\r\n", + duration_s, std::thread::current().name().unwrap_or("anonymous"), record.level(), record.file().unwrap(), @@ -148,8 +150,9 @@ impl log::Log for Logger { } else { write!( *(*(self.output.lock().unwrap())), - "cloud-hypervisor: {:.6?}: <{}> {}:{} -- {}\r\n", - duration, + // 10: 6 decimal places + sep => whole seconds in range `0..=999` properly aligned + "cloud-hypervisor: {:>10.6?}s: <{}> {}:{} -- {}\r\n", + duration_s, std::thread::current().name().unwrap_or("anonymous"), record.level(), record.target(), From fae32412dcb76b4942245aeab2839811873521f1 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 26 Aug 2025 16:25:48 +0200 Subject: [PATCH 0193/1893] vmm: logger: improve code, remove duplication Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- src/main.rs | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 020cd35161..19a4d4eab6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -135,30 +135,23 @@ impl log::Log for Logger { let duration = now.duration_since(self.start); let duration_s = duration.as_secs_f32(); - if record.file().is_some() && record.line().is_some() { - write!( - *(*(self.output.lock().unwrap())), - // 10: 6 decimal places + sep => whole seconds in range `0..=999` properly aligned - "cloud-hypervisor: {:>10.6?}s: <{}> {}:{}:{} -- {}\r\n", - duration_s, - std::thread::current().name().unwrap_or("anonymous"), - record.level(), - record.file().unwrap(), - record.line().unwrap(), - record.args() - ) + let location = if let (Some(file), Some(line)) = (record.file(), record.line()) { + format!("{}:{}", file, line) } else { - write!( - *(*(self.output.lock().unwrap())), - // 10: 6 decimal places + sep => whole seconds in range `0..=999` properly aligned - "cloud-hypervisor: {:>10.6?}s: <{}> {}:{} -- {}\r\n", - duration_s, - std::thread::current().name().unwrap_or("anonymous"), - record.level(), - record.target(), - record.args() - ) - } + record.target().to_string() + }; + + let mut out = self.output.lock().unwrap(); + write!( + &mut *out, + // 10: 6 decimal places + sep => whole seconds in range `0..=999` properly aligned + "cloud-hypervisor: {:>10.6?}s: <{}> {}:{} -- {}\r\n", + duration_s, + std::thread::current().name().unwrap_or("anonymous"), + record.level(), + location, + record.args(), + ) .ok(); } fn flush(&self) {} From 60efa1aa0388e5bed6adcf9484fc82b4c2977db0 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 17 Jun 2025 14:14:29 +0200 Subject: [PATCH 0194/1893] vmm: doc improvement for "magic" network FD handling Live migration, state save/resume, and hotplug are not trivial when it comes to virtio-net devices backed by externally provided FDs. As the mechanism behind it can be considered as quite "multi-step magic" even for experienced programmers, it makes sense to thoroughly document this to ease debugging and to improve the mental model of developers working on this in the future. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/api/http/http_endpoint.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/vmm/src/api/http/http_endpoint.rs b/vmm/src/api/http/http_endpoint.rs index 5a4bf93da6..6bda58d50e 100644 --- a/vmm/src/api/http/http_endpoint.rs +++ b/vmm/src/api/http/http_endpoint.rs @@ -4,6 +4,33 @@ // SPDX-License-Identifier: Apache-2.0 // +//! # HTTP Endpoints of the Cloud Hypervisor API +//! +//! ## Special Handling for Devices Backed by Network File Descriptors (FDs) (e.g., virtio-net) +//! +//! Some of the HTTP handlers here implement special logic for devices +//! **backed by network FDs** to enable live-migration, state save/resume +//! (restore), and similar VM lifecycle events. +//! +//! The utilized mechanism requires that the control software (e.g., libvirt) +//! connects to Cloud Hypervisor by using a UNIX domain socket and that it +//! passes file descriptors (FDs) via _ancillary_ messages - specifically using +//! the `SCM_RIGHTS` mechanism described in [`cmsg(3)`]. These ancillary +//! messages must accompany the primary payload (HTTP JSON REST API in this +//! case). The Linux kernel handles these messages by `dup()`ing the referenced +//! FDs from the sender process into the receiving process, thereby ensuring +//! they are valid and usable in the target context. +//! +//! Once these valid file descriptors are received here, we integrate the actual +//! FDs into the VM's configuration, allowing the device to function correctly +//! with its backing network resources. +//! +//! We can receive these FDs as we use a [special HTTP library] that is aware +//! of the described mechanism. +//! +//! [`cmsg(3)`]: https://man7.org/linux/man-pages/man3/cmsg.3.html +//! [special HTTP library]: https://github.com/firecracker-microvm/micro-http + use std::fs::File; use std::os::unix::io::IntoRawFd; use std::sync::mpsc::Sender; @@ -193,6 +220,8 @@ vm_action_put_handler_body!(VmSendMigration); #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] vm_action_put_handler_body!(VmCoredump); +// Special handling for virtio-net devices backed by network FDs. +// See module description for more info. impl PutHandler for VmAddNet { fn handle_request( &'static self, @@ -249,6 +278,8 @@ impl PutHandler for VmResize { impl GetHandler for VmResize {} +// Special handling for virtio-net devices backed by network FDs. +// See module description for more info. impl PutHandler for VmRestore { fn handle_request( &'static self, From 0cd87053ee1df25ab2bedf7128a0e22b6de3c45a Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 19 Jun 2025 18:41:16 +0200 Subject: [PATCH 0195/1893] vmm: document + FD (de)serialization + warn -> debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (1) The old messages are missing the "why" part. With this change, users of Cloud Hypervisor have somehow more context and people looking at the code perfectly know what's going on. (2) Using warn! implies that the user should take action, but in this case, there’s nothing the user can do. If the API is used correctly and file descriptors are passed via an SCM_RIGHTS message over a UNIX domain socket, everything works as intended. In that case, there's no need to issue a warning — debug! is sufficient. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/config.rs | 21 ++++++++++++++++----- vmm/src/vm_config.rs | 14 +++++++++----- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index d04536f60c..1d89792100 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2205,6 +2205,13 @@ pub struct RestoredNetConfig { pub id: String, #[serde(default)] pub num_fds: usize, + // Special (de)serialize handling: + // A serialize-deserialize cycle typically happens across processes. + // Therefore, we don't serialize FDs, and whatever value is here after + // deserialization is invalid. + // + // Valid FDs are transmitted via a different channel (SCM_RIGHTS message) + // and will be populated into this struct on the destination VMM eventually. #[serde( default, serialize_with = "serialize_restorednetconfig_fds", @@ -2221,9 +2228,10 @@ where S: serde::Serializer, { if let Some(x) = x { - warn!( - "'RestoredNetConfig' contains FDs that can't be serialized correctly. Serializing them as invalid FDs." - ); + // If the live-migration path is used properly, new FDs are passed as + // SCM_RIGHTS message. So, we don't get them from the serialized JSON + // anyway. + debug!("FDs in 'RestoredNetConfig' won't be serialized as they are most likely invalid after deserialization. Serializing them as -1."); let invalid_fds = vec![-1; x.len()]; s.serialize_some(&invalid_fds) } else { @@ -2239,8 +2247,11 @@ where { let invalid_fds: Option> = Option::deserialize(d)?; if let Some(invalid_fds) = invalid_fds { - warn!( - "'RestoredNetConfig' contains FDs that can't be deserialized correctly. Deserializing them as invalid FDs." + // If the live-migration path is used properly, new FDs are passed as + // SCM_RIGHTS message. So, we don't get them from the serialized JSON + // anyway. + debug!( + "FDs in 'RestoredNetConfig' won't be deserialized as they are most likely invalid now. Deserializing them as -1." ); Ok(Some(vec![-1; invalid_fds.len()])) } else { diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 76fe2d2b2a..49e3e9596a 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -324,6 +324,12 @@ pub struct NetConfig { pub vhost_mode: VhostMode, #[serde(default)] pub id: Option, + // Special (de)serialize handling: + // Therefore, we don't serialize FDs, and whatever value is here after + // deserialization is invalid. + // + // Valid FDs are transmitted via a different channel (SCM_RIGHTS message) + // and will be populated into this struct on the destination VMM eventually. #[serde( default, serialize_with = "serialize_netconfig_fds", @@ -371,9 +377,7 @@ where S: serde::Serializer, { if let Some(x) = x { - warn!( - "'NetConfig' contains FDs that can't be serialized correctly. Serializing them as invalid FDs." - ); + debug!("FDs in 'NetConfig' won't be serialized as they are most likely invalid after deserialization; using -1."); let invalid_fds = vec![-1; x.len()]; s.serialize_some(&invalid_fds) } else { @@ -387,8 +391,8 @@ where { let invalid_fds: Option> = Option::deserialize(d)?; if let Some(invalid_fds) = invalid_fds { - warn!( - "'NetConfig' contains FDs that can't be deserialized correctly. Deserializing them as invalid FDs." + debug!( + "FDs in 'NetConfig' won't be deserialized as they are most likely invalid now. Deserializing them as -1." ); Ok(Some(vec![-1; invalid_fds.len()])) } else { From ed27e93b8103fb51cc8ddf03f7ec3b1b14781c20 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 19 Jun 2025 18:26:04 +0200 Subject: [PATCH 0196/1893] vmm: remove special serialization of network FDs Deserializing values as `-1` makes sense to prevent errors, so let's keep it. However, serializing them differently adds confusion. For example, a `ch-remote info` call should not report `-1` but the actual FDs. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/config.rs | 27 ++------------------------- vmm/src/vm_config.rs | 21 ++------------------- 2 files changed, 4 insertions(+), 44 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 1d89792100..af89539f04 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2205,40 +2205,17 @@ pub struct RestoredNetConfig { pub id: String, #[serde(default)] pub num_fds: usize, - // Special (de)serialize handling: + // Special deserialize handling: // A serialize-deserialize cycle typically happens across processes. // Therefore, we don't serialize FDs, and whatever value is here after // deserialization is invalid. // // Valid FDs are transmitted via a different channel (SCM_RIGHTS message) // and will be populated into this struct on the destination VMM eventually. - #[serde( - default, - serialize_with = "serialize_restorednetconfig_fds", - deserialize_with = "deserialize_restorednetconfig_fds" - )] + #[serde(default, deserialize_with = "deserialize_restorednetconfig_fds")] pub fds: Option>, } -fn serialize_restorednetconfig_fds( - x: &Option>, - s: S, -) -> std::result::Result -where - S: serde::Serializer, -{ - if let Some(x) = x { - // If the live-migration path is used properly, new FDs are passed as - // SCM_RIGHTS message. So, we don't get them from the serialized JSON - // anyway. - debug!("FDs in 'RestoredNetConfig' won't be serialized as they are most likely invalid after deserialization. Serializing them as -1."); - let invalid_fds = vec![-1; x.len()]; - s.serialize_some(&invalid_fds) - } else { - s.serialize_none() - } -} - fn deserialize_restorednetconfig_fds<'de, D>( d: D, ) -> std::result::Result>, D::Error> diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 49e3e9596a..7021f9e9c6 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -324,17 +324,13 @@ pub struct NetConfig { pub vhost_mode: VhostMode, #[serde(default)] pub id: Option, - // Special (de)serialize handling: + // Special deserialize handling: // Therefore, we don't serialize FDs, and whatever value is here after // deserialization is invalid. // // Valid FDs are transmitted via a different channel (SCM_RIGHTS message) // and will be populated into this struct on the destination VMM eventually. - #[serde( - default, - serialize_with = "serialize_netconfig_fds", - deserialize_with = "deserialize_netconfig_fds" - )] + #[serde(default, deserialize_with = "deserialize_netconfig_fds")] pub fds: Option>, #[serde(default)] pub rate_limiter_config: Option, @@ -372,19 +368,6 @@ pub fn default_netconfig_queue_size() -> u16 { DEFAULT_NET_QUEUE_SIZE } -fn serialize_netconfig_fds(x: &Option>, s: S) -> Result -where - S: serde::Serializer, -{ - if let Some(x) = x { - debug!("FDs in 'NetConfig' won't be serialized as they are most likely invalid after deserialization; using -1."); - let invalid_fds = vec![-1; x.len()]; - s.serialize_some(&invalid_fds) - } else { - s.serialize_none() - } -} - fn deserialize_netconfig_fds<'de, D>(d: D) -> Result>, D::Error> where D: serde::Deserializer<'de>, From 452424b66ed8fe8d4ba6da92e9a02ca33392ed74 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 19 Jun 2025 19:50:56 +0200 Subject: [PATCH 0197/1893] vmm: api: abstract FD handling behind functions ## TL;DR This unifies error handling, implementation, and logging of config objects that are populated with additional FDs received as part of the HTTP API request from the management software. This prevents current and future code in repetition. In the future, if we want to support let's say virtio-blk with external FDs, the AddBlk API handler simply needs a call to `attach_fds_to_cfg()` and gets all the magic for free. ## Motivation The motivation of the new abstraction is to provide a verbose, solid, and bulletproof solution for a complex domain. The interaction between - the management layer, - the passing of file descriptors over UNIX domain sockets via SCM_RIGHTS, - the attachment of configurations to those FDs, - and the ability to give new developers clear insights into what happens under the hood is not trivial. These factors justify encapsulating the complexity behind a convenient and well-documented abstraction, making the system both robust in production and approachable for new developers. In addition, it allows us to perform unit testing. Further, We get rid of existing partial code duplication and inconsistencies. Finally, while this approach may initially result in more code, every new handler that accepts FDs benefits from reduced duplication and a correct implementation by relying on the shared abstraction. This will also enable future functionality, such as virtio-blk devices backed by FDs which can then be integrated with ease. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/api/http/http_endpoint.rs | 376 ++++++++++++++++++++++++++---- 1 file changed, 336 insertions(+), 40 deletions(-) diff --git a/vmm/src/api/http/http_endpoint.rs b/vmm/src/api/http/http_endpoint.rs index 6bda58d50e..b25b7e9ab8 100644 --- a/vmm/src/api/http/http_endpoint.rs +++ b/vmm/src/api/http/http_endpoint.rs @@ -28,11 +28,13 @@ //! We can receive these FDs as we use a [special HTTP library] that is aware //! of the described mechanism. //! +//! Please have a look into the [`fds_helper`] module for the technical +//! implementation. +//! //! [`cmsg(3)`]: https://man7.org/linux/man-pages/man3/cmsg.3.html //! [special HTTP library]: https://github.com/firecracker-microvm/micro-http use std::fs::File; -use std::os::unix::io::IntoRawFd; use std::sync::mpsc::Sender; use micro_http::{Body, Method, Request, Response, StatusCode, Version}; @@ -40,6 +42,7 @@ use vmm_sys_util::eventfd::EventFd; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::api::VmCoredump; +use crate::api::http::http_endpoint::fds_helper::{attach_fds_to_cfg, attach_fds_to_cfgs}; use crate::api::http::{EndpointHandler, HttpError, error_response}; use crate::api::{ AddDisk, ApiAction, ApiError, ApiRequest, NetConfig, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, @@ -51,6 +54,206 @@ use crate::config::RestoreConfig; use crate::cpu::Error as CpuError; use crate::vm::Error as VmError; +/// Helper module for attaching externally opened FDs to config objects. +/// +/// # Difference between [`ConfigWithFDs`] and [`ConfigWithVariableFDs`] +/// +/// The base trait [`ConfigWithFDs`] type must be implemented by all config +/// types that want to take ownership of externally provided FDs. +/// +/// In the case of restore operations, e.g., after a live-migration, config +/// objects will know the amount of FDs they need. In this case, they must +/// also implement [`ConfigWithVariableFDs`]. In other scenarios, such as +/// hot device attach, the base type is sufficient and the type will take +/// over all available FDs. +/// +/// In any case, the management software (e.g., libvirt) is responsible for +/// providing the exact amount of FDs. +mod fds_helper { + use std::fs::File; + use std::os::fd::{IntoRawFd, RawFd}; + + use crate::api::http::HttpError; + + /// Abstraction over configuration types received via the HTTP API that + /// have associated externally opened FDs. + pub trait ConfigWithFDs { + /// Returns the ID of the device. + /// + /// Used for logging. + fn id(&self) -> Option<&str>; + + /// Returns any FDs provided in the HTTP body. + /// + /// They will always be invalid and are used for user-facing logging. + fn fds_from_http_body(&self) -> Option<&[RawFd]>; + + /// Assigns the provided file descriptors (`fds`) to this configuration + /// object. + /// + /// After calling this method, the configuration will behave as if it + /// had originally been created with these FDs. Next, the configuration + /// can be used to properly configure the corresponding device. + /// + /// # Arguments + /// - `fds`: Either a non-empty Vector with corresponding FDs or `None` + /// indicating that no valid FDs were supplied. + fn set_fds(&mut self, fds: Option>); + } + + /// Extension of [`ConfigWithFDs`] for config objects that know how many + /// FDs they want (e.g., a restore configuration that is aware of the + /// previous state). + pub trait ConfigWithVariableFDs: ConfigWithFDs { + /// Returns how many FDs this type wants to have from the pool of + /// available FDs. + fn expected_num_fds(&self) -> usize; + } + + mod config_with_fds_impls { + use std::os::fd::RawFd; + + use super::{ConfigWithFDs, ConfigWithVariableFDs}; + use crate::config::RestoredNetConfig; + use crate::vm_config::NetConfig; + + impl ConfigWithFDs for NetConfig { + fn id(&self) -> Option<&str> { + self.id.as_deref() + } + + fn fds_from_http_body(&self) -> Option<&[RawFd]> { + self.fds.as_deref() + } + + fn set_fds(&mut self, fds: Option>) { + self.fds = fds; + } + } + + impl ConfigWithFDs for RestoredNetConfig { + fn id(&self) -> Option<&str> { + Some(self.id.as_str()) + } + + fn fds_from_http_body(&self) -> Option<&[RawFd]> { + self.fds.as_deref() + } + + fn set_fds(&mut self, fds: Option>) { + self.fds = fds; + } + } + + impl ConfigWithVariableFDs for RestoredNetConfig { + fn expected_num_fds(&self) -> usize { + self.num_fds + } + } + } + + fn attach_fds_to_cfg_inner( + fds: &mut Vec, + fds_amount: usize, + cfg: &mut T, + ) { + if cfg.fds_from_http_body().is_some() { + // Only FDs transmitted via an SCM_RIGHTS UNIX Domain Socket message + // are valid. Any provided over the HTTP API are set to `-1` in our + // specialized serializer callbacks. + warn!( + "FD numbers were present in HTTP request body for device {:?} but will be ignored", + cfg.id() + ); + + // Reset old value in any case; if there are FDs, they are invalid. + cfg.set_fds(None); + } + + if fds_amount > 0 { + let new_fds = fds.drain(..fds_amount).collect::>(); + log::debug!( + "Attaching network FDs received via UNIX domain socket to device: id={:?}, fds={new_fds:?}", + cfg.id() + ); + cfg.set_fds(Some(new_fds)); + } + } + + /// Applies FDs to configs for their corresponding devices, as part of the special + /// handling for devices backed by externally provided FDs. + /// + /// The FDs (via `files`) must be provided in the exact order matching the + /// config struct they belong to. + /// + /// See [module description] for more info. + /// + /// # Arguments + /// - `device_fds`: Ordered list of all FDs from the request. + /// - `cfgs`: List of network configurations where each network can have up to `n` FDs. + /// + /// [module description]: self + pub fn attach_fds_to_cfgs( + device_fds: Vec, + cfgs: &mut [&mut T], + ) -> Result<(), HttpError> { + let expected_fds: usize = cfgs.iter().map(|cfg| cfg.expected_num_fds()).sum(); + + if device_fds.len() != expected_fds { + error!( + "Number of expected FDs: {}, received: {}", + expected_fds, + device_fds.len() + ); + return Err(HttpError::BadRequest); + } + + // We are only interested in the raw FDs. After this operation, we are + // responsible for manually closing the FDs eventually. + let mut fds = device_fds + .into_iter() + .map(|f| f.into_raw_fd()) + .collect::>(); + + // For each config: We drain the FDs vector by the amount of FDs the config expects. + for cfg in cfgs { + attach_fds_to_cfg_inner(&mut fds, cfg.expected_num_fds(), *cfg); + } + + // We checked that `fds.len() == expected_fds`; so if we panic here, we + // have a hard programming error + assert!(fds.is_empty()); + + Ok(()) + } + + /// Applies FDs to the config for the corresponding device, as part of the special + /// handling for devices backed by externally provided FDs. + /// + /// See [module description] for more info. + /// + /// # Arguments + /// - `device_fds`: Ordered list of all FDs from the request. + /// - `cfg`: The config object that wants to take ownership of all available FDs. + /// + /// [module description]: self + pub fn attach_fds_to_cfg( + device_fds: Vec, + cfg: &mut T, + ) -> Result<(), HttpError> { + // We are only interested in the raw FDs. + let mut fds = device_fds + .into_iter() + .map(|f| f.into_raw_fd()) + .collect::>(); + + let len = fds.len(); + attach_fds_to_cfg_inner(&mut fds, len, cfg); + + Ok(()) + } +} + // /api/v1/vm.create handler pub struct VmCreate {} @@ -74,11 +277,18 @@ impl EndpointHandler for VmCreate { }; if let Some(ref mut nets) = vm_config.net { - if nets.iter().any(|net| net.fds.is_some()) { - warn!("Ignoring FDs sent via the HTTP request body"); - } - for net in nets { - net.fds = None; + let mut cfgs = nets.iter_mut().collect::>(); + let cfgs = cfgs.as_mut_slice(); + + // For the VmCreate call, we do not accept FDs from the socket currently. + // This call sets all FDs to null while doing the same logging as + // similar code paths. + for cfg in cfgs { + if let Err(e) = attach_fds_to_cfg(vec![], *cfg) + .map_err(|e| error_response(e, StatusCode::InternalServerError)) + { + return e; + } } } @@ -228,18 +438,12 @@ impl PutHandler for VmAddNet { api_notifier: EventFd, api_sender: Sender, body: &Option, - mut files: Vec, + files: Vec, ) -> std::result::Result, HttpError> { if let Some(body) = body { let mut net_cfg: NetConfig = serde_json::from_slice(body.raw())?; - if net_cfg.fds.is_some() { - warn!("Ignoring FDs sent via the HTTP request body"); - net_cfg.fds = None; - } - if !files.is_empty() { - let fds = files.drain(..).map(|f| f.into_raw_fd()).collect(); - net_cfg.fds = Some(fds); - } + attach_fds_to_cfg(files, &mut net_cfg)?; + self.send(api_notifier, api_sender, net_cfg) .map_err(HttpError::ApiError) } else { @@ -286,35 +490,15 @@ impl PutHandler for VmRestore { api_notifier: EventFd, api_sender: Sender, body: &Option, - mut files: Vec, + files: Vec, ) -> std::result::Result, HttpError> { if let Some(body) = body { let mut restore_cfg: RestoreConfig = serde_json::from_slice(body.raw())?; - let mut fds = Vec::new(); - if !files.is_empty() { - fds = files.drain(..).map(|f| f.into_raw_fd()).collect(); - } - let expected_fds = match restore_cfg.net_fds { - Some(ref net_fds) => net_fds.iter().map(|net| net.num_fds).sum(), - None => 0, - }; - if fds.len() != expected_fds { - error!( - "Number of FDs expected: {}, but received: {}", - expected_fds, - fds.len() - ); - return Err(HttpError::BadRequest); - } - if let Some(ref mut nets) = restore_cfg.net_fds { - warn!("Ignoring FDs sent via the HTTP request body"); - let mut start_idx = 0; - for restored_net in nets.iter_mut() { - let end_idx = start_idx + restored_net.num_fds; - restored_net.fds = Some(fds[start_idx..end_idx].to_vec()); - start_idx = end_idx; - } + if let Some(cfgs) = restore_cfg.net_fds.as_mut() { + let mut cfgs = cfgs.iter_mut().collect::>(); + let cfgs = cfgs.as_mut_slice(); + attach_fds_to_cfgs(files, cfgs)?; } self.send(api_notifier, api_sender, restore_cfg) @@ -442,3 +626,115 @@ impl EndpointHandler for VmmShutdown { } } } + +#[cfg(test)] +mod external_fds_tests { + use super::*; + use crate::api::http::http_endpoint::fds_helper::{ConfigWithFDs, ConfigWithVariableFDs}; + + struct DummyNewDeviceCfg { + http_fds: Option>, + } + + impl ConfigWithFDs for DummyNewDeviceCfg { + fn id(&self) -> Option<&str> { + Some("dummy") + } + + fn fds_from_http_body(&self) -> Option<&[i32]> { + self.http_fds.as_deref() + } + + fn set_fds(&mut self, fds: Option>) { + self.http_fds = fds; + } + } + + struct DummyRestoreDeviceCfg { + http_fds: Option>, + num_fds: usize, + } + + impl ConfigWithFDs for DummyRestoreDeviceCfg { + fn id(&self) -> Option<&str> { + Some("dummy") + } + + fn fds_from_http_body(&self) -> Option<&[i32]> { + self.http_fds.as_deref() + } + + fn set_fds(&mut self, fds: Option>) { + self.http_fds = fds; + } + } + + impl ConfigWithVariableFDs for DummyRestoreDeviceCfg { + fn expected_num_fds(&self) -> usize { + self.num_fds + } + } + + #[test] + fn test_fds_provided_via_http_api_are_reset() { + let mut config = DummyNewDeviceCfg { + http_fds: Some(vec![1, 2, 3]), + }; + + attach_fds_to_cfg(vec![], &mut config).unwrap(); + assert_eq!(config.http_fds, None); + } + + #[test] + fn test_new_device_cfg_takes_all_fds() { + let path = "/dev/null"; + + let new_fds = vec![ + File::open(path).unwrap(), + File::open(path).unwrap(), + File::open(path).unwrap(), + ]; + let mut config = DummyNewDeviceCfg { + http_fds: Some(vec![1, 2, 3]), + }; + + attach_fds_to_cfg(new_fds, &mut config).unwrap(); + assert_eq!(config.http_fds.unwrap().len(), 3); + } + + #[test] + fn test_restore_cfgs_take_only_their_fds() { + let path = "/dev/null"; + let new_fds = vec![ + File::open(path).unwrap(), + File::open(path).unwrap(), + File::open(path).unwrap(), + File::open(path).unwrap(), + File::open(path).unwrap(), + File::open(path).unwrap(), + ]; + let mut config1 = DummyRestoreDeviceCfg { + http_fds: None, + num_fds: 3, + }; + let mut config2 = DummyRestoreDeviceCfg { + http_fds: None, + num_fds: 1, + }; + let mut config3 = DummyRestoreDeviceCfg { + http_fds: None, + num_fds: 0, + }; + let mut config4 = DummyRestoreDeviceCfg { + http_fds: None, + num_fds: 2, + }; + let mut configs = [&mut config1, &mut config2, &mut config3, &mut config4]; + + attach_fds_to_cfgs(new_fds, &mut configs).unwrap(); + assert_eq!(config1.http_fds.unwrap().len(), 3); + assert_eq!(config2.http_fds.unwrap().len(), 1); + assert!(config3.http_fds.is_none()); + assert_eq!(config4.http_fds.unwrap().len(), 2); + } +} From db37ef2ad77d282505913fd2316e5a74a9af8922 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Thu, 18 Sep 2025 15:55:08 +0000 Subject: [PATCH 0198/1893] ci: Group dependabot updates to reduce noise Use a workaround [1] to group rust-vmm and non-rust-vmm updates raised by dependabot, which reduces noise from dependabot. [1] https://github.com/dependabot/dependabot-core/issues/1778#issuecomment-1988140219 Signed-off-by: Ruoqing He --- .github/dependabot.yml | 66 +++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4121e76a99..d2c0917e18 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,21 +1,67 @@ version: 2 updates: - package-ecosystem: cargo - directory: "/" + directories: + - "/" + - "/fuzz" schedule: - interval: daily - open-pull-requests-limit: 1 + interval: weekly allow: - - dependency-type: direct - - dependency-type: indirect + - dependency-name: "acpi_tables" + - dependency-name: "kvm-bindings" + - dependency-name: "kvm-ioctls" + - dependency-name: "linux-loader" + - dependency-name: "mshv-bindings" + - dependency-name: "mshv-ioctls" + - dependency-name: "seccompiler" + - dependency-name: "vfio-bindings" + - dependency-name: "vfio-ioctls" + - dependency-name: "vfio_user" + - dependency-name: "vhost" + - dependency-name: "vhost-user-backend" + - dependency-name: "virtio-bindings" + - dependency-name: "virtio-queue" + - dependency-name: "vm-fdt" + - dependency-name: "vm-memory" + - dependency-name: "vmm-sys-util" + groups: + rust-vmm: + patterns: + - "*" + target-branch: main - package-ecosystem: cargo - directory: "/fuzz" + directories: + - "/" + - "/fuzz" schedule: - interval: daily - open-pull-requests-limit: 1 + interval: weekly allow: - - dependency-type: direct - - dependency-type: indirect + - dependency-type: all + ignore: + - dependency-name: "acpi_tables" + - dependency-name: "kvm-bindings" + - dependency-name: "kvm-ioctls" + - dependency-name: "linux-loader" + - dependency-name: "mshv-bindings" + - dependency-name: "mshv-ioctls" + - dependency-name: "seccompiler" + - dependency-name: "vfio-bindings" + - dependency-name: "vfio-ioctls" + - dependency-name: "vfio_user" + - dependency-name: "vhost" + - dependency-name: "vhost-user-backend" + - dependency-name: "virtio-bindings" + - dependency-name: "virtio-queue" + - dependency-name: "vm-fdt" + - dependency-name: "vm-memory" + - dependency-name: "vmm-sys-util" + groups: + non-rust-vmm: + patterns: + - "*" + # Makes it possible to have another config for the same directory. + # https://github.com/dependabot/dependabot-core/issues/1778#issuecomment-1988140219 + target-branch: main - package-ecosystem: github-actions directory: "/" schedule: From 70c798d2b9ca2d7638399552212e180ef3c0e90e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 23:02:08 +0000 Subject: [PATCH 0199/1893] build: Bump landlock from 0.4.2 to 0.4.3 Bumps [landlock](https://github.com/landlock-lsm/rust-landlock) from 0.4.2 to 0.4.3. - [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.2...v0.4.3) --- updated-dependencies: - dependency-name: landlock dependency-version: 0.4.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- vmm/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9d515952a..4d9506a410 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1076,9 +1076,9 @@ dependencies = [ [[package]] name = "landlock" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d2ef408b88e913bfc6594f5e693d57676f6463ded7d8bf994175364320c706" +checksum = "affe8b77dce5b172f8e290bd801b12832a77cd1942d1ea98259916e89d5829d6" dependencies = [ "enumflags2", "libc", @@ -1093,9 +1093,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libredox" diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index b931193a19..4b2e3bd754 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -54,7 +54,7 @@ hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } igvm = { workspace = true, optional = true } igvm_defs = { workspace = true, optional = true } -landlock = "0.4.2" +landlock = "0.4.3" libc = { workspace = true } linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] } log = { workspace = true } From ad0f69535267479a2696acd367973e82e77c0442 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Tue, 23 Sep 2025 01:36:16 +0000 Subject: [PATCH 0200/1893] ci: Make package-ecosystem combination unique "Update configs must have a unique combination of 'package-ecosystem', 'directory', and 'target-branch'. Ecosystem 'cargo' has overlapping directories for target branch 'main'." Drop `target-branch` in rust-vmm group to make those two unique. Signed-off-by: Ruoqing He --- .github/dependabot.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d2c0917e18..2c6136600e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -28,7 +28,6 @@ updates: rust-vmm: patterns: - "*" - target-branch: main - package-ecosystem: cargo directories: - "/" From ea83fe314cbcf34d3e3838e819418d346a4169b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:32:16 +0000 Subject: [PATCH 0201/1893] build: Bump the non-rust-vmm group across 2 directories with 107 updates Bumps the non-rust-vmm group with 57 updates in the / directory: | Package | From | To | | --- | --- | --- | | [zbus](https://github.com/dbus2/zbus) | `5.7.1` | `5.11.0` | | [serde](https://github.com/serde-rs/serde) | `1.0.208` | `1.0.226` | | [serde_json](https://github.com/serde-rs/json) | `1.0.143` | `1.0.145` | | [serde_with](https://github.com/jonasbb/serde_with) | `3.14.0` | `3.14.1` | | [anyhow](https://github.com/dtolnay/anyhow) | `1.0.99` | `1.0.100` | | [cfg-if](https://github.com/rust-lang/cfg-if) | `1.0.0` | `1.0.3` | | [clap](https://github.com/clap-rs/clap) | `4.5.47` | `4.5.48` | | [log](https://github.com/rust-lang/log) | `0.4.22` | `0.4.28` | | [thiserror](https://github.com/dtolnay/thiserror) | `2.0.12` | `2.0.16` | | [zerocopy](https://github.com/google/zerocopy) | `0.8.26` | `0.8.27` | | [bitfield-struct](https://github.com/wrenger/bitfield-struct-rs) | `0.10.1` | `0.11.0` | | [num_enum](https://github.com/illicitonion/num_enum) | `0.7.3` | `0.7.4` | | [blocking](https://github.com/smol-rs/blocking) | `1.6.1` | `1.6.2` | | [gdbstub](https://github.com/daniel5151/gdbstub) | `0.7.6` | `0.7.7` | | [micro_http](https://github.com/firecracker-microvm/micro-http) | ``bf50989`` | ``3248cee`` | | [anstream](https://github.com/rust-cli/anstyle) | `0.6.19` | `0.6.20` | | [anstyle-query](https://github.com/rust-cli/anstyle) | `1.1.2` | `1.1.4` | | [anstyle-wincon](https://github.com/rust-cli/anstyle) | `3.0.6` | `3.0.10` | | [async-broadcast](https://github.com/smol-rs/async-broadcast) | `0.7.1` | `0.7.2` | | [async-executor](https://github.com/smol-rs/async-executor) | `1.13.1` | `1.13.3` | | [async-io](https://github.com/smol-rs/async-io) | `2.5.0` | `2.6.0` | | [async-lock](https://github.com/smol-rs/async-lock) | `3.4.0` | `3.4.1` | | [async-signal](https://github.com/smol-rs/async-signal) | `0.2.12` | `0.2.13` | | [bumpalo](https://github.com/fitzgen/bumpalo) | `3.16.0` | `3.19.0` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.34` | `1.2.38` | | [clap_lex](https://github.com/clap-rs/clap) | `0.7.4` | `0.7.5` | | [colorchoice](https://github.com/rust-cli/anstyle) | `1.0.3` | `1.0.4` | | [crc32fast](https://github.com/srijs/rust-crc32fast) | `1.4.2` | `1.5.0` | | [env_filter](https://github.com/rust-cli/env_logger) | `0.1.2` | `0.1.3` | | [errno](https://github.com/lambda-fairy/rust-errno) | `0.3.13` | `0.3.14` | | [event-listener-strategy](https://github.com/smol-rs/event-listener-strategy) | `0.5.3` | `0.5.4` | | [futures-lite](https://github.com/smol-rs/futures-lite) | `2.6.0` | `2.6.1` | | [glob](https://github.com/rust-lang/glob) | `0.3.2` | `0.3.3` | | [hashbrown](https://github.com/rust-lang/hashbrown) | `0.15.4` | `0.15.5` | | [indexmap](https://github.com/indexmap-rs/indexmap) | `2.8.0` | `2.11.4` | | libredox | `0.1.3` | `0.1.10` | | [libz-sys](https://github.com/rust-lang/libz-sys) | `1.1.18` | `1.1.22` | | [lock_api](https://github.com/Amanieu/parking_lot) | `0.4.12` | `0.4.13` | | [memchr](https://github.com/BurntSushi/memchr) | `2.7.2` | `2.7.5` | | [once_cell](https://github.com/matklad/once_cell) | `1.20.2` | `1.21.3` | | [openssl-src](https://github.com/alexcrichton/openssl-src-rs) | `300.3.2+3.3.2` | `300.5.2+3.5.2` | | [openssl-sys](https://github.com/sfackler/rust-openssl) | `0.9.104` | `0.9.109` | | [parking_lot](https://github.com/Amanieu/parking_lot) | `0.12.1` | `0.12.4` | | [pin-project-lite](https://github.com/taiki-e/pin-project-lite) | `0.2.15` | `0.2.16` | | [pkg-config](https://github.com/rust-lang/pkg-config-rs) | `0.3.31` | `0.3.32` | | [polling](https://github.com/smol-rs/polling) | `3.6.0` | `3.11.0` | | [ppv-lite86](https://github.com/cryptocorrosion/cryptocorrosion) | `0.2.20` | `0.2.21` | | [proc-macro-crate](https://github.com/bkchr/proc-macro-crate) | `3.3.0` | `3.4.0` | | [regex](https://github.com/rust-lang/regex) | `1.11.1` | `1.11.2` | | [regex-automata](https://github.com/rust-lang/regex) | `0.4.9` | `0.4.10` | | [regex-syntax](https://github.com/rust-lang/regex) | `0.8.5` | `0.8.6` | | [serde_repr](https://github.com/dtolnay/serde-repr) | `0.1.19` | `0.1.20` | | [tempfile](https://github.com/Stebalien/tempfile) | `3.12.0` | `3.23.0` | | [tracing](https://github.com/tokio-rs/tracing) | `0.1.40` | `0.1.41` | | [unicode-ident](https://github.com/dtolnay/unicode-ident) | `1.0.18` | `1.0.19` | | [wasi](https://github.com/bytecodealliance/wasi-rs) | `0.11.0+wasi-snapshot-preview1` | `0.11.1+wasi-snapshot-preview1` | | [zvariant_utils](https://github.com/dbus2/zbus) | `3.2.0` | `3.2.1` | Bumps the non-rust-vmm group with 44 updates in the /fuzz directory: | Package | From | To | | --- | --- | --- | | [serde](https://github.com/serde-rs/serde) | `1.0.208` | `1.0.226` | | [serde_json](https://github.com/serde-rs/json) | `1.0.143` | `1.0.145` | | [serde_with](https://github.com/jonasbb/serde_with) | `3.14.0` | `3.14.1` | | [cfg-if](https://github.com/rust-lang/cfg-if) | `1.0.0` | `1.0.3` | | [clap](https://github.com/clap-rs/clap) | `4.5.47` | `4.5.48` | | [log](https://github.com/rust-lang/log) | `0.4.22` | `0.4.28` | | [thiserror](https://github.com/dtolnay/thiserror) | `2.0.12` | `2.0.16` | | [zerocopy](https://github.com/google/zerocopy) | `0.8.26` | `0.8.27` | | [num_enum](https://github.com/illicitonion/num_enum) | `0.7.2` | `0.7.4` | | [getrandom](https://github.com/rust-random/getrandom) | `0.2.15` | `0.2.16` | | [gdbstub](https://github.com/daniel5151/gdbstub) | `0.7.6` | `0.7.7` | | [micro_http](https://github.com/firecracker-microvm/micro-http) | ``bf50989`` | ``3248cee`` | | [anstream](https://github.com/rust-cli/anstyle) | `0.6.15` | `0.6.20` | | [anstyle](https://github.com/rust-cli/anstyle) | `1.0.8` | `1.0.11` | | [anstyle-parse](https://github.com/rust-cli/anstyle) | `0.2.5` | `0.2.7` | | [anstyle-query](https://github.com/rust-cli/anstyle) | `1.1.1` | `1.1.4` | | [autocfg](https://github.com/cuviper/autocfg) | `1.3.0` | `1.5.0` | | [bumpalo](https://github.com/fitzgen/bumpalo) | `3.16.0` | `3.19.0` | | [cc](https://github.com/rust-lang/cc-rs) | `1.1.11` | `1.2.38` | | [colorchoice](https://github.com/rust-cli/anstyle) | `1.0.2` | `1.0.4` | | [enumflags2](https://github.com/meithecatte/enumflags2) | `0.7.10` | `0.7.12` | | [equivalent](https://github.com/indexmap-rs/equivalent) | `1.0.1` | `1.0.2` | | [futures-core](https://github.com/rust-lang/futures-rs) | `0.3.30` | `0.3.31` | | [futures-sink](https://github.com/rust-lang/futures-rs) | `0.3.30` | `0.3.31` | | [indexmap](https://github.com/indexmap-rs/indexmap) | `2.5.0` | `2.11.4` | | [itoa](https://github.com/dtolnay/itoa) | `1.0.11` | `1.0.15` | | [lock_api](https://github.com/Amanieu/parking_lot) | `0.4.12` | `0.4.13` | | [memchr](https://github.com/BurntSushi/memchr) | `2.7.4` | `2.7.5` | | [once_cell](https://github.com/matklad/once_cell) | `1.20.2` | `1.21.3` | | [ppv-lite86](https://github.com/cryptocorrosion/cryptocorrosion) | `0.2.20` | `0.2.21` | | [proc-macro-crate](https://github.com/bkchr/proc-macro-crate) | `3.2.0` | `3.4.0` | | [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.92` | `1.0.101` | | [quote](https://github.com/dtolnay/quote) | `1.0.37` | `1.0.40` | | [r-efi](https://github.com/r-efi/r-efi) | `5.2.0` | `5.3.0` | | [rand](https://github.com/rust-random/rand) | `0.9.0` | `0.9.2` | | [rustversion](https://github.com/dtolnay/rustversion) | `1.0.21` | `1.0.22` | | [ryu](https://github.com/dtolnay/ryu) | `1.0.18` | `1.0.20` | | [signal-hook-registry](https://github.com/vorner/signal-hook) | `1.4.2` | `1.4.6` | | [syn](https://github.com/dtolnay/syn) | `2.0.95` | `2.0.106` | | [unicode-ident](https://github.com/dtolnay/unicode-ident) | `1.0.13` | `1.0.19` | | [wasi](https://github.com/bytecodealliance/wasi-rs) | `0.11.0+wasi-snapshot-preview1` | `0.11.1+wasi-snapshot-preview1` | | [arbitrary](https://github.com/rust-fuzz/arbitrary) | `1.4.1` | `1.4.2` | | [libfuzzer-sys](https://github.com/rust-fuzz/libfuzzer) | `0.4.7` | `0.4.10` | | [jobserver](https://github.com/rust-lang/jobserver-rs) | `0.1.32` | `0.1.34` | Updates `zbus` from 5.7.1 to 5.11.0 - [Release notes](https://github.com/dbus2/zbus/releases) - [Commits](https://github.com/dbus2/zbus/compare/zbus-5.7.1...zbus-5.11.0) Updates `serde` from 1.0.208 to 1.0.226 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.208...v1.0.226) Updates `serde_json` from 1.0.143 to 1.0.145 - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.143...v1.0.145) Updates `serde_with` from 3.14.0 to 3.14.1 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.14.0...v3.14.1) Updates `anyhow` from 1.0.99 to 1.0.100 - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.99...1.0.100) Updates `cfg-if` from 1.0.0 to 1.0.3 - [Release notes](https://github.com/rust-lang/cfg-if/releases) - [Changelog](https://github.com/rust-lang/cfg-if/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/cfg-if/compare/1.0.0...v1.0.3) Updates `clap` from 4.5.47 to 4.5.48 - [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.47...clap_complete-v4.5.48) Updates `log` from 0.4.22 to 0.4.28 - [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.22...0.4.28) Updates `thiserror` from 2.0.12 to 2.0.16 - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.12...2.0.16) Updates `zerocopy` from 0.8.26 to 0.8.27 - [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.26...v0.8.27) Updates `bitfield-struct` from 0.10.1 to 0.11.0 - [Release notes](https://github.com/wrenger/bitfield-struct-rs/releases) - [Commits](https://github.com/wrenger/bitfield-struct-rs/compare/0.10.1...0.11.0) Updates `num_enum` from 0.7.3 to 0.7.4 - [Commits](https://github.com/illicitonion/num_enum/compare/0.7.3...0.7.4) Updates `blocking` from 1.6.1 to 1.6.2 - [Release notes](https://github.com/smol-rs/blocking/releases) - [Changelog](https://github.com/smol-rs/blocking/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/blocking/compare/v1.6.1...v1.6.2) Updates `gdbstub` from 0.7.6 to 0.7.7 - [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.6...0.7.7) Updates `micro_http` from `bf50989` to `3248cee` - [Commits](https://github.com/firecracker-microvm/micro-http/compare/bf5098916006912f8dd35aaa6daa5579c6c297b2...3248ceeae41461d034624b582d5d358cd6e6f89f) Updates `anstream` from 0.6.19 to 0.6.20 - [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.19...anstream-v0.6.20) Updates `anstyle-query` from 1.1.2 to 1.1.4 - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-query-v1.1.2...anstyle-query-v1.1.4) Updates `anstyle-wincon` from 3.0.6 to 3.0.10 - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-wincon-v3.0.6...anstyle-wincon-v3.0.10) Updates `async-broadcast` from 0.7.1 to 0.7.2 - [Release notes](https://github.com/smol-rs/async-broadcast/releases) - [Changelog](https://github.com/smol-rs/async-broadcast/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/async-broadcast/compare/0.7.1...0.7.2) Updates `async-executor` from 1.13.1 to 1.13.3 - [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.1...v1.13.3) Updates `async-io` from 2.5.0 to 2.6.0 - [Release notes](https://github.com/smol-rs/async-io/releases) - [Changelog](https://github.com/smol-rs/async-io/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/async-io/compare/v2.5.0...v2.6.0) Updates `async-lock` from 3.4.0 to 3.4.1 - [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.0...v3.4.1) Updates `async-signal` from 0.2.12 to 0.2.13 - [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.12...v0.2.13) Updates `bumpalo` from 3.16.0 to 3.19.0 - [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md) - [Commits](https://github.com/fitzgen/bumpalo/commits) Updates `cc` from 1.2.34 to 1.2.38 - [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.34...cc-v1.2.38) Updates `clap_builder` from 4.5.47 to 4.5.48 - [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.47...v4.5.48) Updates `clap_lex` from 0.7.4 to 0.7.5 - [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.4...clap_lex-v0.7.5) Updates `colorchoice` from 1.0.3 to 1.0.4 - [Commits](https://github.com/rust-cli/anstyle/compare/colorchoice-v1.0.3...colorchoice-v1.0.4) Updates `crc32fast` from 1.4.2 to 1.5.0 - [Commits](https://github.com/srijs/rust-crc32fast/compare/v1.4.2...v1.5.0) Updates `darling` from 0.20.10 to 0.21.3 - [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.20.10...v0.21.3) Updates `darling_core` from 0.20.10 to 0.21.3 - [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.20.10...v0.21.3) Updates `darling_macro` from 0.20.10 to 0.21.3 - [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.20.10...v0.21.3) Updates `env_filter` from 0.1.2 to 0.1.3 - [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.2...env_filter-v0.1.3) Updates `errno` from 0.3.13 to 0.3.14 - [Release notes](https://github.com/lambda-fairy/rust-errno/releases) - [Changelog](https://github.com/lambda-fairy/rust-errno/blob/main/CHANGELOG.md) - [Commits](https://github.com/lambda-fairy/rust-errno/commits) Updates `event-listener-strategy` from 0.5.3 to 0.5.4 - [Release notes](https://github.com/smol-rs/event-listener-strategy/releases) - [Changelog](https://github.com/smol-rs/event-listener-strategy/blob/main/CHANGELOG.md) - [Commits](https://github.com/smol-rs/event-listener-strategy/compare/v0.5.3...v0.5.4) Updates `futures-lite` from 2.6.0 to 2.6.1 - [Release notes](https://github.com/smol-rs/futures-lite/releases) - [Changelog](https://github.com/smol-rs/futures-lite/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/futures-lite/compare/v2.6.0...v2.6.1) Updates `glob` from 0.3.2 to 0.3.3 - [Release notes](https://github.com/rust-lang/glob/releases) - [Changelog](https://github.com/rust-lang/glob/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/glob/compare/v0.3.2...v0.3.3) Updates `hashbrown` from 0.15.4 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.15.4...v0.15.5) Updates `indexmap` from 2.8.0 to 2.11.4 - [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.8.0...2.11.4) Updates `libredox` from 0.1.3 to 0.1.10 Updates `libz-sys` from 1.1.18 to 1.1.22 - [Release notes](https://github.com/rust-lang/libz-sys/releases) - [Commits](https://github.com/rust-lang/libz-sys/compare/1.1.18...1.1.22) Updates `lock_api` from 0.4.12 to 0.4.13 - [Release notes](https://github.com/Amanieu/parking_lot/releases) - [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md) - [Commits](https://github.com/Amanieu/parking_lot/compare/lock_api-0.4.12...lock_api-v0.4.13) Updates `memchr` from 2.7.2 to 2.7.5 - [Commits](https://github.com/BurntSushi/memchr/compare/2.7.2...2.7.5) Updates `num_enum_derive` from 0.7.3 to 0.7.4 - [Commits](https://github.com/illicitonion/num_enum/compare/0.7.3...0.7.4) Updates `once_cell` from 1.20.2 to 1.21.3 - [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md) - [Commits](https://github.com/matklad/once_cell/compare/v1.20.2...v1.21.3) Updates `openssl-src` from 300.3.2+3.3.2 to 300.5.2+3.5.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.104 to 0.9.109 - [Release notes](https://github.com/sfackler/rust-openssl/releases) - [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.104...openssl-sys-v0.9.109) Updates `parking_lot` from 0.12.1 to 0.12.4 - [Release notes](https://github.com/Amanieu/parking_lot/releases) - [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md) - [Commits](https://github.com/Amanieu/parking_lot/compare/0.12.1...parking_lot-v0.12.4) Updates `parking_lot_core` from 0.9.9 to 0.9.11 - [Release notes](https://github.com/Amanieu/parking_lot/releases) - [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md) - [Commits](https://github.com/Amanieu/parking_lot/compare/core-0.9.9...parking_lot_core-v0.9.11) Updates `pin-project-lite` from 0.2.15 to 0.2.16 - [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.15...v0.2.16) Updates `pkg-config` from 0.3.31 to 0.3.32 - [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.31...0.3.32) Updates `polling` from 3.6.0 to 3.11.0 - [Release notes](https://github.com/smol-rs/polling/releases) - [Changelog](https://github.com/smol-rs/polling/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/polling/compare/v3.6.0...v3.11.0) Updates `ppv-lite86` from 0.2.20 to 0.2.21 - [Commits](https://github.com/cryptocorrosion/cryptocorrosion/commits/ppv-lite86-0.2.21) Updates `proc-macro-crate` from 3.3.0 to 3.4.0 - [Release notes](https://github.com/bkchr/proc-macro-crate/releases) - [Commits](https://github.com/bkchr/proc-macro-crate/compare/v3.3.0...v3.4.0) Updates `redox_syscall` from 0.4.1 to 0.5.17 Updates `regex` from 1.11.1 to 1.11.2 - [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.11.1...1.11.2) Updates `regex-automata` from 0.4.9 to 0.4.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-automata-0.4.9...regex-automata-0.4.10) Updates `regex-syntax` from 0.8.5 to 0.8.6 - [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.5...regex-syntax-0.8.6) Updates `serde_derive` from 1.0.208 to 1.0.226 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.208...v1.0.226) Updates `serde_repr` from 0.1.19 to 0.1.20 - [Release notes](https://github.com/dtolnay/serde-repr/releases) - [Commits](https://github.com/dtolnay/serde-repr/compare/0.1.19...0.1.20) Updates `serde_with_macros` from 3.14.0 to 3.14.1 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.14.0...v3.14.1) Updates `tempfile` from 3.12.0 to 3.23.0 - [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md) - [Commits](https://github.com/Stebalien/tempfile/compare/v3.12.0...v3.23.0) Updates `toml_datetime` from 0.6.11 to 0.7.2 - [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.6.11...toml_datetime-v0.7.2) Updates `toml_edit` from 0.22.24 to 0.23.6 - [Commits](https://github.com/toml-rs/toml/compare/v0.22.24...v0.23.6) Updates `tracing` from 0.1.40 to 0.1.41 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.40...tracing-0.1.41) Updates `tracing-attributes` from 0.1.27 to 0.1.30 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-attributes-0.1.27...tracing-attributes-0.1.30) Updates `tracing-core` from 0.1.32 to 0.1.34 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-core-0.1.32...tracing-core-0.1.34) Updates `unicode-ident` from 1.0.18 to 1.0.19 - [Release notes](https://github.com/dtolnay/unicode-ident/releases) - [Commits](https://github.com/dtolnay/unicode-ident/compare/1.0.18...1.0.19) Updates `wasi` from 0.11.0+wasi-snapshot-preview1 to 0.11.1+wasi-snapshot-preview1 - [Commits](https://github.com/bytecodealliance/wasi-rs/compare/0.11.0...0.11.1) Updates `windows-sys` from 0.52.0 to 0.59.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/compare/0.52.0...0.59.0) Updates `windows-targets` from 0.48.5 to 0.52.6 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_gnullvm` from 0.48.5 to 0.52.6 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_msvc` from 0.48.5 to 0.52.6 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_gnu` from 0.48.5 to 0.52.6 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_msvc` from 0.48.5 to 0.52.6 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnu` from 0.48.5 to 0.52.6 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnullvm` from 0.48.5 to 0.52.6 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_msvc` from 0.48.5 to 0.52.6 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `winnow` from 0.7.2 to 0.7.13 - [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md) - [Commits](https://github.com/winnow-rs/winnow/compare/v0.7.2...v0.7.13) Updates `zbus_macros` from 5.7.1 to 5.11.0 - [Release notes](https://github.com/dbus2/zbus/releases) - [Commits](https://github.com/dbus2/zbus/compare/zbus-5.7.1...zbus-5.11.0) Updates `zerocopy-derive` from 0.7.35 to 0.8.27 - [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.7.35...v0.8.27) Updates `zvariant_utils` from 3.2.0 to 3.2.1 - [Release notes](https://github.com/dbus2/zbus/releases) - [Commits](https://github.com/dbus2/zbus/compare/zvariant_utils-3.2.0...zvariant-3.2.1) Updates `serde` from 1.0.208 to 1.0.226 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.208...v1.0.226) Updates `serde_json` from 1.0.143 to 1.0.145 - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.143...v1.0.145) Updates `serde_with` from 3.14.0 to 3.14.1 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.14.0...v3.14.1) Updates `anyhow` from 1.0.98 to 1.0.100 - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.99...1.0.100) Updates `cfg-if` from 1.0.0 to 1.0.3 - [Release notes](https://github.com/rust-lang/cfg-if/releases) - [Changelog](https://github.com/rust-lang/cfg-if/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/cfg-if/compare/1.0.0...v1.0.3) Updates `clap` from 4.5.47 to 4.5.48 - [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.47...clap_complete-v4.5.48) Updates `epoll` from 4.3.3 to 4.4.0 - [Release notes](https://github.com/nathansizemore/epoll/releases) - [Commits](https://github.com/nathansizemore/epoll/compare/4.3.3...4.4.0) Updates `libc` from 0.2.169 to 0.2.175 - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.175/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.169...0.2.175) Updates `log` from 0.4.22 to 0.4.28 - [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.22...0.4.28) Updates `thiserror` from 2.0.12 to 2.0.16 - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.12...2.0.16) Updates `zerocopy` from 0.8.26 to 0.8.27 - [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.26...v0.8.27) Updates `num_enum` from 0.7.2 to 0.7.4 - [Commits](https://github.com/illicitonion/num_enum/compare/0.7.3...0.7.4) Updates `getrandom` from 0.2.15 to 0.2.16 - [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-random/getrandom/compare/v0.2.15...v0.2.16) Updates `gdbstub` from 0.7.6 to 0.7.7 - [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.6...0.7.7) Updates `landlock` from 0.4.2 to 0.4.3 - [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.2...v0.4.3) Updates `micro_http` from `bf50989` to `3248cee` - [Commits](https://github.com/firecracker-microvm/micro-http/compare/bf5098916006912f8dd35aaa6daa5579c6c297b2...3248ceeae41461d034624b582d5d358cd6e6f89f) Updates `anstream` from 0.6.15 to 0.6.20 - [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.19...anstream-v0.6.20) Updates `anstyle` from 1.0.8 to 1.0.11 - [Commits](https://github.com/rust-cli/anstyle/compare/v1.0.8...v1.0.11) Updates `anstyle-parse` from 0.2.5 to 0.2.7 - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-parse-v0.2.5...anstyle-parse-v0.2.7) Updates `anstyle-query` from 1.1.1 to 1.1.4 - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-query-v1.1.2...anstyle-query-v1.1.4) Updates `anstyle-wincon` from 3.0.4 to 3.0.10 - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-wincon-v3.0.6...anstyle-wincon-v3.0.10) Updates `autocfg` from 1.3.0 to 1.5.0 - [Commits](https://github.com/cuviper/autocfg/compare/1.3.0...1.5.0) Updates `bumpalo` from 3.16.0 to 3.19.0 - [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md) - [Commits](https://github.com/fitzgen/bumpalo/commits) Updates `cc` from 1.1.11 to 1.2.38 - [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.34...cc-v1.2.38) Updates `clap_builder` from 4.5.47 to 4.5.48 - [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.47...v4.5.48) Updates `colorchoice` from 1.0.2 to 1.0.4 - [Commits](https://github.com/rust-cli/anstyle/compare/colorchoice-v1.0.3...colorchoice-v1.0.4) Updates `darling` from 0.20.10 to 0.21.3 - [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.20.10...v0.21.3) Updates `darling_core` from 0.20.10 to 0.21.3 - [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.20.10...v0.21.3) Updates `darling_macro` from 0.20.10 to 0.21.3 - [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.20.10...v0.21.3) Updates `enumflags2` from 0.7.10 to 0.7.12 - [Release notes](https://github.com/meithecatte/enumflags2/releases) - [Commits](https://github.com/meithecatte/enumflags2/compare/v0.7.10...v0.7.12) Updates `enumflags2_derive` from 0.7.10 to 0.7.12 - [Release notes](https://github.com/meithecatte/enumflags2/releases) - [Commits](https://github.com/meithecatte/enumflags2/compare/v0.7.10...v0.7.12) Updates `equivalent` from 1.0.1 to 1.0.2 - [Commits](https://github.com/indexmap-rs/equivalent/compare/v1.0.1...v1.0.2) Updates `futures-core` from 0.3.30 to 0.3.31 - [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.30...0.3.31) Updates `futures-sink` from 0.3.30 to 0.3.31 - [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.30...0.3.31) Updates `indexmap` from 2.5.0 to 2.11.4 - [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.8.0...2.11.4) Updates `itoa` from 1.0.11 to 1.0.15 - [Release notes](https://github.com/dtolnay/itoa/releases) - [Commits](https://github.com/dtolnay/itoa/compare/1.0.11...1.0.15) Updates `lock_api` from 0.4.12 to 0.4.13 - [Release notes](https://github.com/Amanieu/parking_lot/releases) - [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md) - [Commits](https://github.com/Amanieu/parking_lot/compare/lock_api-0.4.12...lock_api-v0.4.13) Updates `memchr` from 2.7.4 to 2.7.5 - [Commits](https://github.com/BurntSushi/memchr/compare/2.7.2...2.7.5) Updates `num_enum_derive` from 0.7.2 to 0.7.4 - [Commits](https://github.com/illicitonion/num_enum/compare/0.7.3...0.7.4) Updates `once_cell` from 1.20.2 to 1.21.3 - [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md) - [Commits](https://github.com/matklad/once_cell/compare/v1.20.2...v1.21.3) Updates `ppv-lite86` from 0.2.20 to 0.2.21 - [Commits](https://github.com/cryptocorrosion/cryptocorrosion/commits/ppv-lite86-0.2.21) Updates `proc-macro-crate` from 3.2.0 to 3.4.0 - [Release notes](https://github.com/bkchr/proc-macro-crate/releases) - [Commits](https://github.com/bkchr/proc-macro-crate/compare/v3.3.0...v3.4.0) Updates `proc-macro2` from 1.0.92 to 1.0.101 - [Release notes](https://github.com/dtolnay/proc-macro2/releases) - [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.92...1.0.101) Updates `quote` from 1.0.37 to 1.0.40 - [Release notes](https://github.com/dtolnay/quote/releases) - [Commits](https://github.com/dtolnay/quote/compare/1.0.37...1.0.40) Updates `r-efi` from 5.2.0 to 5.3.0 - [Release notes](https://github.com/r-efi/r-efi/releases) - [Changelog](https://github.com/r-efi/r-efi/blob/main/NEWS.md) - [Commits](https://github.com/r-efi/r-efi/compare/v5.2.0...v5.3.0) Updates `rand` from 0.9.0 to 0.9.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.9.0...rand_core-0.9.2) Updates `rustversion` from 1.0.21 to 1.0.22 - [Release notes](https://github.com/dtolnay/rustversion/releases) - [Commits](https://github.com/dtolnay/rustversion/compare/1.0.21...1.0.22) Updates `ryu` from 1.0.18 to 1.0.20 - [Release notes](https://github.com/dtolnay/ryu/releases) - [Commits](https://github.com/dtolnay/ryu/compare/1.0.18...1.0.20) Updates `serde_derive` from 1.0.208 to 1.0.226 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.208...v1.0.226) Updates `serde_with_macros` from 3.14.0 to 3.14.1 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.14.0...v3.14.1) Updates `signal-hook-registry` from 1.4.2 to 1.4.6 - [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md) - [Commits](https://github.com/vorner/signal-hook/compare/registry-v1.4.2...registry-v1.4.6) Updates `syn` from 2.0.95 to 2.0.106 - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.95...2.0.106) Updates `toml_datetime` from 0.6.8 to 0.7.2 - [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.6.11...toml_datetime-v0.7.2) Updates `toml_edit` from 0.22.22 to 0.23.6 - [Commits](https://github.com/toml-rs/toml/compare/v0.22.24...v0.23.6) Updates `unicode-ident` from 1.0.13 to 1.0.19 - [Release notes](https://github.com/dtolnay/unicode-ident/releases) - [Commits](https://github.com/dtolnay/unicode-ident/compare/1.0.18...1.0.19) Updates `wasi` from 0.11.0+wasi-snapshot-preview1 to 0.11.1+wasi-snapshot-preview1 - [Commits](https://github.com/bytecodealliance/wasi-rs/compare/0.11.0...0.11.1) Updates `windows-sys` from 0.52.0 to 0.60.2 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/compare/0.52.0...0.59.0) Updates `windows-targets` from 0.52.6 to 0.53.3 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_gnullvm` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_msvc` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_gnu` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_gnullvm` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits/0.53.0) Updates `windows_i686_msvc` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnu` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnullvm` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_msvc` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `winnow` from 0.6.19 to 0.7.13 - [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md) - [Commits](https://github.com/winnow-rs/winnow/compare/v0.7.2...v0.7.13) Updates `zerocopy-derive` from 0.7.35 to 0.8.27 - [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.7.35...v0.8.27) Updates `arbitrary` from 1.4.1 to 1.4.2 - [Changelog](https://github.com/rust-fuzz/arbitrary/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-fuzz/arbitrary/compare/v1.4.1...v1.4.2) Updates `libfuzzer-sys` from 0.4.7 to 0.4.10 - [Changelog](https://github.com/rust-fuzz/libfuzzer/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-fuzz/libfuzzer/compare/0.4.7...0.4.10) Updates `jobserver` from 0.1.32 to 0.1.34 - [Commits](https://github.com/rust-lang/jobserver-rs/compare/0.1.32...0.1.34) --- updated-dependencies: - dependency-name: zbus dependency-version: 5.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: serde dependency-version: 1.0.226 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_json dependency-version: 1.0.145 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with dependency-version: 3.14.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anyhow dependency-version: 1.0.100 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cfg-if dependency-version: 1.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap dependency-version: 4.5.48 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: log dependency-version: 0.4.28 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: thiserror dependency-version: 2.0.16 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: zerocopy dependency-version: 0.8.27 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: bitfield-struct dependency-version: 0.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: num_enum dependency-version: 0.7.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: blocking dependency-version: 1.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: gdbstub dependency-version: 0.7.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: micro_http dependency-version: 3248ceeae41461d034624b582d5d358cd6e6f89f dependency-type: direct:production dependency-group: non-rust-vmm - dependency-name: anstream dependency-version: 0.6.20 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle-query dependency-version: 1.1.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle-wincon dependency-version: 3.0.10 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: async-broadcast dependency-version: 0.7.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: async-executor dependency-version: 1.13.3 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: async-io dependency-version: 2.6.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: async-lock dependency-version: 3.4.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: async-signal dependency-version: 0.2.13 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: bumpalo dependency-version: 3.19.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.38 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap_builder dependency-version: 4.5.48 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap_lex dependency-version: 0.7.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: colorchoice dependency-version: 1.0.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: crc32fast dependency-version: 1.5.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: darling dependency-version: 0.21.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: darling_core dependency-version: 0.21.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: darling_macro dependency-version: 0.21.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: env_filter dependency-version: 0.1.3 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: errno dependency-version: 0.3.14 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: event-listener-strategy dependency-version: 0.5.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: futures-lite dependency-version: 2.6.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: glob dependency-version: 0.3.3 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-patch dependency-group: non-rust-vmm - dependency-name: indexmap dependency-version: 2.11.4 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: libredox dependency-version: 0.1.10 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: libz-sys dependency-version: 1.1.22 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: lock_api dependency-version: 0.4.13 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: memchr dependency-version: 2.7.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: num_enum_derive dependency-version: 0.7.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: once_cell dependency-version: 1.21.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: openssl-src dependency-version: 300.5.2+3.5.2 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: openssl-sys dependency-version: 0.9.109 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: parking_lot dependency-version: 0.12.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: parking_lot_core dependency-version: 0.9.11 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: pin-project-lite dependency-version: 0.2.16 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: pkg-config dependency-version: 0.3.32 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: polling dependency-version: 3.11.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: ppv-lite86 dependency-version: 0.2.21 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: proc-macro-crate dependency-version: 3.4.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: redox_syscall dependency-version: 0.5.17 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: regex dependency-version: 1.11.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: regex-automata dependency-version: 0.4.10 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: regex-syntax dependency-version: 0.8.6 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_derive dependency-version: 1.0.226 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_repr dependency-version: 0.1.20 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with_macros dependency-version: 3.14.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: tempfile dependency-version: 3.23.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: toml_datetime dependency-version: 0.7.2 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: toml_edit dependency-version: 0.23.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: tracing dependency-version: 0.1.41 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: tracing-attributes dependency-version: 0.1.30 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: tracing-core dependency-version: 0.1.34 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: unicode-ident dependency-version: 1.0.19 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: wasi dependency-version: 0.11.1+wasi-snapshot-preview1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows-sys dependency-version: 0.59.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows-targets dependency-version: 0.52.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_aarch64_gnullvm dependency-version: 0.52.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_aarch64_msvc dependency-version: 0.52.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_i686_gnu dependency-version: 0.52.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_i686_msvc dependency-version: 0.52.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnu dependency-version: 0.52.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnullvm dependency-version: 0.52.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_x86_64_msvc dependency-version: 0.52.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: winnow dependency-version: 0.7.13 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: zbus_macros dependency-version: 5.11.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: zerocopy-derive dependency-version: 0.8.27 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: zvariant_utils dependency-version: 3.2.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde dependency-version: 1.0.226 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_json dependency-version: 1.0.145 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with dependency-version: 3.14.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anyhow dependency-version: 1.0.100 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cfg-if dependency-version: 1.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap dependency-version: 4.5.48 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: epoll dependency-version: 4.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: libc dependency-version: 0.2.175 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: log dependency-version: 0.4.28 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: thiserror dependency-version: 2.0.16 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: zerocopy dependency-version: 0.8.27 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: num_enum dependency-version: 0.7.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: getrandom dependency-version: 0.2.16 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: gdbstub dependency-version: 0.7.7 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: landlock dependency-version: 0.4.3 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: micro_http dependency-version: 3248ceeae41461d034624b582d5d358cd6e6f89f dependency-type: direct:production dependency-group: non-rust-vmm - dependency-name: anstream dependency-version: 0.6.20 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle dependency-version: 1.0.11 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle-parse dependency-version: 0.2.7 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle-query dependency-version: 1.1.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle-wincon dependency-version: 3.0.10 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: autocfg dependency-version: 1.5.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: bumpalo dependency-version: 3.19.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.38 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: clap_builder dependency-version: 4.5.48 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: colorchoice dependency-version: 1.0.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: darling dependency-version: 0.21.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: darling_core dependency-version: 0.21.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: darling_macro dependency-version: 0.21.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: enumflags2 dependency-version: 0.7.12 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: enumflags2_derive dependency-version: 0.7.12 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: equivalent dependency-version: 1.0.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: futures-core dependency-version: 0.3.31 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: futures-sink dependency-version: 0.3.31 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: indexmap dependency-version: 2.11.4 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: itoa dependency-version: 1.0.15 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: lock_api dependency-version: 0.4.13 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: memchr dependency-version: 2.7.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: num_enum_derive dependency-version: 0.7.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: once_cell dependency-version: 1.21.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: ppv-lite86 dependency-version: 0.2.21 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: proc-macro-crate dependency-version: 3.4.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: proc-macro2 dependency-version: 1.0.101 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: quote dependency-version: 1.0.40 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: r-efi dependency-version: 5.3.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: rand dependency-version: 0.9.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: rustversion dependency-version: 1.0.22 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: ryu dependency-version: 1.0.20 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_derive dependency-version: 1.0.226 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with_macros dependency-version: 3.14.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: signal-hook-registry dependency-version: 1.4.6 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: syn dependency-version: 2.0.106 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: toml_datetime dependency-version: 0.7.2 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: toml_edit dependency-version: 0.23.6 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: unicode-ident dependency-version: 1.0.19 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: wasi dependency-version: 0.11.1+wasi-snapshot-preview1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows-sys dependency-version: 0.60.2 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows-targets dependency-version: 0.53.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_aarch64_gnullvm dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_aarch64_msvc dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_i686_gnu dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_i686_gnullvm dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_i686_msvc dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnu dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnullvm dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_x86_64_msvc dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: winnow dependency-version: 0.7.13 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: zerocopy-derive dependency-version: 0.8.27 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: arbitrary dependency-version: 1.4.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: libfuzzer-sys dependency-version: 0.4.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: jobserver dependency-version: 0.1.34 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm ... Signed-off-by: dependabot[bot] --- Cargo.lock | 574 ++++++++++++++++++++---------------------- Cargo.toml | 16 +- devices/Cargo.toml | 6 +- fuzz/Cargo.lock | 413 ++++++++++++++++-------------- fuzz/Cargo.toml | 4 +- hypervisor/Cargo.toml | 2 +- vmm/Cargo.toml | 6 +- 7 files changed, 502 insertions(+), 519 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d9506a410..2cb893abb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,7 +7,7 @@ name = "acpi_tables" version = "0.1.0" source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#e08a3f0b0a59b98859dbf59f5aa7fd4d2eb4018a" dependencies = [ - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -36,9 +36,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -66,34 +66,35 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "api_client" version = "0.1.0" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.16", "vmm-sys-util", ] @@ -115,7 +116,7 @@ dependencies = [ "linux-loader", "log", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "uuid", "vm-fdt", "vm-memory", @@ -124,9 +125,9 @@ dependencies = [ [[package]] name = "async-broadcast" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ "event-listener", "event-listener-strategy", @@ -148,40 +149,41 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.1" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", + "pin-project-lite", "slab", ] [[package]] name = "async-io" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ - "async-lock", + "autocfg", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", - "rustix 1.0.7", + "rustix", "slab", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ "event-listener", "event-listener-strategy", @@ -203,7 +205,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.0.7", + "rustix", ] [[package]] @@ -219,9 +221,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" dependencies = [ "async-io", "async-lock", @@ -229,10 +231,10 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.0.7", + "rustix", "signal-hook-registry", "slab", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -290,6 +292,17 @@ dependencies = [ "syn", ] +[[package]] +name = "bitfield-struct" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -314,7 +327,7 @@ dependencies = [ "remain", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.16", "uuid", "virtio-bindings", "virtio-queue", @@ -325,9 +338,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ "async-channel", "async-task", @@ -338,9 +351,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" @@ -350,18 +363,19 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.34" +version = "1.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -371,18 +385,18 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -393,9 +407,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cloud-hypervisor" @@ -418,7 +432,7 @@ dependencies = [ "serde_json", "signal-hook", "test_infra", - "thiserror 2.0.12", + "thiserror 2.0.16", "tpm", "tracer", "vm-memory", @@ -430,9 +444,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "concat-idents" @@ -464,9 +478,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -479,9 +493,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "darling" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ "darling_core", "darling_macro", @@ -489,9 +503,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", @@ -503,9 +517,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", @@ -525,7 +539,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitfield-struct", + "bitfield-struct 0.11.0", "bitflags 2.9.4", "byteorder", "event_monitor", @@ -536,14 +550,14 @@ dependencies = [ "num_enum", "pci", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "tpm", "vm-allocator", "vm-device", "vm-memory", "vm-migration", "vmm-sys-util", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -580,7 +594,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -612,9 +626,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -651,12 +665,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -672,9 +686,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", @@ -702,6 +716,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + [[package]] name = "flume" version = "0.11.1" @@ -770,9 +790,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -824,9 +844,9 @@ dependencies = [ [[package]] name = "gdbstub" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d66e32caf5dd59f561be0143e413e01d651bd8498eb9aa0be8c482c81c8d31" +checksum = "b686b198dfaa4109ebd0443d2841bc521e4b4b2915f1d84b3bb50332a8cdc1ae" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -855,7 +875,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -879,21 +899,21 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -907,7 +927,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arc-swap", - "bitfield-struct", + "bitfield-struct 0.11.0", "byteorder", "cfg-if", "concat-idents", @@ -925,11 +945,11 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-ioctls", "vm-memory", "vmm-sys-util", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -952,16 +972,16 @@ name = "igvm" version = "0.4.0" source = "git+https://github.com/microsoft/igvm?branch=main#9b67b6fa774f4a842718c5c70645973fe73ef616" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.10.1", "crc32fast", "hex", "igvm_defs", "open-enum", "range_map_vec", "static_assertions", - "thiserror 2.0.12", + "thiserror 2.0.16", "tracing", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -969,17 +989,17 @@ name = "igvm_defs" version = "0.4.0" source = "git+https://github.com/microsoft/igvm?branch=main#9b67b6fa774f4a842718c5c70645973fe73ef616" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.10.1", "open-enum", "static_assertions", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] name = "indexmap" -version = "2.8.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -1059,7 +1079,7 @@ checksum = "d4b153a59bb3ca930ff8148655b2ef68c34259a623ae08cf2fb9b570b2e45363" dependencies = [ "serde", "vmm-sys-util", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -1082,7 +1102,7 @@ checksum = "affe8b77dce5b172f8e290bd801b12832a77cd1942d1ea98259916e89d5829d6" dependencies = [ "enumflags2", "libc", - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] @@ -1099,9 +1119,9 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", @@ -1123,9 +1143,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.18" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", @@ -1141,12 +1161,6 @@ dependencies = [ "vm-memory", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1155,9 +1169,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1165,9 +1179,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "managed" @@ -1177,9 +1191,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" @@ -1193,7 +1207,7 @@ dependencies = [ [[package]] name = "micro_http" version = "0.1.0" -source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#bf5098916006912f8dd35aaa6daa5579c6c297b2" +source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#3248ceeae41461d034624b582d5d358cd6e6f89f" dependencies = [ "libc", "vmm-sys-util", @@ -1225,7 +1239,7 @@ dependencies = [ "serde", "serde_derive", "vmm-sys-util", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -1236,7 +1250,7 @@ checksum = "aefaab4c067cf5226a917227640d835327b25b71a8d465f815f74f490344e10a" dependencies = [ "libc", "mshv-bindings", - "thiserror 2.0.12", + "thiserror 2.0.16", "vmm-sys-util", ] @@ -1270,7 +1284,7 @@ dependencies = [ "rate_limiter", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "virtio-bindings", "virtio-queue", "vm-memory", @@ -1308,18 +1322,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1338,9 +1353,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "open-enum" @@ -1364,18 +1385,18 @@ dependencies = [ [[package]] name = "openssl-src" -version = "300.3.2+3.3.2" +version = "300.5.2+3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" +checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -1394,7 +1415,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" name = "option_parser" version = "0.1.0" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] @@ -1415,9 +1436,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1425,15 +1446,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1452,7 +1473,7 @@ dependencies = [ "libc", "log", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-bindings", "vfio-ioctls", "vfio_user", @@ -1472,14 +1493,14 @@ dependencies = [ "serde", "serde_json", "test_infra", - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1500,9 +1521,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "pnet" @@ -1597,17 +1618,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.6.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 0.38.44", - "tracing", - "windows-sys 0.52.0", + "rustix", + "windows-sys 0.61.0", ] [[package]] @@ -1627,18 +1647,18 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] @@ -1709,17 +1729,17 @@ dependencies = [ "epoll", "libc", "log", - "thiserror 2.0.12", + "thiserror 2.0.16", "vmm-sys-util", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.4", ] [[package]] @@ -1730,14 +1750,14 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -1747,9 +1767,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -1758,9 +1778,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "remain" @@ -1785,19 +1805,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.4", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.0.7" @@ -1807,7 +1814,7 @@ dependencies = [ "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "windows-sys 0.59.0", ] @@ -1840,18 +1847,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", @@ -1860,21 +1877,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", @@ -1883,9 +1901,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" dependencies = [ "serde", "serde_derive", @@ -1894,9 +1912,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" dependencies = [ "darling", "proc-macro2", @@ -1991,15 +2009,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.3", "once_cell", - "rustix 0.38.44", - "windows-sys 0.59.0", + "rustix", + "windows-sys 0.61.0", ] [[package]] @@ -2008,7 +2026,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.0.7", + "rustix", "windows-sys 0.60.2", ] @@ -2021,7 +2039,7 @@ dependencies = [ "libc", "serde_json", "ssh2", - "thiserror 2.0.12", + "thiserror 2.0.16", "vmm-sys-util", "wait-timeout", ] @@ -2037,11 +2055,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] @@ -2057,9 +2075,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -2074,18 +2092,31 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap", "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ "winnow", ] @@ -2097,7 +2128,7 @@ dependencies = [ "libc", "log", "net_gen", - "thiserror 2.0.12", + "thiserror 2.0.16", "vmm-sys-util", ] @@ -2113,9 +2144,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2124,9 +2155,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -2135,9 +2166,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] @@ -2155,9 +2186,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "utf8parse" @@ -2205,7 +2236,7 @@ dependencies = [ "log", "mshv-bindings", "mshv-ioctls", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2223,7 +2254,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2267,7 +2298,7 @@ dependencies = [ "libc", "log", "option_parser", - "thiserror 2.0.12", + "thiserror 2.0.16", "vhost", "vhost-user-backend", "virtio-bindings", @@ -2287,7 +2318,7 @@ dependencies = [ "log", "net_util", "option_parser", - "thiserror 2.0.12", + "thiserror 2.0.16", "vhost", "vhost-user-backend", "virtio-bindings", @@ -2320,7 +2351,7 @@ dependencies = [ "serde", "serde_with", "serial_buffer", - "thiserror 2.0.12", + "thiserror 2.0.16", "vhost", "virtio-bindings", "virtio-queue", @@ -2359,7 +2390,7 @@ version = "0.1.0" dependencies = [ "hypervisor", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -2389,7 +2420,7 @@ dependencies = [ "anyhow", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "vm-memory", ] @@ -2441,7 +2472,7 @@ dependencies = [ "serde_json", "serial_buffer", "signal-hook", - "thiserror 2.0.12", + "thiserror 2.0.16", "tracer", "uuid", "vfio-ioctls", @@ -2456,7 +2487,7 @@ dependencies = [ "vm-virtio", "vmm-sys-util", "zbus", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -2482,9 +2513,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -2576,13 +2607,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-link" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-sys" @@ -2603,18 +2631,12 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", ] [[package]] @@ -2649,12 +2671,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -2667,12 +2683,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -2685,12 +2695,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2715,12 +2719,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -2733,12 +2731,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -2751,12 +2743,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -2769,12 +2755,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2789,9 +2769,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.2" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -2807,9 +2787,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.7.1" +version = "5.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68" +checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" dependencies = [ "async-broadcast", "async-executor", @@ -2831,7 +2811,7 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "windows-sys 0.59.0", + "windows-sys 0.60.2", "winnow", "zbus_macros", "zbus_names", @@ -2840,9 +2820,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.7.1" +version = "5.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a" +checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2867,39 +2847,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive 0.8.26", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -2935,14 +2894,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" dependencies = [ "proc-macro2", "quote", "serde", - "static_assertions", "syn", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 266379ae28..a64b8c3c90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,25 +131,25 @@ igvm = { git = "https://github.com/microsoft/igvm", branch = "main" } igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates -serde = "1.0.208" -serde_json = "1.0.143" -serde_with = { version = "3.14.0", default-features = false } +serde = "1.0.226" +serde_json = "1.0.145" +serde_with = { version = "3.14.1", default-features = false } # other crates anyhow = "1.0.99" bitflags = "2.9.4" byteorder = "1.5.0" -cfg-if = "1.0.0" -clap = "4.5.47" +cfg-if = "1.0.3" +clap = "4.5.48" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" epoll = "4.4.0" flume = "0.11.1" libc = "0.2.167" -log = "0.4.22" +log = "0.4.28" signal-hook = "0.3.18" -thiserror = "2.0.12" +thiserror = "2.0.16" uuid = { version = "1.18.1" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.26", default-features = false } +zerocopy = { version = "0.8.27", default-features = false } diff --git a/devices/Cargo.toml b/devices/Cargo.toml index bf620eca90..a15030870b 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -8,7 +8,7 @@ version = "0.1.0" acpi_tables = { workspace = true } anyhow = { workspace = true } arch = { path = "../arch" } -bitfield-struct = { version = "0.10.1", optional = true } +bitfield-struct = { version = "0.11.0", optional = true } bitflags = { workspace = true } byteorder = { workspace = true } event_monitor = { path = "../event_monitor" } @@ -20,7 +20,7 @@ linux-loader = { workspace = true, features = [ "pe", ], optional = true } log = { workspace = true } -num_enum = "0.7.2" +num_enum = "0.7.4" pci = { path = "../pci" } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } @@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.26", features = [ +zerocopy = { version = "0.8.27", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 1750a54b58..32cfbd990e 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -7,14 +7,14 @@ name = "acpi_tables" version = "0.1.0" source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#e08a3f0b0a59b98859dbf59f5aa7fd4d2eb4018a" dependencies = [ - "zerocopy 0.8.26", + "zerocopy", ] [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -27,49 +27,50 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", + "once_cell_polyfill", "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" @@ -89,7 +90,7 @@ dependencies = [ "linux-loader", "log", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "uuid", "vm-fdt", "vm-memory", @@ -98,9 +99,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitfield-struct" @@ -136,7 +137,7 @@ dependencies = [ "remain", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.16", "uuid", "virtio-bindings", "virtio-queue", @@ -147,9 +148,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" @@ -159,10 +160,11 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.1.11" +version = "1.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb8dd288a69fc53a1996d7ecfbf4a20d59065bff137ce7e56bbd620de191189" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -170,24 +172,24 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -229,9 +231,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "concat-idents" @@ -254,9 +256,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ "darling_core", "darling_macro", @@ -264,9 +266,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", @@ -278,9 +280,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", @@ -309,7 +311,7 @@ dependencies = [ "num_enum", "pci", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "tpm", "vm-allocator", "vm-device", @@ -320,18 +322,18 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", @@ -340,9 +342,9 @@ dependencies = [ [[package]] name = "epoll" -version = "4.3.3" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" +checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e" dependencies = [ "bitflags 2.9.4", "libc", @@ -350,9 +352,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "event_monitor" @@ -370,6 +372,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + [[package]] name = "flume" version = "0.11.1" @@ -390,21 +398,21 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "gdbstub" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d66e32caf5dd59f561be0143e413e01d651bd8498eb9aa0be8c482c81c8d31" +checksum = "b686b198dfaa4109ebd0443d2841bc521e4b4b2915f1d84b3bb50332a8cdc1ae" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -426,14 +434,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -451,9 +459,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hypervisor" @@ -475,11 +483,11 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-ioctls", "vm-memory", "vmm-sys-util", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -499,9 +507,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.5.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -515,16 +523,17 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.3", "libc", ] @@ -546,7 +555,7 @@ checksum = "d4b153a59bb3ca930ff8148655b2ef68c34259a623ae08cf2fb9b570b2e45363" dependencies = [ "serde", "vmm-sys-util", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -563,13 +572,13 @@ dependencies = [ [[package]] name = "landlock" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d2ef408b88e913bfc6594f5e693d57676f6463ded7d8bf994175364320c706" +checksum = "affe8b77dce5b172f8e290bd801b12832a77cd1942d1ea98259916e89d5829d6" dependencies = [ "enumflags2", "libc", - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] @@ -580,19 +589,18 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libfuzzer-sys" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" dependencies = [ "arbitrary", "cc", - "once_cell", ] [[package]] @@ -605,9 +613,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -615,9 +623,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "managed" @@ -627,14 +635,14 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "micro_http" version = "0.1.0" -source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#bf5098916006912f8dd35aaa6daa5579c6c297b2" +source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#3248ceeae41461d034624b582d5d358cd6e6f89f" dependencies = [ "libc", "vmm-sys-util", @@ -651,7 +659,7 @@ dependencies = [ "serde", "serde_derive", "vmm-sys-util", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -660,7 +668,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -681,7 +689,7 @@ dependencies = [ "net_gen", "rate_limiter", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "virtio-bindings", "virtio-queue", "vm-memory", @@ -700,18 +708,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -721,9 +730,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "open-enum" @@ -749,7 +764,7 @@ dependencies = [ name = "option_parser" version = "0.1.0" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] @@ -768,7 +783,7 @@ dependencies = [ "libc", "log", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-bindings", "vfio-ioctls", "vfio_user", @@ -781,55 +796,54 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", - "zerocopy 0.8.26", ] [[package]] @@ -858,7 +872,7 @@ dependencies = [ "epoll", "libc", "log", - "thiserror 2.0.12", + "thiserror 2.0.16", "vmm-sys-util", ] @@ -875,15 +889,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "scopeguard" @@ -902,18 +916,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", @@ -922,21 +946,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "serde_with" -version = "3.14.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" dependencies = [ "serde", "serde_derive", @@ -945,9 +970,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" dependencies = [ "darling", "proc-macro2", @@ -977,9 +1002,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -1007,9 +1032,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.95" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -1027,11 +1052,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] @@ -1047,9 +1072,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -1058,18 +1083,31 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap", "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ "winnow", ] @@ -1081,7 +1119,7 @@ dependencies = [ "libc", "log", "net_gen", - "thiserror 2.0.12", + "thiserror 2.0.16", "vmm-sys-util", ] @@ -1097,9 +1135,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "utf8parse" @@ -1139,7 +1177,7 @@ dependencies = [ "kvm-ioctls", "libc", "log", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1157,7 +1195,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1200,7 +1238,7 @@ dependencies = [ "serde", "serde_with", "serial_buffer", - "thiserror 2.0.12", + "thiserror 2.0.16", "vhost", "virtio-bindings", "virtio-queue", @@ -1239,7 +1277,7 @@ version = "0.1.0" dependencies = [ "hypervisor", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -1269,7 +1307,7 @@ dependencies = [ "anyhow", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "vm-memory", ] @@ -1313,11 +1351,12 @@ dependencies = [ "serde_json", "serial_buffer", "signal-hook", - "thiserror 2.0.12", + "thiserror 2.0.16", "tracer", "uuid", "vfio-ioctls", "vfio_user", + "vhost", "virtio-bindings", "virtio-devices", "vm-allocator", @@ -1326,7 +1365,7 @@ dependencies = [ "vm-migration", "vm-virtio", "vmm-sys-util", - "zerocopy 0.8.26", + "zerocopy", ] [[package]] @@ -1343,9 +1382,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -1436,21 +1475,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", @@ -1463,57 +1509,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.6.19" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52ac009d615e79296318c1bcce2d422aaca15ad08515e344feeda07df67a587" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -1529,39 +1575,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "zerocopy-derive 0.8.26", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index ccdad23241..36d0b1749a 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -16,13 +16,13 @@ mshv_emulator = ["hypervisor/mshv_emulator"] pvmemcontrol = [] [dependencies] -arbitrary = "1.4.1" +arbitrary = "1.4.2" block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } libc = "0.2.155" -libfuzzer-sys = "0.4.7" +libfuzzer-sys = "0.4.10" # TODO: update to 0.13.1+ linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main", features = [ "bzimage", diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index c756cf9fc1..ec65d85ccf 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -15,7 +15,7 @@ tdx = [] [dependencies] anyhow = { workspace = true } arc-swap = "1.7.1" -bitfield-struct = "0.10.1" +bitfield-struct = "0.11.0" byteorder = { workspace = true } cfg-if = { workspace = true } concat-idents = "1.1.5" diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 4b2e3bd754..de64f91e3c 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -39,7 +39,7 @@ anyhow = { workspace = true } arch = { path = "../arch" } bitflags = { workspace = true } block = { path = "../block" } -blocking = { version = "1.6.1", optional = true } +blocking = { version = "1.6.2", optional = true } cfg-if = { workspace = true } clap = { workspace = true } devices = { path = "../devices" } @@ -48,7 +48,7 @@ epoll = { workspace = true } event_monitor = { path = "../event_monitor" } flume = { workspace = true } futures = { version = "0.3.31", optional = true } -gdbstub = { version = "0.7.6", optional = true } +gdbstub = { version = "0.7.7", optional = true } gdbstub_arch = { version = "0.3.2", optional = true } hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } @@ -91,5 +91,5 @@ vm-memory = { workspace = true, features = [ vm-migration = { path = "../vm-migration" } vm-virtio = { path = "../vm-virtio" } vmm-sys-util = { workspace = true, features = ["with-serde"] } -zbus = { version = "5.7.1", optional = true } +zbus = { version = "5.11.0", optional = true } zerocopy = { workspace = true, features = ["alloc", "derive"] } From f2dfa7f6e0ae242ad540c176768cacf8361e9562 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Tue, 23 Sep 2025 15:07:13 +0000 Subject: [PATCH 0202/1893] misc: Use variables directly in format! string Fix clippy warning `uninlined_format_args` reported by rustc rustc 1.89.0 (29483883e 2025-08-04). ```console warning: variables can be used directly in the `format!` string --> block/src/lib.rs:649:17 | 649 | info!("{} failed to create io_uring instance: {}", error_msg, e); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args = note: `#[warn(clippy::uninlined_format_args)]` on by default help: change this to | 649 - info!("{} failed to create io_uring instance: {}", error_msg, e); 649 + info!("{error_msg} failed to create io_uring instance: {e}"); | ``` Signed-off-by: Ruoqing He --- arch/src/x86_64/mod.rs | 10 +- arch/src/x86_64/tdx/mod.rs | 2 +- block/src/lib.rs | 6 +- devices/src/acpi.rs | 4 +- devices/src/ioapic.rs | 14 +-- devices/src/legacy/cmos.rs | 4 +- devices/src/legacy/i8042.rs | 2 +- devices/src/pvpanic.rs | 8 +- devices/src/tpm.rs | 14 +-- hypervisor/src/kvm/mod.rs | 11 +- net_util/src/ctrl_queue.rs | 10 +- net_util/src/mac.rs | 5 +- net_util/src/queue_pair.rs | 4 +- pci/src/configuration.rs | 8 +- pci/src/msi.rs | 6 +- pci/src/msix.rs | 20 ++-- pci/src/vfio.rs | 41 +++---- pci/src/vfio_user.rs | 12 +- rate_limiter/src/group.rs | 6 +- src/main.rs | 2 +- tpm/src/emulator.rs | 40 +++---- tpm/src/socket.rs | 8 +- vhost_user_block/src/lib.rs | 13 +-- vhost_user_net/src/lib.rs | 9 +- virtio-devices/src/balloon.rs | 22 ++-- virtio-devices/src/block.rs | 47 +++----- virtio-devices/src/console.rs | 48 +++----- virtio-devices/src/device.rs | 8 +- virtio-devices/src/iommu.rs | 37 +++--- virtio-devices/src/mem.rs | 36 +++--- virtio-devices/src/net.rs | 49 ++++---- virtio-devices/src/pmem.rs | 24 ++-- virtio-devices/src/rng.rs | 18 ++- virtio-devices/src/thread_helper.rs | 8 +- .../src/transport/pci_common_config.rs | 30 ++--- virtio-devices/src/transport/pci_device.rs | 27 ++--- virtio-devices/src/vdpa.rs | 14 +-- virtio-devices/src/vhost_user/blk.rs | 13 +-- virtio-devices/src/vhost_user/fs.rs | 9 +- virtio-devices/src/vhost_user/mod.rs | 22 ++-- virtio-devices/src/vhost_user/net.rs | 13 +-- .../src/vhost_user/vu_common_ctrl.rs | 5 +- virtio-devices/src/vsock/device.rs | 35 +++--- virtio-devices/src/vsock/unix/muxer.rs | 28 ++--- virtio-devices/src/watchdog.rs | 38 +++--- vm-migration/src/lib.rs | 4 +- vmm/src/api/http/mod.rs | 11 +- vmm/src/api/mod.rs | 34 +++--- vmm/src/config.rs | 2 +- vmm/src/cpu.rs | 33 +++--- vmm/src/device_manager.rs | 59 ++++------ vmm/src/lib.rs | 110 ++++++++---------- vmm/src/memory_manager.rs | 55 ++++----- vmm/src/migration.rs | 2 +- vmm/src/serial_manager.rs | 2 +- vmm/src/vm.rs | 47 ++++---- 56 files changed, 470 insertions(+), 679 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 648220e070..f1503c39ef 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -829,7 +829,7 @@ pub fn configure_vcpu( } for c in &cpuid { - debug!("{}", c); + debug!("{c}"); } vcpu.set_cpuid2(&cpuid) @@ -995,17 +995,15 @@ pub fn generate_ram_ranges(guest_mem: &GuestMemoryMmap) -> super::Result Result<(SeekFrom, bool), TdvfError u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize; debug!( "Entry GUID = {}, size = {}", - entry_uuid.hyphenated().to_string(), + entry_uuid.hyphenated(), entry_size ); diff --git a/block/src/lib.rs b/block/src/lib.rs index 5599258e3e..4cfed22839 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -293,14 +293,14 @@ impl Request { .next() .ok_or(Error::DescriptorChainTooShort) .inspect_err(|_| { - error!("Only head descriptor present: request = {:?}", req); + error!("Only head descriptor present: request = {req:?}"); })?; if !desc.has_next() { status_desc = desc; // Only flush requests are allowed to skip the data descriptor. if req.request_type != RequestType::Flush { - error!("Need a data descriptor: request = {:?}", req); + error!("Need a data descriptor: request = {req:?}"); return Err(Error::DescriptorChainTooShort); } } else { @@ -325,7 +325,7 @@ impl Request { .next() .ok_or(Error::DescriptorChainTooShort) .inspect_err(|_| { - error!("DescriptorChain corrupted: request = {:?}", req); + error!("DescriptorChain corrupted: request = {req:?}"); })?; } status_desc = desc; diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs index 229b67be54..4791d52db0 100644 --- a/devices/src/acpi.rs +++ b/devices/src/acpi.rs @@ -51,7 +51,7 @@ impl BusDevice for AcpiShutdownDevice { if data[0] == 1 { info!("ACPI Reboot signalled"); if let Err(e) = self.reset_evt.write(1) { - error!("Error triggering ACPI reset event: {}", e); + error!("Error triggering ACPI reset event: {e}"); } // Spin until we are sure the reset_evt has been handled and that when // we return from the KVM_RUN we will exit rather than re-enter the guest. @@ -68,7 +68,7 @@ impl BusDevice for AcpiShutdownDevice { if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) { info!("ACPI Shutdown signalled"); if let Err(e) = self.exit_evt.write(1) { - error!("Error triggering ACPI shutdown event: {}", e); + error!("Error triggering ACPI shutdown event: {e}"); } // Spin until we are sure the reset_evt has been handled and that when // we return from the KVM_RUN we will exit rather than re-enter the guest. diff --git a/devices/src/ioapic.rs b/devices/src/ioapic.rs index 97932f016d..3ce827bd18 100644 --- a/devices/src/ioapic.rs +++ b/devices/src/ioapic.rs @@ -151,13 +151,13 @@ impl BusDevice for Ioapic { return; } - debug!("IOAPIC_R @ offset 0x{:x}", offset); + debug!("IOAPIC_R @ offset 0x{offset:x}"); let value: u32 = match offset as u8 { IOREGSEL_OFF => self.reg_sel, IOWIN_OFF => self.ioapic_read(), _ => { - error!("IOAPIC: failed reading at offset {}", offset); + error!("IOAPIC: failed reading at offset {offset}"); return; } }; @@ -171,7 +171,7 @@ impl BusDevice for Ioapic { return None; } - debug!("IOAPIC_W @ offset 0x{:x}", offset); + debug!("IOAPIC_W @ offset 0x{offset:x}"); let value = LittleEndian::read_u32(data); @@ -179,7 +179,7 @@ impl BusDevice for Ioapic { IOREGSEL_OFF => self.reg_sel = value, IOWIN_OFF => self.ioapic_write(value), _ => { - error!("IOAPIC: failed writing at offset {}", offset); + error!("IOAPIC: failed writing at offset {offset}"); } } None @@ -266,7 +266,7 @@ impl Ioapic { IOWIN_OFF..=REG_MAX_OFFSET => { let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8); if index > NUM_IOAPIC_PINS { - warn!("IOAPIC index out of range: {}", index); + warn!("IOAPIC index out of range: {index}"); return; } if is_high_bits { @@ -282,7 +282,7 @@ impl Ioapic { // The entry must be updated through the interrupt source // group. if let Err(e) = self.update_entry(index, true) { - error!("Failed updating IOAPIC entry: {:?}", e); + error!("Failed updating IOAPIC entry: {e:?}"); } // Store the information this IRQ is now being used. self.used_entries[index] = true; @@ -303,7 +303,7 @@ impl Ioapic { IOWIN_OFF..=REG_MAX_OFFSET => { let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8); if index > NUM_IOAPIC_PINS { - warn!("IOAPIC index out of range: {}", index); + warn!("IOAPIC index out of range: {index}"); return 0; } if is_high_bits { diff --git a/devices/src/legacy/cmos.rs b/devices/src/legacy/cmos.rs index b0e140acc3..d5528fb775 100644 --- a/devices/src/legacy/cmos.rs +++ b/devices/src/legacy/cmos.rs @@ -90,7 +90,7 @@ impl BusDevice for Cmos { self.data[(self.index & INDEX_MASK) as usize] = data[0] } } - o => warn!("bad write offset on CMOS device: {}", o), + o => warn!("bad write offset on CMOS device: {o}"), }; None } @@ -164,7 +164,7 @@ impl BusDevice for Cmos { } } o => { - warn!("bad read offset on CMOS device: {}", o); + warn!("bad read offset on CMOS device: {o}"); 0 } } diff --git a/devices/src/legacy/i8042.rs b/devices/src/legacy/i8042.rs index cc4bcd3e61..bbfe94eeb4 100644 --- a/devices/src/legacy/i8042.rs +++ b/devices/src/legacy/i8042.rs @@ -45,7 +45,7 @@ impl BusDevice for I8042Device { if data.len() == 1 && data[0] == 0xfe && offset == 3 { info!("i8042 reset signalled"); if let Err(e) = self.reset_evt.write(1) { - error!("Error triggering i8042 reset event: {}", e); + error!("Error triggering i8042 reset event: {e}"); } // Spin until we are sure the reset_evt has been handled and that when // we return from the KVM_RUN we will exit rather than re-enter the guest. diff --git a/devices/src/pvpanic.rs b/devices/src/pvpanic.rs index 4fd61188b0..d6d3f01163 100644 --- a/devices/src/pvpanic.rs +++ b/devices/src/pvpanic.rs @@ -68,8 +68,7 @@ impl PvPanicDevice { let pci_configuration_state = vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| { PvPanicError::RetrievePciConfigurationState(anyhow!( - "Failed to get PciConfigurationState from Snapshot: {}", - e + "Failed to get PciConfigurationState from Snapshot: {e}" )) })?; @@ -100,8 +99,7 @@ impl PvPanicDevice { .transpose() .map_err(|e| { PvPanicError::CreatePvPanicDevice(anyhow!( - "Failed to get PvPanicDeviceState from Snapshot: {}", - e + "Failed to get PvPanicDeviceState from Snapshot: {e}" )) })?; let events = if let Some(state) = state { @@ -148,7 +146,7 @@ impl BusDevice for PvPanicDevice { fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option> { let event = self.event_to_string(data[0]); - info!("pvpanic got guest event {}", event); + info!("pvpanic got guest event {event}"); event!("guest", "panic", "event", &event); None } diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs index 72fef3e539..fa8d468b8d 100644 --- a/devices/src/tpm.rs +++ b/devices/src/tpm.rs @@ -221,7 +221,7 @@ pub struct Tpm { impl Tpm { pub fn new(path: String) -> Result { let emulator = Emulator::new(path) - .map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {:?}", e)))?; + .map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {e:?}")))?; let mut tpm = Tpm { emulator, regs: [0; TPM_CRB_R_MAX], @@ -331,8 +331,7 @@ impl Tpm { if let Err(e) = self.emulator.startup_tpm(self.backend_buff_size) { return Err(Error::Init(anyhow!( - "Failed while running Startup TPM. Error: {:?}", - e + "Failed while running Startup TPM. Error: {e:?}" ))); } Ok(()) @@ -460,7 +459,7 @@ impl BusDevice for Tpm { && (self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE != 0) && let Err(e) = self.emulator.cancel_cmd() { - error!("Failed to run cancel command. Error: {:?}", e); + error!("Failed to run cancel command. Error: {e:?}"); } } CRB_CTRL_START => { @@ -481,10 +480,7 @@ impl BusDevice for Tpm { } } CRB_LOC_CTRL => { - warn!( - "CRB_LOC_CTRL locality to write = {:?} val = {:?}", - locality, v - ); + warn!("CRB_LOC_CTRL locality to write = {locality:?} val = {v:?}"); match v { CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT => {} CRB_LOC_CTRL_RELINQUISH => { @@ -517,7 +513,7 @@ impl BusDevice for Tpm { ); } _ => { - error!("Invalid value to write in CRB_LOC_CTRL {:#X} ", v); + error!("Invalid value to write in CRB_LOC_CTRL {v:#X} "); } } } diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 8e516bb76f..a4a062b3ea 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -633,7 +633,7 @@ impl vm::Vm for KvmVm { /// fn create_vaia(&self, config: VaiaConfig) -> vm::Result>> { let aia_device = KvmAiaImsics::new(self, config) - .map_err(|e| vm::HypervisorVmError::CreateVaia(anyhow!("Vaia error {:?}", e)))?; + .map_err(|e| vm::HypervisorVmError::CreateVaia(anyhow!("Vaia error {e:?}")))?; Ok(Arc::new(Mutex::new(aia_device))) } @@ -1997,8 +1997,7 @@ impl cpu::Vcpu for KvmVcpu { // tr.valid is set if the GVA is mapped to valid GPA. match tr.valid { 0 => Err(cpu::HypervisorCpuError::TranslateVirtualAddress(anyhow!( - "Invalid GVA: {:#x}", - gva + "Invalid GVA: {gva:#x}" ))), _ => Ok((tr.physical_address, 0)), } @@ -2081,16 +2080,14 @@ impl cpu::Vcpu for KvmVcpu { VcpuExit::Debug(_) => Ok(cpu::VmExit::Debug), r => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( - "Unexpected exit reason on vcpu run: {:?}", - r + "Unexpected exit reason on vcpu run: {r:?}" ))), }, Err(ref e) => match e.errno() { libc::EAGAIN | libc::EINTR => Ok(cpu::VmExit::Ignore), _ => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( - "VCPU error {:?}", - e + "VCPU error {e:?}" ))), }, } diff --git a/net_util/src/ctrl_queue.rs b/net_util/src/ctrl_queue.rs index b4305cbea9..3fd242f668 100644 --- a/net_util/src/ctrl_queue.rs +++ b/net_util/src/ctrl_queue.rs @@ -100,10 +100,10 @@ impl CtrlQueue { } else if (queue_pairs < VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN as u16) || (queue_pairs > VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX as u16) { - warn!("Number of MQ pairs out of range: {}", queue_pairs); + warn!("Number of MQ pairs out of range: {queue_pairs}"); false } else { - info!("Number of MQ pairs requested: {}", queue_pairs); + info!("Number of MQ pairs requested: {queue_pairs}"); true } } @@ -118,10 +118,10 @@ impl CtrlQueue { } else { let mut ok = true; for tap in self.taps.iter_mut() { - info!("Reprogramming tap offload with features: {}", features); + info!("Reprogramming tap offload with features: {features}"); tap.set_offload(virtio_features_to_tap_offload(features)) .map_err(|e| { - error!("Error programming tap offload: {:?}", e); + error!("Error programming tap offload: {e:?}"); ok = false }) .ok(); @@ -130,7 +130,7 @@ impl CtrlQueue { } } _ => { - warn!("Unsupported command {:?}", ctrl_hdr); + warn!("Unsupported command {ctrl_hdr:?}"); false } }; diff --git a/net_util/src/mac.rs b/net_util/src/mac.rs index 4bd1ac38ff..432bfebae2 100644 --- a/net_util/src/mac.rs +++ b/net_util/src/mac.rs @@ -83,10 +83,7 @@ impl MacAddr { // Generate a fully random MAC let mut random_bytes = [0u8; MAC_ADDR_LEN]; if let Err(e) = getrandom::fill(&mut random_bytes) { - error!( - "Error populating MAC address with random data: {}", - e.to_string() - ); + error!("Error populating MAC address with random data: {e}"); } // Set the first byte to make the OUI a locally administered OUI diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index 63fe677509..c3c145646a 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -105,7 +105,7 @@ impl TxVirtio { retry_write = true; break; } - error!("net: tx: failed writing to tap: {}", e); + error!("net: tx: failed writing to tap: {e}"); return Err(NetQueuePairError::WriteTap(e)); } @@ -246,7 +246,7 @@ impl RxVirtio { break; } - error!("net: rx: failed reading from tap: {}", e); + error!("net: rx: failed reading from tap: {e}"); return Err(NetQueuePairError::ReadTap(e)); } diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index 706947050c..7998370688 100644 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -662,7 +662,7 @@ impl PciConfiguration { if let Some(r) = self.registers.get_mut(reg_idx) { *r = (*r & !self.writable_bits[reg_idx]) | (value & mask); } else { - warn!("bad PCI register write {}", reg_idx); + warn!("bad PCI register write {reg_idx}"); } } @@ -672,7 +672,7 @@ impl PciConfiguration { 0 => 0, 2 => 16, _ => { - warn!("bad PCI config write offset {}", offset); + warn!("bad PCI config write offset {offset}"); return; } }; @@ -684,7 +684,7 @@ impl PciConfiguration { let shifted_value = (u32::from(value) << shift) & writable_mask; *r = *r & !mask | shifted_value; } else { - warn!("bad PCI config write offset {}", offset); + warn!("bad PCI config write offset {offset}"); } } @@ -708,7 +708,7 @@ impl PciConfiguration { let shifted_value = (u32::from(value) << shift) & writable_mask; *r = *r & !mask | shifted_value; } else { - warn!("bad PCI config write offset {}", offset); + warn!("bad PCI config write offset {offset}"); } } diff --git a/pci/src/msi.rs b/pci/src/msi.rs index a0215dcdd3..fd590cb14c 100644 --- a/pci/src/msi.rs +++ b/pci/src/msi.rs @@ -267,15 +267,15 @@ impl MsiConfig { self.cap.vector_masked(idx), true, ) { - error!("Failed updating vector: {:?}", e); + error!("Failed updating vector: {e:?}"); } } if !old_enabled && let Err(e) = self.interrupt_source_group.enable() { - error!("Failed enabling irq_fd: {:?}", e); + error!("Failed enabling irq_fd: {e:?}"); } } else if old_enabled && let Err(e) = self.interrupt_source_group.disable() { - error!("Failed disabling irq_fd: {:?}", e); + error!("Failed disabling irq_fd: {e:?}"); } } } diff --git a/pci/src/msix.rs b/pci/src/msix.rs index 718c4f83ee..7007eb3e5d 100644 --- a/pci/src/msix.rs +++ b/pci/src/msix.rs @@ -186,13 +186,13 @@ impl MsixConfig { table_entry.masked(), true, ) { - error!("Failed updating vector: {:?}", e); + error!("Failed updating vector: {e:?}"); } } } else if old_enabled || !old_masked { debug!("MSI-X disabled for device 0x{:x}", self.devid); if let Err(e) = self.interrupt_source_group.disable() { - error!("Failed disabling irq_fd: {:?}", e); + error!("Failed disabling irq_fd: {e:?}"); } } } @@ -235,7 +235,7 @@ impl MsixConfig { } }; - debug!("MSI_R TABLE offset 0x{:x} data 0x{:x}", offset, value); + debug!("MSI_R TABLE offset 0x{offset:x} data 0x{value:x}"); LittleEndian::write_u32(data, value); } 8 => { @@ -254,7 +254,7 @@ impl MsixConfig { } }; - debug!("MSI_R TABLE offset 0x{:x} data 0x{:x}", offset, value); + debug!("MSI_R TABLE offset 0x{offset:x} data 0x{value:x}"); LittleEndian::write_u64(data, value); } _ => { @@ -290,7 +290,7 @@ impl MsixConfig { _ => error!("invalid offset"), }; - debug!("MSI_W TABLE offset 0x{:x} data 0x{:x}", offset, value); + debug!("MSI_W TABLE offset 0x{offset:x} data 0x{value:x}"); } 8 => { let value = LittleEndian::read_u64(data); @@ -306,7 +306,7 @@ impl MsixConfig { _ => error!("invalid offset"), }; - debug!("MSI_W TABLE offset 0x{:x} data 0x{:x}", offset, value); + debug!("MSI_W TABLE offset 0x{offset:x} data 0x{value:x}"); } _ => error!("invalid data length"), }; @@ -336,7 +336,7 @@ impl MsixConfig { table_entry.masked(), true, ) { - error!("Failed updating vector: {:?}", e); + error!("Failed updating vector: {e:?}"); } } @@ -382,7 +382,7 @@ impl MsixConfig { } }; - debug!("MSI_R PBA offset 0x{:x} data 0x{:x}", offset, value); + debug!("MSI_R PBA offset 0x{offset:x} data 0x{value:x}"); LittleEndian::write_u32(data, value); } 8 => { @@ -394,7 +394,7 @@ impl MsixConfig { } }; - debug!("MSI_R PBA offset 0x{:x} data 0x{:x}", offset, value); + debug!("MSI_R PBA offset 0x{offset:x} data 0x{value:x}"); LittleEndian::write_u64(data, value); } _ => { @@ -438,7 +438,7 @@ impl MsixConfig { .trigger(vector as InterruptIndex) { Ok(_) => debug!("MSI-X injected on vector control flip"), - Err(e) => error!("failed to inject MSI-X: {}", e), + Err(e) => error!("failed to inject MSI-X: {e}"), } // Clear the bit from PBA diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 8372046acf..abe80073b4 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -474,8 +474,7 @@ impl VfioCommon { let pci_configuration_state = vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| { VfioPciError::RetrievePciConfigurationState(anyhow!( - "Failed to get PciConfigurationState from Snapshot: {}", - e + "Failed to get PciConfigurationState from Snapshot: {e}" )) })?; @@ -514,22 +513,19 @@ impl VfioCommon { .transpose() .map_err(|e| { VfioPciError::RetrieveVfioCommonState(anyhow!( - "Failed to get VfioCommonState from Snapshot: {}", - e + "Failed to get VfioCommonState from Snapshot: {e}" )) })?; let msi_state = vm_migration::state_from_id(snapshot.as_ref(), MSI_CONFIG_ID).map_err(|e| { VfioPciError::RetrieveMsiConfigState(anyhow!( - "Failed to get MsiConfigState from Snapshot: {}", - e + "Failed to get MsiConfigState from Snapshot: {e}" )) })?; let msix_state = vm_migration::state_from_id(snapshot.as_ref(), MSIX_CONFIG_ID).map_err(|e| { VfioPciError::RetrieveMsixConfigState(anyhow!( - "Failed to get MsixConfigState from Snapshot: {}", - e + "Failed to get MsixConfigState from Snapshot: {e}" )) })?; @@ -1057,7 +1053,7 @@ impl VfioCommon { && intx.enabled { if let Err(e) = self.vfio_wrapper.disable_irq(VFIO_PCI_INTX_IRQ_INDEX) { - error!("Could not disable INTx: {}", e); + error!("Could not disable INTx: {e}"); } else { intx.enabled = false; } @@ -1085,7 +1081,7 @@ impl VfioCommon { pub(crate) fn disable_msi(&self) { if let Err(e) = self.vfio_wrapper.disable_msi() { - error!("Could not disable MSI: {}", e); + error!("Could not disable MSI: {e}"); } } @@ -1110,7 +1106,7 @@ impl VfioCommon { pub(crate) fn disable_msix(&self) { if let Err(e) = self.vfio_wrapper.disable_msix() { - error!("Could not disable MSI-X: {}", e); + error!("Could not disable MSI-X: {e}"); } } @@ -1200,7 +1196,7 @@ impl VfioCommon { if self.interrupt.intx_in_use() && let Err(e) = self.vfio_wrapper.unmask_irq(VFIO_PCI_INTX_IRQ_INDEX) { - error!("Failed unmasking INTx IRQ: {}", e); + error!("Failed unmasking INTx IRQ: {e}"); } } @@ -1228,7 +1224,7 @@ impl VfioCommon { if self.interrupt.intx_in_use() && let Err(e) = self.vfio_wrapper.unmask_irq(VFIO_PCI_INTX_IRQ_INDEX) { - error!("Failed unmasking INTx IRQ: {}", e); + error!("Failed unmasking INTx IRQ: {e}"); } None @@ -1267,12 +1263,12 @@ impl VfioCommon { match cap_id { PciCapabilityId::MessageSignalledInterrupts => { if let Err(e) = self.update_msi_capabilities(cap_offset, data) { - error!("Could not update MSI capabilities: {}", e); + error!("Could not update MSI capabilities: {e}"); } } PciCapabilityId::MsiX => { if let Err(e) = self.update_msix_capabilities(cap_offset, data) { - error!("Could not update MSI-X capabilities: {}", e); + error!("Could not update MSI-X capabilities: {e}"); } } _ => {} @@ -1296,12 +1292,11 @@ impl VfioCommon { & crate::configuration::COMMAND_REG_MEMORY_SPACE_MASK == crate::configuration::COMMAND_REG_MEMORY_SPACE_MASK { - info!("BAR reprogramming parameter is returned: {:x?}", ret_param); + info!("BAR reprogramming parameter is returned: {ret_param:x?}"); self.configuration.clear_pending_bar_reprogram(); } else { info!( - "MSE bit is disabled. No BAR reprogramming parameter is returned: {:x?}", - ret_param + "MSE bit is disabled. No BAR reprogramming parameter is returned: {ret_param:x?}" ); ret_param = Vec::new(); @@ -1513,16 +1508,12 @@ impl VfioPciDevice { VfioRegionInfoCap::MsixMappable => { if !is_4k_aligned(region_start) { error!( - "Region start address 0x{:x} must be at least aligned on 4KiB", - region_start + "Region start address 0x{region_start:x} must be at least aligned on 4KiB" ); return Err(VfioPciError::RegionAlignment); } if !is_4k_multiple(region_size) { - error!( - "Region size 0x{:x} must be at least a multiple of 4KiB", - region_size - ); + error!("Region size 0x{region_size:x} must be at least a multiple of 4KiB"); return Err(VfioPciError::RegionSize); } @@ -1733,7 +1724,7 @@ impl VfioPciDevice { ); if let Err(e) = self.vm.remove_user_memory_region(r) { - error!("Could not remove the userspace memory region: {}", e); + error!("Could not remove the userspace memory region: {e}"); } self.memory_slot_allocator diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index e92c719964..79dfb659ac 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -219,7 +219,7 @@ impl VfioUserPciDevice { ); if let Err(e) = self.vm.remove_user_memory_region(r) { - error!("Could not remove the userspace memory region: {}", e); + error!("Could not remove the userspace memory region: {e}"); } self.memory_slot_allocator @@ -370,7 +370,7 @@ impl Vfio for VfioUserClientWrapper { } fn disable_irq(&self, irq_index: u32) -> Result<(), VfioError> { - info!("Disabling IRQ {:x}", irq_index); + info!("Disabling IRQ {irq_index:x}"); self.client .lock() .unwrap() @@ -385,7 +385,7 @@ impl Vfio for VfioUserClientWrapper { } fn unmask_irq(&self, irq_index: u32) -> Result<(), VfioError> { - info!("Unmasking IRQ {:x}", irq_index); + info!("Unmasking IRQ {irq_index:x}"); self.client .lock() .unwrap() @@ -448,7 +448,7 @@ impl PciDevice for VfioUserPciDevice { } fn move_bar(&mut self, old_base: u64, new_base: u64) -> Result<(), std::io::Error> { - info!("Moving BAR 0x{:x} -> 0x{:x}", old_base, new_base); + info!("Moving BAR 0x{old_base:x} -> 0x{new_base:x}"); for mmio_region in self.common.mmio_regions.iter_mut() { if mmio_region.start.raw_value() == old_base { mmio_region.start = GuestAddress(new_base); @@ -489,7 +489,7 @@ impl PciDevice for VfioUserPciDevice { .create_user_memory_region(new_region) .map_err(std::io::Error::other)?; } - info!("Moved bar 0x{:x} -> 0x{:x}", old_base, new_base); + info!("Moved bar 0x{old_base:x} -> 0x{new_base:x}"); } } @@ -522,7 +522,7 @@ impl Drop for VfioUserPciDevice { } if let Err(e) = self.client.lock().unwrap().shutdown() { - error!("Failed shutting down vfio-user client: {}", e); + error!("Failed shutting down vfio-user client: {e}"); } } } diff --git a/rate_limiter/src/group.rs b/rate_limiter/src/group.rs index a986a7f581..e89dddae7f 100644 --- a/rate_limiter/src/group.rs +++ b/rate_limiter/src/group.rs @@ -244,7 +244,7 @@ impl RateLimiterGroup { match dispatch_event { EpollDispatch::Unknown => { let event = event.data; - warn!("Unknown rate-limiter loop event: {}", event); + warn!("Unknown rate-limiter loop event: {event}"); } EpollDispatch::Unblocked => { inner.rate_limiter.event_handler().unwrap(); @@ -267,7 +267,7 @@ impl RateLimiterGroup { match res { Ok(res) => { if let Err(e) = res { - error!("Error running rate-limit-group worker: {:?}", e); + error!("Error running rate-limit-group worker: {e:?}"); exit_evt.write(1).unwrap(); } } @@ -291,7 +291,7 @@ impl Drop for RateLimiterGroup { if let Some(t) = self.epoll_thread.take() && let Err(e) = t.join() { - error!("Error joining thread: {:?}", e); + error!("Error joining thread: {e:?}"); } } } diff --git a/src/main.rs b/src/main.rs index 19a4d4eab6..32dad8dffd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -136,7 +136,7 @@ impl log::Log for Logger { let duration_s = duration.as_secs_f32(); let location = if let (Some(file), Some(line)) = (record.file(), record.line()) { - format!("{}:{}", file, line) + format!("{file}:{line}") } else { record.target().to_string() }; diff --git a/tpm/src/emulator.rs b/tpm/src/emulator.rs index ac762d12a4..16e88c5af7 100644 --- a/tpm/src/emulator.rs +++ b/tpm/src/emulator.rs @@ -88,13 +88,12 @@ impl Emulator { pub fn new(path: String) -> Result { if !Path::new(&path).exists() { return Err(Error::InitializeEmulator(anyhow!( - "The input TPM Socket path: {:?} does not exist", - path + "The input TPM Socket path: {path:?} does not exist" ))); } let mut socket = SocketDev::new(); socket.init(path).map_err(|e| { - Error::InitializeEmulator(anyhow!("Failed while initializing tpm emulator: {:?}", e)) + Error::InitializeEmulator(anyhow!("Failed while initializing tpm emulator: {e:?}")) })?; let mut emulator = Self { @@ -217,41 +216,36 @@ impl Emulator { msg_len_in: usize, msg_len_out: usize, ) -> Result<()> { - debug!("Control Cmd to send : {:02X?}", cmd); + debug!("Control Cmd to send : {cmd:02X?}"); let cmd_no = (cmd as u32).to_be_bytes(); let n = mem::size_of::() + msg_len_in; let converted_req = msg.ptm_to_request(); - debug!("converted request: {:02X?}", converted_req); + debug!("converted request: {converted_req:02X?}"); let mut buf = Vec::::with_capacity(n); buf.extend(cmd_no); buf.extend(converted_req); - debug!("full Control request {:02X?}", buf); + debug!("full Control request {buf:02X?}"); let written = self.control_socket.write(&buf).map_err(|e| { Error::RunControlCmd(anyhow!( - "Failed while running {:02X?} Control Cmd. Error: {:?}", - cmd, - e + "Failed while running {cmd:02X?} Control Cmd. Error: {e:?}" )) })?; if written < buf.len() { return Err(Error::RunControlCmd(anyhow!( - "Truncated write while running {:02X?} Control Cmd", - cmd, + "Truncated write while running {cmd:02X?} Control Cmd", ))); } // The largest response is 16 bytes so far. if msg_len_out > 16 { return Err(Error::RunControlCmd(anyhow!( - "Response size is too large for Cmd {:02X?}, max 16 wanted {}", - cmd, - msg_len_out + "Response size is too large for Cmd {cmd:02X?}, max 16 wanted {msg_len_out}" ))); } @@ -260,9 +254,7 @@ impl Emulator { // Every Control Cmd gets at least a result code in response. Read it let read_size = self.control_socket.read(&mut output).map_err(|e| { Error::RunControlCmd(anyhow!( - "Failed while reading response for Control Cmd: {:02X?}. Error: {:?}", - cmd, - e + "Failed while reading response for Control Cmd: {cmd:02X?}. Error: {e:?}" )) })?; @@ -270,9 +262,7 @@ impl Emulator { msg.update_ptm_with_response(&output[0..read_size]) .map_err(|e| { Error::RunControlCmd(anyhow!( - "Failed while converting response of Control Cmd: {:02X?} to PTM. Error: {:?}", - cmd, - e + "Failed while converting response of Control Cmd: {cmd:02X?} to PTM. Error: {e:?}" )) })?; } else { @@ -303,10 +293,7 @@ impl Emulator { 0, 2 * mem::size_of::(), ) { - error!( - "Failed to run CmdGetTpmEstablished Control Cmd. Error: {:?}", - e - ); + error!("Failed to run CmdGetTpmEstablished Control Cmd. Error: {e:?}"); return false; } @@ -379,8 +366,7 @@ impl Emulator { if isselftest && output_len < 10 { return Err(Error::SelfTest(anyhow!( - "Self test response should have 10 bytes. Only {:?} returned", - output_len + "Self test response should have 10 bytes. Only {output_len:?} returned" ))); } @@ -426,7 +412,7 @@ impl Emulator { if buffersize != 0 { let actual_size = self.set_buffer_size(buffersize)?; - debug!("set tpm buffersize to {:?} during Startup", actual_size); + debug!("set tpm buffersize to {actual_size:?} during Startup"); } self.run_control_cmd( diff --git a/tpm/src/socket.rs b/tpm/src/socket.rs index a6ec0d0a4d..0c248e43eb 100644 --- a/tpm/src/socket.rs +++ b/tpm/src/socket.rs @@ -66,12 +66,12 @@ impl SocketDev { self.state = SocketDevState::Connecting; let s = UnixStream::connect(socket_path).map_err(|e| { - Error::ConnectToSocket(anyhow!("Failed to connect to tpm Socket. Error: {:?}", e)) + Error::ConnectToSocket(anyhow!("Failed to connect to tpm Socket. Error: {e:?}")) })?; self.control_fd = s.as_raw_fd(); self.stream = Some(s); self.state = SocketDevState::Connected; - debug!("Connected to tpm socket path : {:?}", socket_path); + debug!("Connected to tpm socket path : {socket_path:?}"); Ok(()) } @@ -92,7 +92,7 @@ impl SocketDev { .unwrap() .send_with_fd(buf, write_fd) .map_err(|e| { - Error::WriteToSocket(anyhow!("Failed to write to Socket. Error: {:?}", e)) + Error::WriteToSocket(anyhow!("Failed to write to Socket. Error: {e:?}")) })?; Ok(size) @@ -129,7 +129,7 @@ impl SocketDev { } let mut socket = self.stream.as_ref().unwrap(); let size: usize = socket.read(buf).map_err(|e| { - Error::ReadFromSocket(anyhow!("Failed to read from socket. Error Code {:?}", e)) + Error::ReadFromSocket(anyhow!("Failed to read from socket. Error Code {e:?}")) })?; Ok(size) } diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 3977a25ab5..b3549243e0 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -152,7 +152,7 @@ impl VhostUserBlkThread { .unwrap(); } Err(err) => { - error!("failed to parse available descriptor chain: {:?}", err); + error!("failed to parse available descriptor chain: {err:?}"); len = 0; } } @@ -350,7 +350,7 @@ impl VhostUserBackendMut for VhostUserBlkBackend { return Err(Error::HandleEventNotEpollIn.into()); } - debug!("event received: {:?}", device_event); + debug!("event received: {device_event:?}"); let thread = self.threads[thread_id].get_mut().unwrap(); match device_event { @@ -531,20 +531,17 @@ pub fn start_block_backend(backend_command: &str) { debug!("blk_daemon is created!\n"); if let Err(e) = blk_daemon.start(listener) { - error!( - "Failed to start daemon for vhost-user-block with error: {:?}\n", - e - ); + error!("Failed to start daemon for vhost-user-block with error: {e:?}\n"); process::exit(1); } if let Err(e) = blk_daemon.wait() { - error!("Error from the main thread: {:?}", e); + error!("Error from the main thread: {e:?}"); } for thread in blk_backend.read().unwrap().threads.iter() { if let Err(e) = thread.lock().unwrap().kill_evt.write(1) { - error!("Error shutting down worker thread: {:?}", e) + error!("Error shutting down worker thread: {e:?}") } } } diff --git a/vhost_user_net/src/lib.rs b/vhost_user_net/src/lib.rs index b5c0aa8019..9979cc9f58 100644 --- a/vhost_user_net/src/lib.rs +++ b/vhost_user_net/src/lib.rs @@ -396,20 +396,17 @@ pub fn start_net_backend(backend_command: &str) { } else { net_daemon.start(Listener::new(&backend_config.socket, true).unwrap()) } { - error!( - "failed to start daemon for vhost-user-net with error: {:?}", - e - ); + error!("failed to start daemon for vhost-user-net with error: {e:?}"); process::exit(1); } if let Err(e) = net_daemon.wait() { - error!("Error from the main thread: {:?}", e); + error!("Error from the main thread: {e:?}"); } for thread in net_backend.read().unwrap().threads.iter() { if let Err(e) = thread.lock().unwrap().kill_evt.write(1) { - error!("Error shutting down worker thread: {:?}", e) + error!("Error shutting down worker thread: {e:?}") } } } diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index 1a5e202fdf..b721410e33 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -163,7 +163,7 @@ struct BalloonEpollHandler { impl BalloonEpollHandler { fn signal(&self, int_type: VirtioInterruptType) -> result::Result<(), Error> { self.interrupt_cb.trigger(int_type).map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); Error::FailedSignal(e) }) } @@ -363,28 +363,24 @@ impl EpollHelperHandler for BalloonEpollHandler { INFLATE_QUEUE_EVENT => { self.inflate_queue_evt.read().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to get inflate queue event: {:?}", - e + "Failed to get inflate queue event: {e:?}" )) })?; self.process_queue(0).map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used inflate queue: {:?}", - e + "Failed to signal used inflate queue: {e:?}" )) })?; } DEFLATE_QUEUE_EVENT => { self.deflate_queue_evt.read().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to get deflate queue event: {:?}", - e + "Failed to get deflate queue event: {e:?}" )) })?; self.process_queue(1).map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used deflate queue: {:?}", - e + "Failed to signal used deflate queue: {e:?}" )) })?; } @@ -392,14 +388,12 @@ impl EpollHelperHandler for BalloonEpollHandler { if let Some(reporting_queue_evt) = self.reporting_queue_evt.as_ref() { reporting_queue_evt.read().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to get reporting queue event: {:?}", - e + "Failed to get reporting queue event: {e:?}" )) })?; self.process_reporting_queue(2).map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used inflate queue: {:?}", - e + "Failed to signal used inflate queue: {e:?}" )) })?; } else { @@ -450,7 +444,7 @@ impl Balloon { let mut queue_sizes = vec![QUEUE_SIZE; MIN_NUM_QUEUES]; let (avail_features, acked_features, config, paused) = if let Some(state) = state { - info!("Restoring virtio-balloon {}", id); + info!("Restoring virtio-balloon {id}"); ( state.avail_features, state.acked_features, diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index dad4fd2158..89228c3826 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -178,7 +178,7 @@ impl BlockEpollHandler { // "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request // if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data." if let Err(e) = Self::check_request(self.acked_features, request.request_type) { - warn!("Request check failed: {:x?} {:?}", request, e); + warn!("Request check failed: {request:x?} {e:?}"); desc_chain .memory() .write_obj(VIRTIO_BLK_S_IOERR, request.status_addr) @@ -257,7 +257,7 @@ impl BlockEpollHandler { let status = match result { Ok(_) => VIRTIO_BLK_S_OK, Err(e) => { - warn!("Request failed: {:x?} {:?}", request, e); + warn!("Request failed: {request:x?} {e:?}"); VIRTIO_BLK_S_IOERR } }; @@ -285,10 +285,7 @@ impl BlockEpollHandler { Err(e) => { // If batch submission fails, report VIRTIO_BLK_S_IOERR for all requests. for (user_data, request) in batch_inflight_requests { - warn!( - "Request failed with batch submission: {:x?} {:?}", - request, e - ); + warn!("Request failed with batch submission: {request:x?} {e:?}"); let desc_index = user_data; let mem = self.mem.memory(); mem.write_obj(VIRTIO_BLK_S_IOERR as u8, request.status_addr) @@ -311,14 +308,11 @@ impl BlockEpollHandler { .queue .needs_notification(self.mem.memory().deref()) .map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to check needs_notification: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to check needs_notification: {e:?}")) })? { self.signal_used_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {e:?}")) })?; } @@ -327,7 +321,7 @@ impl BlockEpollHandler { fn process_queue_submit_and_signal(&mut self) -> result::Result<(), EpollHelperError> { self.process_queue_submit().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to process queue (submit): {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to process queue (submit): {e:?}")) })?; self.try_signal_used_queue() @@ -493,7 +487,7 @@ impl BlockEpollHandler { self.interrupt_cb .trigger(VirtioInterruptType::Queue(self.queue_index)) .map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -561,7 +555,7 @@ impl EpollHelperHandler for BlockEpollHandler { match ev_type { QUEUE_AVAIL_EVENT => { self.queue_evt.read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}")) })?; let rate_limit_reached = self.rate_limiter.as_ref().is_some_and(|r| r.is_blocked()); @@ -573,13 +567,12 @@ impl EpollHelperHandler for BlockEpollHandler { } COMPLETION_EVENT => { self.disk_image.notifier().read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}")) })?; self.process_queue_complete().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to process queue (complete): {:?}", - e + "Failed to process queue (complete): {e:?}" )) })?; @@ -589,8 +582,7 @@ impl EpollHelperHandler for BlockEpollHandler { if !rate_limit_reached { self.process_queue_submit().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to process queue (submit): {:?}", - e + "Failed to process queue (submit): {e:?}" )) })?; } @@ -602,8 +594,7 @@ impl EpollHelperHandler for BlockEpollHandler { // and restart processing the queue. rate_limiter.event_handler().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to process rate limiter event: {:?}", - e + "Failed to process rate limiter event: {e:?}" )) })?; @@ -616,8 +607,7 @@ impl EpollHelperHandler for BlockEpollHandler { } _ => { return Err(EpollHelperError::HandleEvent(anyhow!( - "Unexpected event: {}", - ev_type + "Unexpected event: {ev_type}" ))); } } @@ -671,7 +661,7 @@ impl Block { ) -> io::Result { let (disk_nsectors, avail_features, acked_features, config, paused) = if let Some(state) = state { - info!("Restoring virtio-block {}", id); + info!("Restoring virtio-block {id}"); ( state.disk_nsectors, state.avail_features, @@ -685,9 +675,8 @@ impl Block { .map_err(|e| io::Error::other(format!("Failed getting disk size: {e}")))?; if disk_size % SECTOR_SIZE != 0 { warn!( - "Disk size {} is not a multiple of sector size {}; \ - the remainder will not be visible to the guest.", - disk_size, SECTOR_SIZE + "Disk size {disk_size} is not a multiple of sector size {SECTOR_SIZE}; \ + the remainder will not be visible to the guest." ); } @@ -708,7 +697,7 @@ impl Block { } let topology = disk_image.topology(); - info!("Disk topology: {:?}", topology); + info!("Disk topology: {topology:?}"); let logical_block_size = if topology.logical_block_size > 512 { topology.logical_block_size @@ -939,7 +928,7 @@ impl VirtioDevice for Block { .disk_image .new_async_io(queue_size as u32) .map_err(|e| { - error!("failed to create new AsyncIo: {}", e); + error!("failed to create new AsyncIo: {e}"); ActivateError::BadActivate })?, disk_nsectors: self.disk_nsectors, diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index 6f237b5eed..37b10b91b8 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -275,7 +275,7 @@ impl ConsoleEpollHandler { self.interrupt_cb .trigger(VirtioInterruptType::Queue(queue_index)) .map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -328,7 +328,7 @@ impl ConsoleEpollHandler { } pty_write_out.store(true, Ordering::Release); out.flush() - .map_err(|e| anyhow!("Failed to flush PTY: {:?}", e)) + .map_err(|e| anyhow!("Failed to flush PTY: {e:?}")) } else { Ok(()) } @@ -365,52 +365,39 @@ impl EpollHelperHandler for ConsoleEpollHandler { match ev_type { INPUT_QUEUE_EVENT => { self.input_queue_evt.read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}")) })?; let needs_notification = self.process_input_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to process input queue : {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to process input queue : {e:?}")) })?; if needs_notification { self.signal_used_queue(0).map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {e:?}")) })?; } } OUTPUT_QUEUE_EVENT => { self.output_queue_evt.read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}")) })?; let needs_notification = self.process_output_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to process output queue : {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to process output queue : {e:?}")) })?; if needs_notification { self.signal_used_queue(1).map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {e:?}")) })?; } } CONFIG_EVENT => { self.config_evt.read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get config event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get config event: {e:?}")) })?; self.interrupt_cb .trigger(VirtioInterruptType::Config) .map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to signal console driver: {:?}", - e + "Failed to signal console driver: {e:?}" )) })?; } @@ -420,10 +407,7 @@ impl EpollHelperHandler for ConsoleEpollHandler { .unwrap() .read_exact(&mut [0]) .map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to get resize event: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to get resize event: {e:?}")) })?; self.resizer.update_console_size(); } @@ -438,15 +422,13 @@ impl EpollHelperHandler for ConsoleEpollHandler { let needs_notification = self.process_input_queue().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to process input queue : {:?}", - e + "Failed to process input queue : {e:?}" )) })?; if needs_notification { self.signal_used_queue(0).map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used queue: {:?}", - e + "Failed to signal used queue: {e:?}" )) })?; } @@ -614,7 +596,7 @@ impl Console { ) -> io::Result<(Console, Arc)> { let (avail_features, acked_features, config, in_buffer, paused) = if let Some(state) = state { - info!("Restoring virtio-console {}", id); + info!("Restoring virtio-console {id}"); ( state.avail_features, state.acked_features, @@ -733,7 +715,7 @@ impl VirtioDevice for Console { if self.common.feature_acked(VIRTIO_CONSOLE_F_SIZE) && let Err(e) = interrupt_cb.trigger(VirtioInterruptType::Config) { - error!("Failed to signal console driver: {:?}", e); + error!("Failed to signal console driver: {e:?}"); } let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index c0d24902a1..1a6a79b99f 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -233,13 +233,13 @@ impl VirtioCommon { } let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(|e| { - error!("failed creating kill EventFd: {}", e); + error!("failed creating kill EventFd: {e}"); ActivateError::BadActivate })?; self.kill_evt = Some(kill_evt); let pause_evt = EventFd::new(EFD_NONBLOCK).map_err(|e| { - error!("failed creating pause EventFd: {}", e); + error!("failed creating pause EventFd: {e}"); ActivateError::BadActivate })?; self.pause_evt = Some(pause_evt); @@ -265,7 +265,7 @@ impl VirtioCommon { if let Some(mut threads) = self.epoll_threads.take() { for t in threads.drain(..) { if let Err(e) = t.join() { - error!("Error joining thread: {:?}", e); + error!("Error joining thread: {e:?}"); } } } @@ -279,7 +279,7 @@ impl VirtioCommon { if let Some(mut threads) = self.epoll_threads.take() { for t in threads.drain(..) { if let Err(e) = t.join() { - error!("Error joining thread: {:?}", e); + error!("Error joining thread: {e:?}"); } } } diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index f2795a8cff..ca6b1a1be0 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -394,7 +394,7 @@ impl Request { .memory() .read_obj(req_addr as GuestAddress) .map_err(Error::GuestMemory)?; - debug!("Attach request 0x{:x?}", req); + debug!("Attach request 0x{req:x?}"); // Copy the value to use it as a proper reference. let domain_id = req.domain; @@ -448,7 +448,7 @@ impl Request { .memory() .read_obj(req_addr as GuestAddress) .map_err(Error::GuestMemory)?; - debug!("Detach request 0x{:x?}", req); + debug!("Detach request 0x{req:x?}"); // Copy the value to use it as a proper reference. let domain_id = req.domain; @@ -467,7 +467,7 @@ impl Request { .memory() .read_obj(req_addr as GuestAddress) .map_err(Error::GuestMemory)?; - debug!("Map request 0x{:x?}", req); + debug!("Map request 0x{req:x?}"); // Copy the value to use it as a proper reference. let domain_id = req.domain; @@ -530,7 +530,7 @@ impl Request { .memory() .read_obj(req_addr as GuestAddress) .map_err(Error::GuestMemory)?; - debug!("Unmap request 0x{:x?}", req); + debug!("Unmap request 0x{req:x?}"); // Copy the value to use it as a proper reference. let domain_id = req.domain; @@ -586,7 +586,7 @@ impl Request { .memory() .read_obj(req_addr as GuestAddress) .map_err(Error::GuestMemory)?; - debug!("Probe request 0x{:x?}", req); + debug!("Probe request 0x{req:x?}"); let probe_prop = VirtioIommuProbeProperty { type_: VIRTIO_IOMMU_PROBE_T_RESV_MEM, @@ -718,7 +718,7 @@ impl IommuEpollHandler { self.interrupt_cb .trigger(VirtioInterruptType::Queue(queue_index)) .map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -746,28 +746,23 @@ impl EpollHelperHandler for IommuEpollHandler { match ev_type { REQUEST_Q_EVENT => { self.request_queue_evt.read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}")) })?; let needs_notification = self.request_queue().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to process request queue : {:?}", - e + "Failed to process request queue : {e:?}" )) })?; if needs_notification { self.signal_used_queue(0).map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {e:?}")) })?; } } _ => { return Err(EpollHelperError::HandleEvent(anyhow!( - "Unexpected event: {}", - ev_type + "Unexpected event: {ev_type}" ))); } } @@ -800,7 +795,7 @@ pub struct IommuMapping { impl DmaRemapping for IommuMapping { fn translate_gva(&self, id: u32, addr: u64) -> std::result::Result { - debug!("Translate GVA addr 0x{:x}", addr); + debug!("Translate GVA addr 0x{addr:x}"); if let Some(domain_id) = self.endpoints.read().unwrap().get(&id) { if let Some(domain) = self.domains.read().unwrap().get(domain_id) { // Directly return identity mapping in case the domain is in @@ -812,7 +807,7 @@ impl DmaRemapping for IommuMapping { for (&key, &value) in domain.mappings.iter() { if addr >= key && addr < key + value.size { let new_addr = addr - key + value.gpa; - debug!("Into GPA addr 0x{:x}", new_addr); + debug!("Into GPA addr 0x{new_addr:x}"); return Ok(new_addr); } } @@ -827,7 +822,7 @@ impl DmaRemapping for IommuMapping { } fn translate_gpa(&self, id: u32, addr: u64) -> std::result::Result { - debug!("Translate GPA addr 0x{:x}", addr); + debug!("Translate GPA addr 0x{addr:x}"); if let Some(domain_id) = self.endpoints.read().unwrap().get(&id) { if let Some(domain) = self.domains.read().unwrap().get(domain_id) { // Directly return identity mapping in case the domain is in @@ -839,7 +834,7 @@ impl DmaRemapping for IommuMapping { for (&key, &value) in domain.mappings.iter() { if addr >= value.gpa && addr < value.gpa + value.size { let new_addr = addr - value.gpa + key; - debug!("Into GVA addr 0x{:x}", new_addr); + debug!("Into GVA addr 0x{new_addr:x}"); return Ok(new_addr); } } @@ -908,7 +903,7 @@ impl Iommu { ) -> io::Result<(Self, Arc)> { let (mut avail_features, acked_features, endpoints, domains, paused) = if let Some(state) = state { - info!("Restoring virtio-iommu {}", id); + info!("Restoring virtio-iommu {id}"); ( state.avail_features, state.acked_features, @@ -1015,7 +1010,7 @@ impl Iommu { } let bypass = self.config.bypass == 1; - info!("Updating bypass mode to {}", bypass); + info!("Updating bypass mode to {bypass}"); self.mapping.bypass.store(bypass, Ordering::Release); } diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 7893be6b1a..6205c822ac 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -235,8 +235,7 @@ impl VirtioMemConfig { fn resize(&mut self, size: u64) -> result::Result<(), Error> { if self.requested_size == size { return Err(Error::ResizeError(anyhow!( - "new size 0x{:x} and requested_size are identical", - size + "new size 0x{size:x} and requested_size are identical" ))); } else if size > self.region_size { return Err(Error::ResizeError(anyhow!( @@ -426,7 +425,7 @@ impl MemEpollHandler { }; if res != 0 { let err = io::Error::last_os_error(); - error!("Deallocating file space failed: {}", err); + error!("Deallocating file space failed: {err}"); return Err(Error::DiscardMemoryRange(err)); } } @@ -444,7 +443,7 @@ impl MemEpollHandler { }; if res != 0 { let err = io::Error::last_os_error(); - error!("Advising kernel about pages range failed: {}", err); + error!("Advising kernel about pages range failed: {err}"); return Err(Error::DiscardMemoryRange(err)); } } @@ -476,7 +475,7 @@ impl MemEpollHandler { } if !plug && let Err(e) = self.discard_memory_range(offset, size) { - error!("failed discarding memory range: {:?}", e); + error!("failed discarding memory range: {e:?}"); return VIRTIO_MEM_RESP_ERROR; } @@ -506,10 +505,7 @@ impl MemEpollHandler { } else { for (_, handler) in handlers.iter() { if let Err(e) = handler.unmap(addr, size) { - error!( - "failed DMA unmapping addr 0x{:x} size 0x{:x}: {}", - addr, size, e - ); + error!("failed DMA unmapping addr 0x{addr:x} size 0x{size:x}: {e}"); return VIRTIO_MEM_RESP_ERROR; } } @@ -523,7 +519,7 @@ impl MemEpollHandler { fn unplug_all(&mut self) -> u16 { let mut config = self.config.lock().unwrap(); if let Err(e) = self.discard_memory_range(0, config.region_size) { - error!("failed discarding memory range: {:?}", e); + error!("failed discarding memory range: {e:?}"); return VIRTIO_MEM_RESP_ERROR; } @@ -592,7 +588,7 @@ impl MemEpollHandler { fn signal(&self, int_type: VirtioInterruptType) -> result::Result<(), DeviceError> { self.interrupt_cb.trigger(int_type).map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -650,25 +646,21 @@ impl EpollHelperHandler for MemEpollHandler { match ev_type { QUEUE_AVAIL_EVENT => { self.queue_evt.read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}")) })?; let needs_notification = self.process_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {e:?}")) })?; if needs_notification { self.signal(VirtioInterruptType::Queue(0)).map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {e:?}")) })?; } } _ => { return Err(EpollHelperError::HandleEvent(anyhow!( - "Unexpected event: {}", - ev_type + "Unexpected event: {ev_type}" ))); } } @@ -727,7 +719,7 @@ impl Mem { } let (avail_features, acked_features, config, paused) = if let Some(state) = state { - info!("Restoring virtio-mem {}", id); + info!("Restoring virtio-mem {id}"); *(blocks_state.lock().unwrap()) = state.blocks_state.clone(); ( state.avail_features, @@ -801,14 +793,14 @@ impl Mem { pub fn resize(&mut self, size: u64) -> result::Result<(), Error> { let mut config = self.config.lock().unwrap(); config.resize(size).map_err(|e| { - Error::ResizeError(anyhow!("Failed to update virtio configuration: {:?}", e)) + Error::ResizeError(anyhow!("Failed to update virtio configuration: {e:?}")) })?; if let Some(interrupt_cb) = self.interrupt_cb.as_ref() { interrupt_cb .trigger(VirtioInterruptType::Config) .map_err(|e| { - Error::ResizeError(anyhow!("Failed to signal the guest about resize: {:?}", e)) + Error::ResizeError(anyhow!("Failed to signal the guest about resize: {e:?}")) }) } else { Ok(()) diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index bbe0a8b37f..598bce71d9 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -66,7 +66,7 @@ impl NetCtrlEpollHandler { self.interrupt_cb .trigger(VirtioInterruptType::Queue(queue_index)) .map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -96,32 +96,28 @@ impl EpollHelperHandler for NetCtrlEpollHandler { let mem = self.mem.memory(); self.queue_evt.read().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to get control queue event: {:?}", - e + "Failed to get control queue event: {e:?}" )) })?; self.ctrl_q .process(mem.deref(), &mut self.queue, self.access_platform.as_ref()) .map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to process control queue: {:?}", - e + "Failed to process control queue: {e:?}" )) })?; match self.queue.needs_notification(mem.deref()) { Ok(true) => { self.signal_used_queue(self.queue_index).map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Error signalling that control queue was used: {:?}", - e + "Error signalling that control queue was used: {e:?}" )) })?; } Ok(false) => {} Err(e) => { return Err(EpollHelperError::HandleEvent(anyhow!( - "Error getting notification state of control queue: {}", - e + "Error getting notification state of control queue: {e}" ))); } }; @@ -184,7 +180,7 @@ impl NetEpollHandler { self.interrupt_cb .trigger(VirtioInterruptType::Queue(queue_index)) .map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -192,7 +188,7 @@ impl NetEpollHandler { fn handle_rx_event(&mut self) -> result::Result<(), DeviceError> { let queue_evt = &self.queue_evt_pair.0; if let Err(e) = queue_evt.read() { - error!("Failed to get rx queue event: {:?}", e); + error!("Failed to get rx queue event: {e:?}"); } self.net.rx_desc_avail = true; @@ -316,30 +312,29 @@ impl EpollHelperHandler for NetEpollHandler { RX_QUEUE_EVENT => { self.driver_awake = true; self.handle_rx_event().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Error processing RX queue: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Error processing RX queue: {e:?}")) })?; } TX_QUEUE_EVENT => { let queue_evt = &self.queue_evt_pair.1; if let Err(e) = queue_evt.read() { - error!("Failed to get tx queue event: {:?}", e); + error!("Failed to get tx queue event: {e:?}"); } self.driver_awake = true; self.handle_tx_event().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Error processing TX queue: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Error processing TX queue: {e:?}")) })?; } TX_TAP_EVENT => { self.handle_tx_event().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Error processing TX queue (TAP event): {:?}", - e + "Error processing TX queue (TAP event): {e:?}" )) })?; } RX_TAP_EVENT => { self.handle_rx_tap_event().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Error processing tap queue: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Error processing tap queue: {e:?}")) })?; } RX_RATE_LIMITER_EVENT => { @@ -348,8 +343,7 @@ impl EpollHelperHandler for NetEpollHandler { // TAP fd for further processing if some RX buffers are available rate_limiter.event_handler().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Error from 'rate_limiter.event_handler()': {:?}", - e + "Error from 'rate_limiter.event_handler()': {e:?}" )) })?; @@ -362,8 +356,7 @@ impl EpollHelperHandler for NetEpollHandler { ) .map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Error register_listener with `RX_RATE_LIMITER_EVENT`: {:?}", - e + "Error register_listener with `RX_RATE_LIMITER_EVENT`: {e:?}" )) })?; @@ -381,14 +374,13 @@ impl EpollHelperHandler for NetEpollHandler { // and restart processing the queue. rate_limiter.event_handler().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Error from 'rate_limiter.event_handler()': {:?}", - e + "Error from 'rate_limiter.event_handler()': {e:?}" )) })?; self.driver_awake = true; self.process_tx().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Error processing TX queue: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Error processing TX queue: {e:?}")) })?; } else { return Err(EpollHelperError::HandleEvent(anyhow!( @@ -398,8 +390,7 @@ impl EpollHelperHandler for NetEpollHandler { } _ => { return Err(EpollHelperError::HandleEvent(anyhow!( - "Unexpected event: {}", - ev_type + "Unexpected event: {ev_type}" ))); } } @@ -452,7 +443,7 @@ impl Net { let (avail_features, acked_features, config, queue_sizes, paused) = if let Some(state) = state { - info!("Restoring virtio-net {}", id); + info!("Restoring virtio-net {id}"); ( state.avail_features, state.acked_features, @@ -670,7 +661,7 @@ impl Drop for Net { if let Some(thread) = self.ctrl_queue_epoll_thread.take() && let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); + error!("Error joining thread: {e:?}"); } } } @@ -777,7 +768,7 @@ impl VirtioDevice for Net { #[cfg(not(fuzzing))] tap.set_offload(virtio_features_to_tap_offload(self.common.acked_features)) .map_err(|e| { - error!("Error programming tap offload: {:?}", e); + error!("Error programming tap offload: {e:?}"); ActivateError::BadActivate })?; diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 8e41f0ec05..ff17fa6901 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -170,7 +170,7 @@ impl PmemEpollHandler { let status_code = match self.disk.sync_all() { Ok(()) => VIRTIO_PMEM_RESP_TYPE_OK, Err(e) => { - error!("failed flushing disk image: {}", e); + error!("failed flushing disk image: {e}"); VIRTIO_PMEM_RESP_TYPE_EIO } }; @@ -179,7 +179,7 @@ impl PmemEpollHandler { match desc_chain.memory().write_obj(resp, req.status_addr) { Ok(_) => size_of::() as u32, Err(e) => { - error!("bad guest memory address: {}", e); + error!("bad guest memory address: {e}"); 0 } } @@ -190,7 +190,7 @@ impl PmemEpollHandler { 0 } Err(e) => { - error!("Failed to parse available descriptor chain: {:?}", e); + error!("Failed to parse available descriptor chain: {e:?}"); 0 } }; @@ -208,7 +208,7 @@ impl PmemEpollHandler { self.interrupt_cb .trigger(VirtioInterruptType::Queue(0)) .map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -236,26 +236,22 @@ impl EpollHelperHandler for PmemEpollHandler { match ev_type { QUEUE_AVAIL_EVENT => { self.queue_evt.read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}")) })?; let needs_notification = self.process_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {e:?}")) })?; if needs_notification { self.signal_used_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {e:?}")) })?; } } _ => { return Err(EpollHelperError::HandleEvent(anyhow!( - "Unexpected event: {}", - ev_type + "Unexpected event: {ev_type}" ))); } } @@ -298,7 +294,7 @@ impl Pmem { state: Option, ) -> io::Result { let (avail_features, acked_features, config, paused) = if let Some(state) = state { - info!("Restoring virtio-pmem {}", id); + info!("Restoring virtio-pmem {id}"); ( state.avail_features, state.acked_features, @@ -395,7 +391,7 @@ impl VirtioDevice for Pmem { let (kill_evt, pause_evt) = self.common.dup_eventfds(); if let Some(disk) = self.disk.as_ref() { let disk = disk.try_clone().map_err(|e| { - error!("failed cloning pmem disk: {}", e); + error!("failed cloning pmem disk: {e}"); ActivateError::BadActivate })?; diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 6dccb2de19..3ca58778bb 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -95,7 +95,7 @@ impl RngEpollHandler { self.interrupt_cb .trigger(VirtioInterruptType::Queue(0)) .map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -123,24 +123,20 @@ impl EpollHelperHandler for RngEpollHandler { match ev_type { QUEUE_AVAIL_EVENT => { self.queue_evt.read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}")) })?; let needs_notification = self.process_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {e:?}")) })?; if needs_notification { self.signal_used_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {e:?}")) })?; } } _ => { return Err(EpollHelperError::HandleEvent(anyhow!( - "Unexpected event: {}", - ev_type + "Unexpected event: {ev_type}" ))); } } @@ -176,7 +172,7 @@ impl Rng { let random_file = File::open(path)?; let (avail_features, acked_features, paused) = if let Some(state) = state { - info!("Restoring virtio-rng {}", id); + info!("Restoring virtio-rng {id}"); (state.avail_features, state.acked_features, true) } else { let mut avail_features = 1u64 << VIRTIO_F_VERSION_1; @@ -257,7 +253,7 @@ impl VirtioDevice for Rng { if let Some(file) = self.random_file.as_ref() { let random_file = file.try_clone().map_err(|e| { - error!("failed cloning rng source: {}", e); + error!("failed cloning rng source: {e}"); ActivateError::BadActivate })?; diff --git a/virtio-devices/src/thread_helper.rs b/virtio-devices/src/thread_helper.rs index 74aaddf8d4..df05a60ed6 100644 --- a/virtio-devices/src/thread_helper.rs +++ b/virtio-devices/src/thread_helper.rs @@ -39,18 +39,18 @@ where if !seccomp_filter.is_empty() && let Err(e) = apply_filter(&seccomp_filter) { - error!("Error applying seccomp filter: {:?}", e); + error!("Error applying seccomp filter: {e:?}"); thread_exit_evt.write(1).ok(); return; } match std::panic::catch_unwind(AssertUnwindSafe(f)) { Err(_) => { - error!("{} thread panicked", thread_name); + error!("{thread_name} thread panicked"); thread_exit_evt.write(1).ok(); } Ok(r) => { if let Err(e) = r { - error!("Error running worker: {:?}", e); + error!("Error running worker: {e:?}"); thread_exit_evt.write(1).ok(); } } @@ -58,7 +58,7 @@ where }) .map(|thread| epoll_threads.push(thread)) .map_err(|e| { - error!("Failed to spawn thread for {}: {}", name, e); + error!("Failed to spawn thread for {name}: {e}"); ActivateError::ThreadSpawn(e) }) } diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 549453a5d6..dcf5891f12 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -211,30 +211,30 @@ impl VirtioPciCommonConfig { } fn read_common_config_byte(&self, offset: u64) -> u8 { - debug!("read_common_config_byte: offset 0x{:x}", offset); + debug!("read_common_config_byte: offset 0x{offset:x}"); // The driver is only allowed to do aligned, properly sized access. match offset { 0x14 => self.driver_status, 0x15 => self.config_generation, _ => { - warn!("invalid virtio config byte read: 0x{:x}", offset); + warn!("invalid virtio config byte read: 0x{offset:x}"); 0 } } } fn write_common_config_byte(&mut self, offset: u64, value: u8) { - debug!("write_common_config_byte: offset 0x{:x}", offset); + debug!("write_common_config_byte: offset 0x{offset:x}"); match offset { 0x14 => self.driver_status = value, _ => { - warn!("invalid virtio config byte write: 0x{:x}", offset); + warn!("invalid virtio config byte write: 0x{offset:x}"); } } } fn read_common_config_word(&self, offset: u64, queues: &[Queue]) -> u16 { - debug!("read_common_config_word: offset 0x{:x}", offset); + debug!("read_common_config_word: offset 0x{offset:x}"); match offset { 0x10 => self.msix_config.load(Ordering::Acquire), 0x12 => queues.len() as u16, // num_queues @@ -244,14 +244,14 @@ impl VirtioPciCommonConfig { 0x1c => u16::from(self.with_queue(queues, |q| q.ready()).unwrap_or(false)), 0x1e => self.queue_select, // notify_off _ => { - warn!("invalid virtio register word read: 0x{:x}", offset); + warn!("invalid virtio register word read: 0x{offset:x}"); 0 } } } fn write_common_config_word(&mut self, offset: u64, value: u16, queues: &mut [Queue]) { - debug!("write_common_config_word: offset 0x{:x}", offset); + debug!("write_common_config_word: offset 0x{offset:x}"); match offset { 0x10 => self.msix_config.store(value, Ordering::Release), 0x16 => self.queue_select = value, @@ -286,13 +286,13 @@ impl VirtioPciCommonConfig { } }), _ => { - warn!("invalid virtio register word write: 0x{:x}", offset); + warn!("invalid virtio register word write: 0x{offset:x}"); } } } fn read_common_config_dword(&self, offset: u64, device: Arc>) -> u32 { - debug!("read_common_config_dword: offset 0x{:x}", offset); + debug!("read_common_config_dword: offset 0x{offset:x}"); match offset { 0x00 => self.device_feature_select, 0x04 => { @@ -307,7 +307,7 @@ impl VirtioPciCommonConfig { } 0x08 => self.driver_feature_select, _ => { - warn!("invalid virtio register dword read: 0x{:x}", offset); + warn!("invalid virtio register dword read: 0x{offset:x}"); 0 } } @@ -320,7 +320,7 @@ impl VirtioPciCommonConfig { queues: &mut [Queue], device: Arc>, ) { - debug!("write_common_config_dword: offset 0x{:x}", offset); + debug!("write_common_config_dword: offset 0x{offset:x}"); match offset { 0x00 => self.device_feature_select = value, @@ -344,18 +344,18 @@ impl VirtioPciCommonConfig { 0x30 => self.with_queue_mut(queues, |q| q.set_used_ring_address(Some(value), None)), 0x34 => self.with_queue_mut(queues, |q| q.set_used_ring_address(None, Some(value))), _ => { - warn!("invalid virtio register dword write: 0x{:x}", offset); + warn!("invalid virtio register dword write: 0x{offset:x}"); } } } fn read_common_config_qword(&self, _offset: u64) -> u64 { - debug!("read_common_config_qword: offset 0x{:x}", _offset); + debug!("read_common_config_qword: offset 0x{_offset:x}"); 0 // Assume the guest has no reason to read write-only registers. } fn write_common_config_qword(&mut self, offset: u64, value: u64, queues: &mut [Queue]) { - debug!("write_common_config_qword: offset 0x{:x}", offset); + debug!("write_common_config_qword: offset 0x{offset:x}"); let low = Some((value & 0xffff_ffff) as u32); let high = Some((value >> 32) as u32); @@ -365,7 +365,7 @@ impl VirtioPciCommonConfig { 0x28 => self.with_queue_mut(queues, |q| q.set_avail_ring_address(low, high)), 0x30 => self.with_queue_mut(queues, |q| q.set_used_ring_address(low, high)), _ => { - warn!("invalid virtio register qword write: 0x{:x}", offset); + warn!("invalid virtio register qword write: 0x{offset:x}"); } } } diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index b05020bb47..68adae664c 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -394,10 +394,7 @@ impl VirtioPciDevice { let mut queue_evts = Vec::new(); for _ in locked_device.queue_max_sizes().iter() { queue_evts.push(EventFd::new(EFD_NONBLOCK).map_err(|e| { - VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( - "Failed creating eventfd: {}", - e - )) + VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!("Failed creating eventfd: {e}")) })?) } let num_queues = locked_device.queue_max_sizes().len(); @@ -421,16 +418,14 @@ impl VirtioPciDevice { }) .map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( - "Failed creating MSI interrupt group: {}", - e + "Failed creating MSI interrupt group: {e}" )) })?; let msix_state = vm_migration::state_from_id(snapshot.as_ref(), pci::MSIX_CONFIG_ID) .map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( - "Failed to get MsixConfigState from Snapshot: {}", - e + "Failed to get MsixConfigState from Snapshot: {e}" )) })?; @@ -469,8 +464,7 @@ impl VirtioPciDevice { vm_migration::state_from_id(snapshot.as_ref(), pci::PCI_CONFIGURATION_ID).map_err( |e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( - "Failed to get PciConfigurationState from Snapshot: {}", - e + "Failed to get PciConfigurationState from Snapshot: {e}" )) }, )?; @@ -493,8 +487,7 @@ impl VirtioPciDevice { vm_migration::state_from_id(snapshot.as_ref(), VIRTIO_PCI_COMMON_CONFIG_ID).map_err( |e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( - "Failed to get VirtioPciCommonConfigState from Snapshot: {}", - e + "Failed to get VirtioPciCommonConfigState from Snapshot: {e}" )) }, )?; @@ -522,8 +515,7 @@ impl VirtioPciDevice { .transpose() .map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( - "Failed to get VirtioPciDeviceState from Snapshot: {}", - e + "Failed to get VirtioPciDeviceState from Snapshot: {e}" )) })?; @@ -613,8 +605,7 @@ impl VirtioPciDevice { { virtio_pci_device.activate().map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( - "Failed activating the device: {}", - e + "Failed activating the device: {e}" )) })?; } @@ -795,7 +786,7 @@ impl VirtioPciDevice { } if !queue.is_valid(self.memory.memory().deref()) { - error!("Queue {} is not valid", queue_index); + error!("Queue {queue_index} is not valid"); } queues.push(( @@ -1182,7 +1173,7 @@ impl PciDevice for VirtioPciDevice { } // Handled with ioeventfds. #[cfg(not(feature = "sev_snp"))] - error!("Unexpected write to notification BAR: offset = 0x{:x}", o); + error!("Unexpected write to notification BAR: offset = 0x{o:x}"); } o if (MSIX_TABLE_BAR_OFFSET..MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE).contains(&o) => { if let Some(msix_config) = &self.msix_config { diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 6ca4f7ddaa..46ef48781d 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -132,7 +132,7 @@ impl Vdpa { backend_features, paused, ) = if let Some(state) = state { - info!("Restoring vDPA {}", id); + info!("Restoring vDPA {id}"); vhost.set_backend_features_acked(state.backend_features); vhost @@ -404,14 +404,14 @@ impl VirtioDevice for Vdpa { fn read_config(&self, offset: u64, data: &mut [u8]) { assert!(self.vhost.is_some()); if let Err(e) = self.vhost.as_ref().unwrap().get_config(offset as u32, data) { - error!("Failed reading virtio config: {}", e); + error!("Failed reading virtio config: {e}"); } } fn write_config(&mut self, offset: u64, data: &[u8]) { assert!(self.vhost.is_some()); if let Err(e) = self.vhost.as_ref().unwrap().set_config(offset as u32, data) { - error!("Failed writing virtio config: {}", e); + error!("Failed writing virtio config: {e}"); } } @@ -433,7 +433,7 @@ impl VirtioDevice for Vdpa { fn reset(&mut self) -> Option> { if let Err(e) = self.reset_vdpa() { - error!("Failed to reset vhost-vdpa: {:?}", e); + error!("Failed to reset vhost-vdpa: {e:?}"); return None; } @@ -487,7 +487,7 @@ impl Snapshottable for Vdpa { } let snapshot = Snapshot::new_from_state(&self.state().map_err(|e| { - MigratableError::Snapshot(anyhow!("Error snapshotting vDPA device: {:?}", e)) + MigratableError::Snapshot(anyhow!("Error snapshotting vDPA device: {e:?}")) })?)?; // Force the vhost handler to be dropped in order to close the vDPA @@ -509,7 +509,7 @@ impl Migratable for Vdpa { if self.backend_features & (1 << VHOST_BACKEND_F_SUSPEND) != 0 { assert!(self.vhost.is_some()); self.vhost.as_ref().unwrap().suspend().map_err(|e| { - MigratableError::StartMigration(anyhow!("Error suspending vDPA device: {:?}", e)) + MigratableError::StartMigration(anyhow!("Error suspending vDPA device: {e:?}")) }) } else { Err(MigratableError::StartMigration(anyhow!( @@ -565,7 +565,7 @@ impl ExternalDmaMapping for VdpaDmaMapping std::result::Result<(), std::io::Error> { - debug!("DMA unmap iova 0x{:x} size 0x{:x}", iova, size); + debug!("DMA unmap iova 0x{iova:x} size 0x{size:x}"); self.device .lock() .unwrap() diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 7c0e20c1ac..20309080c9 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -81,7 +81,7 @@ impl Blk { config, paused, ) = if let Some(state) = state { - info!("Restoring vhost-user-block {}", id); + info!("Restoring vhost-user-block {id}"); vu.set_protocol_features_vhost_user( state.acked_features, @@ -135,8 +135,7 @@ impl Blk { if num_queues > backend_num_queues { error!( - "vhost-user-blk requested too many queues ({}) since the backend only supports {}\n", - num_queues, backend_num_queues + "vhost-user-blk requested too many queues ({num_queues}) since the backend only supports {backend_num_queues}\n" ); return Err(Error::BadQueueNum); } @@ -216,13 +215,13 @@ impl Drop for Blk { if let Some(kill_evt) = self.common.kill_evt.take() && let Err(e) = kill_evt.write(1) { - error!("failed to kill vhost-user-blk: {:?}", e); + error!("failed to kill vhost-user-blk: {e:?}"); } self.common.wait_for_epoll_threads(); if let Some(thread) = self.epoll_thread.take() && let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); + error!("Error joining thread: {e:?}"); } } } @@ -275,7 +274,7 @@ impl VirtioDevice for Blk { .set_config(offset as u32, VhostUserConfigFlags::WRITABLE, data) .map_err(Error::VhostUserSetConfig) { - error!("Failed setting vhost-user-blk configuration: {:?}", e); + error!("Failed setting vhost-user-blk configuration: {e:?}"); } } @@ -331,7 +330,7 @@ impl VirtioDevice for Blk { if let Some(vu) = &self.vu_common.vu && let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {:?}", e); + error!("Failed to reset vhost-user daemon: {e:?}"); return None; } diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index c420bb7a33..bf724316cb 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -109,7 +109,7 @@ impl Fs { config, paused, ) = if let Some(state) = state { - info!("Restoring vhost-user-fs {}", id); + info!("Restoring vhost-user-fs {id}"); vu.set_protocol_features_vhost_user( state.acked_features, @@ -148,8 +148,7 @@ impl Fs { if num_queues > backend_num_queues { error!( - "vhost-user-fs requested too many queues ({}) since the backend only supports {}\n", - num_queues, backend_num_queues + "vhost-user-fs requested too many queues ({num_queues}) since the backend only supports {backend_num_queues}\n" ); return Err(Error::BadQueueNum); } @@ -230,7 +229,7 @@ impl Drop for Fs { if let Some(thread) = self.epoll_thread.take() && let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); + error!("Error joining thread: {e:?}"); } } } @@ -311,7 +310,7 @@ impl VirtioDevice for Fs { if let Some(vu) = &self.vu_common.vu && let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {:?}", e); + error!("Failed to reset vhost-user daemon: {e:?}"); return None; } diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 14aa173b78..f9dcb363f3 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -265,8 +265,7 @@ impl EpollHelperHandler for VhostUserEpollHandle HUP_CONNECTION_EVENT => { self.reconnect(helper).map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "failed to reconnect vhost-user backend: {:?}", - e + "failed to reconnect vhost-user backend: {e:?}" )) })?; } @@ -274,8 +273,7 @@ impl EpollHelperHandler for VhostUserEpollHandle if let Some(backend_req_handler) = self.backend_req_handler.as_mut() { backend_req_handler.handle_request().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "Failed to handle request from vhost-user backend: {:?}", - e + "Failed to handle request from vhost-user backend: {e:?}" )) })?; } @@ -412,7 +410,7 @@ impl VhostUserCommon { pub fn pause(&mut self) -> std::result::Result<(), MigratableError> { if let Some(vu) = &self.vu { vu.lock().unwrap().pause_vhost_user().map_err(|e| { - MigratableError::Pause(anyhow!("Error pausing vhost-user backend: {:?}", e)) + MigratableError::Pause(anyhow!("Error pausing vhost-user backend: {e:?}")) }) } else { Ok(()) @@ -422,7 +420,7 @@ impl VhostUserCommon { pub fn resume(&mut self) -> std::result::Result<(), MigratableError> { if let Some(vu) = &self.vu { vu.lock().unwrap().resume_vhost_user().map_err(|e| { - MigratableError::Resume(anyhow!("Error resuming vhost-user backend: {:?}", e)) + MigratableError::Resume(anyhow!("Error resuming vhost-user backend: {e:?}")) }) } else { Ok(()) @@ -454,8 +452,7 @@ impl VhostUserCommon { .start_dirty_log(last_ram_addr) .map_err(|e| { MigratableError::StartDirtyLog(anyhow!( - "Error starting migration for vhost-user backend: {:?}", - e + "Error starting migration for vhost-user backend: {e:?}" )) }) } else { @@ -472,8 +469,7 @@ impl VhostUserCommon { if let Some(vu) = &self.vu { vu.lock().unwrap().stop_dirty_log().map_err(|e| { MigratableError::StopDirtyLog(anyhow!( - "Error stopping migration for vhost-user backend: {:?}", - e + "Error stopping migration for vhost-user backend: {e:?}" )) }) } else { @@ -490,8 +486,7 @@ impl VhostUserCommon { let last_ram_addr = guest_memory.memory().last_addr().raw_value(); vu.lock().unwrap().dirty_log(last_ram_addr).map_err(|e| { MigratableError::DirtyLog(anyhow!( - "Error retrieving dirty ranges from vhost-user backend: {:?}", - e + "Error retrieving dirty ranges from vhost-user backend: {e:?}" )) }) } else { @@ -518,8 +513,7 @@ impl VhostUserCommon { if let Some(kill_evt) = kill_evt { kill_evt.write(1).map_err(|e| { MigratableError::CompleteMigration(anyhow!( - "Error killing vhost-user thread: {:?}", - e + "Error killing vhost-user thread: {e:?}" )) })?; } diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 0f4561bca5..4f9d366c10 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -90,7 +90,7 @@ impl Net { config, paused, ) = if let Some(state) = state { - info!("Restoring vhost-user-net {}", id); + info!("Restoring vhost-user-net {id}"); // The backend acknowledged features must not contain // VIRTIO_NET_F_MAC since we don't expect the backend @@ -169,8 +169,7 @@ impl Net { if num_queues > backend_num_queues { error!( - "vhost-user-net requested too many queues ({}) since the backend only supports {}\n", - num_queues, backend_num_queues + "vhost-user-net requested too many queues ({num_queues}) since the backend only supports {backend_num_queues}\n" ); return Err(Error::BadQueueNum); } @@ -246,7 +245,7 @@ impl Drop for Net { if let Some(kill_evt) = self.common.kill_evt.take() && let Err(e) = kill_evt.write(1) { - error!("failed to kill vhost-user-net: {:?}", e); + error!("failed to kill vhost-user-net: {e:?}"); } self.common.wait_for_epoll_threads(); @@ -254,13 +253,13 @@ impl Drop for Net { if let Some(thread) = self.epoll_thread.take() && let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); + error!("Error joining thread: {e:?}"); } if let Some(thread) = self.ctrl_queue_epoll_thread.take() && let Err(e) = thread.join() { - error!("Error joining thread: {:?}", e); + error!("Error joining thread: {e:?}"); } } } @@ -386,7 +385,7 @@ impl VirtioDevice for Net { if let Some(vu) = &self.vu_common.vu && let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {:?}", e); + error!("Failed to reset vhost-user daemon: {e:?}"); return None; } diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 05034d0ec5..eba0292175 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -409,10 +409,7 @@ impl VhostUserHandle { } }; - error!( - "Failed connecting the backend after trying for 1 minute: {:?}", - err - ); + error!("Failed connecting the backend after trying for 1 minute: {err:?}"); Err(Error::VhostUserConnect) } } diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 4d073c4923..385b5a2425 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -108,7 +108,7 @@ where self.interrupt_cb .trigger(VirtioInterruptType::Queue(queue_index)) .map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -137,7 +137,7 @@ where } } Err(e) => { - warn!("vsock: RX queue error: {:?}", e); + warn!("vsock: RX queue error: {e:?}"); 0 } }; @@ -170,7 +170,7 @@ where ) { Ok(pkt) => pkt, Err(e) => { - error!("vsock: error reading TX packet: {:?}", e); + error!("vsock: error reading TX packet: {e:?}"); self.queues[1] .add_used(desc_chain.memory(), desc_chain.head_index(), 0) .map_err(DeviceError::QueueAddUsed)?; @@ -226,7 +226,7 @@ where Some(evset) => evset, None => { let evbits = event.events; - warn!("epoll: ignoring unknown event set: 0x{:x}", evbits); + warn!("epoll: ignoring unknown event set: 0x{evbits:x}"); return Ok(()); } }; @@ -236,25 +236,22 @@ where RX_QUEUE_EVENT => { debug!("vsock: RX queue event"); self.queue_evts[0].read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get RX queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get RX queue event: {e:?}")) })?; if self.backend.read().unwrap().has_pending_rx() { self.process_rx().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to process RX queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to process RX queue: {e:?}")) })?; } } TX_QUEUE_EVENT => { debug!("vsock: TX queue event"); self.queue_evts[1].read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get TX queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get TX queue event: {e:?}")) })?; self.process_tx().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to process TX queue: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to process TX queue: {e:?}")) })?; // The backend may have queued up responses to the packets we sent during TX queue @@ -262,17 +259,14 @@ where // into RX buffers. if self.backend.read().unwrap().has_pending_rx() { self.process_rx().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to process RX queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to process RX queue: {e:?}")) })?; } } EVT_QUEUE_EVENT => { debug!("vsock: EVT queue event"); self.queue_evts[2].read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get EVT queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get EVT queue event: {e:?}")) })?; } BACKEND_EVENT => { @@ -284,14 +278,11 @@ where // returning an error) at some point in the past, now is the time to try walking the // TX queue again. self.process_tx().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to process TX queue: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to process TX queue: {e:?}")) })?; if self.backend.read().unwrap().has_pending_rx() { self.process_rx().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to process RX queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to process RX queue: {e:?}")) })?; } } @@ -341,7 +332,7 @@ where state: Option, ) -> io::Result> { let (avail_features, acked_features, paused) = if let Some(state) = state { - info!("Restoring virtio-vsock {}", id); + info!("Restoring virtio-vsock {id}"); (state.avail_features, state.acked_features, true) } else { let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_F_IN_ORDER); diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index 5ea8efb9ba..e2e9fe2e22 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -335,7 +335,7 @@ impl VsockEpollListener for VsockMuxer { // appropriate to retry, by calling into epoll_wait(). continue; } - warn!("vsock: failed to consume muxer epoll event: {}", e); + warn!("vsock: failed to consume muxer epoll event: {e}"); } } break 'epoll; @@ -383,10 +383,7 @@ impl VsockMuxer { /// Handle/dispatch an epoll event to its listener. /// fn handle_event(&mut self, fd: RawFd, event_set: epoll::Events) { - debug!( - "vsock: muxer processing event: fd={}, event_set={:?}", - fd, event_set - ); + debug!("vsock: muxer processing event: fd={fd}, event_set={event_set:?}"); match self.listener_map.get_mut(&fd) { // This event needs to be forwarded to a `MuxerConnection` that is listening for @@ -430,7 +427,7 @@ impl VsockMuxer { self.add_listener(stream.as_raw_fd(), EpollListener::LocalStream(stream)) }) .unwrap_or_else(|err| { - warn!("vsock: unable to accept local connection: {:?}", err); + warn!("vsock: unable to accept local connection: {err:?}"); }); } @@ -478,16 +475,13 @@ impl VsockMuxer { ) }) .unwrap_or_else(|err| { - info!("vsock: error adding local-init connection: {:?}", err); + info!("vsock: error adding local-init connection: {err:?}"); }) } } _ => { - info!( - "vsock: unexpected event: fd={:?}, event_set={:?}", - fd, event_set - ); + info!("vsock: unexpected event: fd={fd:?}, event_set={event_set:?}"); } } } @@ -662,10 +656,7 @@ impl VsockMuxer { epoll::Event::new(epoll::Events::empty(), 0), ) .unwrap_or_else(|err| { - warn!( - "vosck muxer: error removing epoll listener for fd {:?}: {:?}", - fd, err - ); + warn!("vosck muxer: error removing epoll listener for fd {fd:?}: {err:?}"); }); } @@ -760,7 +751,7 @@ impl VsockMuxer { } Err(err) => { conn.kill(); - warn!("vsock: unable to ack host connection: {:?}", err); + warn!("vsock: unable to ack host connection: {err:?}"); } }; } @@ -870,10 +861,7 @@ impl VsockMuxer { peer_port, }); if !pushed { - warn!( - "vsock: muxer.rxq full; dropping RST packet for lp={}, pp={}", - local_port, peer_port - ); + warn!("vsock: muxer.rxq full; dropping RST packet for lp={local_port}, pp={peer_port}"); } } } diff --git a/virtio-devices/src/watchdog.rs b/virtio-devices/src/watchdog.rs index 23a33a3e57..cb4b5845c5 100644 --- a/virtio-devices/src/watchdog.rs +++ b/virtio-devices/src/watchdog.rs @@ -93,8 +93,7 @@ impl WatchdogEpollHandler { // If this is the first "ping" then setup the timer if self.last_ping_time.lock().unwrap().is_none() { info!( - "First ping received. Starting timer (every {} seconds)", - WATCHDOG_TIMER_INTERVAL + "First ping received. Starting timer (every {WATCHDOG_TIMER_INTERVAL} seconds)" ); timerfd_setup(&self.timer, WATCHDOG_TIMER_INTERVAL).map_err(Error::TimerfdSetup)?; } @@ -113,7 +112,7 @@ impl WatchdogEpollHandler { self.interrupt_cb .trigger(VirtioInterruptType::Queue(0)) .map_err(|e| { - error!("Failed to signal used queue: {:?}", e); + error!("Failed to signal used queue: {e:?}"); DeviceError::FailedSignalingUsedQueue(e) }) } @@ -142,18 +141,15 @@ impl EpollHelperHandler for WatchdogEpollHandler { match ev_type { QUEUE_AVAIL_EVENT => { self.queue_evt.read().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {e:?}")) })?; let needs_notification = self.process_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {:?}", e)) + EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {e:?}")) })?; if needs_notification { self.signal_used_queue().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to signal used queue: {:?}", - e - )) + EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {e:?}")) })?; } } @@ -162,22 +158,21 @@ impl EpollHelperHandler for WatchdogEpollHandler { // the number of times this event has elapsed since the last read. let mut buf = vec![0; 8]; self.timer.read_exact(&mut buf).map_err(|e| { - EpollHelperError::HandleEvent(anyhow!("Error reading from timer fd: {:}", e)) + EpollHelperError::HandleEvent(anyhow!("Error reading from timer fd: {e:}")) })?; if let Some(last_ping_time) = self.last_ping_time.lock().unwrap().as_ref() { let now = Instant::now(); let gap = now.duration_since(*last_ping_time).as_secs(); if gap > WATCHDOG_TIMEOUT { - error!("Watchdog triggered: {} seconds since last ping", gap); + error!("Watchdog triggered: {gap} seconds since last ping"); self.reset_evt.write(1).ok(); } } } _ => { return Err(EpollHelperError::HandleEvent(anyhow!( - "Unexpected event: {}", - ev_type + "Unexpected event: {ev_type}" ))); } } @@ -214,7 +209,7 @@ impl Watchdog { ) -> io::Result { let mut last_ping_time = None; let (avail_features, acked_features, paused) = if let Some(state) = state { - info!("Restoring virtio-watchdog {}", id); + info!("Restoring virtio-watchdog {id}"); // When restoring enable the watchdog if it was previously enabled. // We reset the timer to ensure that we don't unnecessarily reboot @@ -229,7 +224,7 @@ impl Watchdog { }; let timer_fd = timerfd_create().map_err(|e| { - error!("Failed to create timer fd {}", e); + error!("Failed to create timer fd {e}"); e })?; // SAFETY: timer_fd is a valid fd @@ -339,12 +334,12 @@ impl VirtioDevice for Watchdog { let (kill_evt, pause_evt) = self.common.dup_eventfds(); let reset_evt = self.reset_evt.try_clone().map_err(|e| { - error!("Failed to clone reset_evt eventfd: {}", e); + error!("Failed to clone reset_evt eventfd: {e}"); ActivateError::BadActivate })?; let timer = self.timer.try_clone().map_err(|e| { - error!("Failed to clone timer fd: {}", e); + error!("Failed to clone timer fd: {e}"); ActivateError::BadActivate })?; @@ -392,20 +387,17 @@ impl Pausable for Watchdog { fn pause(&mut self) -> result::Result<(), MigratableError> { info!("Watchdog paused - disabling timer"); timerfd_setup(&self.timer, 0) - .map_err(|e| MigratableError::Pause(anyhow!("Error clearing timer: {:?}", e)))?; + .map_err(|e| MigratableError::Pause(anyhow!("Error clearing timer: {e:?}")))?; self.common.pause() } fn resume(&mut self) -> result::Result<(), MigratableError> { // Reset the timer on pause if it was previously used if self.last_ping_time.lock().unwrap().is_some() { - info!( - "Watchdog resumed - enabling timer (every {} seconds)", - WATCHDOG_TIMER_INTERVAL - ); + info!("Watchdog resumed - enabling timer (every {WATCHDOG_TIMER_INTERVAL} seconds)"); self.last_ping_time.lock().unwrap().replace(Instant::now()); timerfd_setup(&self.timer, WATCHDOG_TIMER_INTERVAL) - .map_err(|e| MigratableError::Resume(anyhow!("Error setting timer: {:?}", e)))?; + .map_err(|e| MigratableError::Resume(anyhow!("Error setting timer: {e:?}")))?; } self.common.resume() } diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 00f322636a..05bcc3131e 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -84,7 +84,7 @@ impl SnapshotData { T: Deserialize<'a>, { serde_json::from_str(&self.state) - .map_err(|e| MigratableError::Restore(anyhow!("Error deserialising: {}", e))) + .map_err(|e| MigratableError::Restore(anyhow!("Error deserialising: {e}"))) } /// Create from state that can be serialized @@ -93,7 +93,7 @@ impl SnapshotData { T: Serialize, { let state = serde_json::to_string(state) - .map_err(|e| MigratableError::Snapshot(anyhow!("Error serialising: {}", e)))?; + .map_err(|e| MigratableError::Snapshot(anyhow!("Error serialising: {e}")))?; Ok(SnapshotData { state }) } diff --git a/vmm/src/api/http/mod.rs b/vmm/src/api/http/mod.rs index 243429154d..456610ce90 100644 --- a/vmm/src/api/http/mod.rs +++ b/vmm/src/api/http/mod.rs @@ -338,7 +338,7 @@ fn start_http_thread( apply_filter(&api_seccomp_filter) .map_err(VmmError::ApplySeccompFilter) .map_err(|e| { - error!("Error applying seccomp filter: {:?}", e); + error!("Error applying seccomp filter: {e:?}"); exit_evt.write(1).ok(); e })?; @@ -350,7 +350,7 @@ fn start_http_thread( .restrict_self() .map_err(VmmError::ApplyLandlock) .map_err(|e| { - error!("Error applying landlock to http-server thread: {:?}", e); + error!("Error applying landlock to http-server thread: {e:?}"); exit_evt.write(1).ok(); e })?; @@ -365,7 +365,7 @@ fn start_http_thread( if let Err(e) = server.respond(server_request.process(|request| { handle_http_request(request, &api_notifier, &api_sender) })) { - error!("HTTP server error on response: {}", e); + error!("HTTP server error on response: {e}"); } } } @@ -374,10 +374,7 @@ fn start_http_thread( return; } Err(e) => { - error!( - "HTTP server error on retrieving incoming request. Error: {}", - e - ); + error!("HTTP server error on retrieving incoming request. Error: {e}"); } } } diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 5ef63ffa39..e0ffc2f8e1 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -426,7 +426,7 @@ impl ApiAction for VmAddDevice { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmAddDevice {:?}", config); + info!("API request event: VmAddDevice {config:?}"); let response = vmm .vm_add_device(config) @@ -463,7 +463,7 @@ impl ApiAction for AddDisk { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: AddDisk {:?}", config); + info!("API request event: AddDisk {config:?}"); let response = vmm .vm_add_disk(config) @@ -500,7 +500,7 @@ impl ApiAction for VmAddFs { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmAddFs {:?}", config); + info!("API request event: VmAddFs {config:?}"); let response = vmm .vm_add_fs(config) @@ -537,7 +537,7 @@ impl ApiAction for VmAddPmem { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmAddPmem {:?}", config); + info!("API request event: VmAddPmem {config:?}"); let response = vmm .vm_add_pmem(config) @@ -574,7 +574,7 @@ impl ApiAction for VmAddNet { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmAddNet {:?}", config); + info!("API request event: VmAddNet {config:?}"); let response = vmm .vm_add_net(config) @@ -611,7 +611,7 @@ impl ApiAction for VmAddVdpa { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmAddVdpa {:?}", config); + info!("API request event: VmAddVdpa {config:?}"); let response = vmm .vm_add_vdpa(config) @@ -648,7 +648,7 @@ impl ApiAction for VmAddVsock { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmAddVsock {:?}", config); + info!("API request event: VmAddVsock {config:?}"); let response = vmm .vm_add_vsock(config) @@ -685,7 +685,7 @@ impl ApiAction for VmAddUserDevice { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmAddUserDevice {:?}", config); + info!("API request event: VmAddUserDevice {config:?}"); let response = vmm .vm_add_user_device(config) @@ -827,7 +827,7 @@ impl ApiAction for VmCreate { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmCreate {:?}", config); + info!("API request event: VmCreate {config:?}"); let response = vmm .vm_create(config) @@ -1032,7 +1032,7 @@ impl ApiAction for VmReceiveMigration { fn request(&self, data: Self::RequestBody, response_sender: Sender) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmReceiveMigration {:?}", data); + info!("API request event: VmReceiveMigration {data:?}"); let response = vmm .vm_receive_migration(data) @@ -1069,7 +1069,7 @@ impl ApiAction for VmRemoveDevice { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmRemoveDevice {:?}", remove_device_data); + info!("API request event: VmRemoveDevice {remove_device_data:?}"); let response = vmm .vm_remove_device(remove_device_data.id) @@ -1106,7 +1106,7 @@ impl ApiAction for VmResize { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmResize {:?}", resize_data); + info!("API request event: VmResize {resize_data:?}"); let response = vmm .vm_resize( @@ -1147,7 +1147,7 @@ impl ApiAction for VmResizeZone { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmResizeZone {:?}", resize_zone_data); + info!("API request event: VmResizeZone {resize_zone_data:?}"); let response = vmm .vm_resize_zone(resize_zone_data.id, resize_zone_data.desired_ram) @@ -1184,7 +1184,7 @@ impl ApiAction for VmRestore { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmRestore {:?}", config); + info!("API request event: VmRestore {config:?}"); let response = vmm .vm_restore(config) @@ -1250,7 +1250,7 @@ impl ApiAction for VmSendMigration { fn request(&self, data: Self::RequestBody, response_sender: Sender) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmSendMigration {:?}", data); + info!("API request event: VmSendMigration {data:?}"); let response = vmm .vm_send_migration(data) @@ -1287,7 +1287,7 @@ impl ApiAction for VmShutdown { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmShutdown {:?}", config); + info!("API request event: VmShutdown {config:?}"); let response = vmm .vm_shutdown() @@ -1324,7 +1324,7 @@ impl ApiAction for VmSnapshot { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmSnapshot {:?}", config); + info!("API request event: VmSnapshot {config:?}"); let response = vmm .vm_snapshot(&config.destination_url) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index af89539f04..6ecdd3c800 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2523,7 +2523,7 @@ impl VmConfig { tty_consoles.push("debug-console"); }; if tty_consoles.len() > 1 { - warn!("Using TTY output for multiple consoles: {:?}", tty_consoles); + warn!("Using TTY output for multiple consoles: {tty_consoles:?}"); } if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() { diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 2b52f950c0..3c54a7c4d4 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -497,7 +497,7 @@ impl Snapshottable for Vcpu { let saved_state = self .vcpu .state() - .map_err(|e| MigratableError::Snapshot(anyhow!("Could not get vCPU state {:?}", e)))?; + .map_err(|e| MigratableError::Snapshot(anyhow!("Could not get vCPU state {e:?}")))?; self.saved_state = Some(saved_state.clone()); @@ -574,10 +574,7 @@ impl BusDevice for CpuManager { } } _ => { - warn!( - "Unexpected offset for accessing CPU manager device: {:#}", - offset - ); + warn!("Unexpected offset for accessing CPU manager device: {offset:#}"); } } } @@ -608,17 +605,14 @@ impl BusDevice for CpuManager { if data[0] & (1 << CPU_EJECT_FLAG) == 1 << CPU_EJECT_FLAG && let Err(e) = self.remove_vcpu(self.selected_cpu) { - error!("Error removing vCPU: {:?}", e); + error!("Error removing vCPU: {e:?}"); } } else { warn!("Out of range vCPU id: {}", self.selected_cpu); } } _ => { - warn!( - "Unexpected offset for accessing CPU manager device: {:#}", - offset - ); + warn!("Unexpected offset for accessing CPU manager device: {offset:#}"); } } None @@ -827,7 +821,7 @@ impl CpuManager { } fn create_vcpu(&mut self, cpu_id: u32, snapshot: Option) -> Result>> { - info!("Creating vCPU: cpu_id = {}", cpu_id); + info!("Creating vCPU: cpu_id = {cpu_id}"); #[cfg(target_arch = "x86_64")] let topology = self.get_vcpu_topology(); @@ -851,11 +845,11 @@ impl CpuManager { vcpu.init(&self.vm)?; let state: CpuState = snapshot.to_state().map_err(|e| { - Error::VcpuCreate(anyhow!("Could not get vCPU state from snapshot {:?}", e)) + Error::VcpuCreate(anyhow!("Could not get vCPU state from snapshot {e:?}")) })?; vcpu.vcpu .set_state(&state) - .map_err(|e| Error::VcpuCreate(anyhow!("Could not set the vCPU state {:?}", e)))?; + .map_err(|e| Error::VcpuCreate(anyhow!("Could not set the vCPU state {e:?}")))?; vcpu.saved_state = Some(state); } @@ -1035,7 +1029,7 @@ impl CpuManager { #[cfg(target_arch = "x86_64")] let interrupt_controller_clone = self.interrupt_controller.as_ref().cloned(); - info!("Starting vCPU: cpu_id = {}", vcpu_id); + info!("Starting vCPU: cpu_id = {vcpu_id}"); let handle = Some( thread::Builder::new() @@ -1066,7 +1060,7 @@ impl CpuManager { if !vcpu_seccomp_filter.is_empty() && let Err(e) = apply_filter(&vcpu_seccomp_filter).map_err(Error::ApplySeccompFilter) { - error!("Error applying seccomp filter: {:?}", e); + error!("Error applying seccomp filter: {e:?}"); return; } @@ -1132,7 +1126,7 @@ impl CpuManager { match vcpu.lock().as_ref().unwrap().vcpu.nmi() { Ok(()) => {}, Err(e) => { - error!("Error when inject nmi {}", e); + error!("Error when inject nmi {e}"); break; } } @@ -1303,7 +1297,7 @@ impl CpuManager { } fn remove_vcpu(&mut self, cpu_id: u32) -> Result<()> { - info!("Removing vCPU: cpu_id = {}", cpu_id); + info!("Removing vCPU: cpu_id = {cpu_id}"); let state = &mut self.vcpu_states[usize::try_from(cpu_id).unwrap()]; state.kill.store(true, Ordering::SeqCst); state.signal_thread(); @@ -1335,7 +1329,7 @@ impl CpuManager { pub fn start_restored_vcpus(&mut self) -> Result<()> { self.activate_vcpus(self.vcpus.len() as u32, false, Some(true)) .map_err(|e| { - Error::StartRestoreVcpu(anyhow!("Failed to start restored vCPUs: {:#?}", e)) + Error::StartRestoreVcpu(anyhow!("Failed to start restored vCPUs: {e:#?}")) })?; Ok(()) @@ -2324,8 +2318,7 @@ impl Pausable for CpuManager { if !self.config.kvm_hyperv { vcpu.vcpu.notify_guest_clock_paused().map_err(|e| { MigratableError::Pause(anyhow!( - "Could not notify guest it has been paused {:?}", - e + "Could not notify guest it has been paused {e:?}" )) })?; } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 7a5adb1c3f..9c14f28eb6 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2484,7 +2484,7 @@ impl DeviceManager { ) -> DeviceManagerResult>> { // Create TPM Device let tpm = devices::tpm::Tpm::new(tpm_path.to_str().unwrap().to_string()).map_err(|e| { - DeviceManagerError::CreateTpmDevice(anyhow!("Failed to create TPM Device : {:?}", e)) + DeviceManagerError::CreateTpmDevice(anyhow!("Failed to create TPM Device : {e:?}")) })?; let tpm = Arc::new(Mutex::new(tpm)); @@ -2606,7 +2606,7 @@ impl DeviceManager { id }; - info!("Creating virtio-block device: {:?}", disk_cfg); + info!("Creating virtio-block device: {disk_cfg:?}"); let (virtio_device, migratable_device) = if disk_cfg.vhost_user { if is_hotplug { @@ -2854,7 +2854,7 @@ impl DeviceManager { net_cfg.id = Some(id.clone()); id }; - info!("Creating virtio-net device: {:?}", net_cfg); + info!("Creating virtio-net device: {net_cfg:?}"); let (virtio_device, migratable_device) = if net_cfg.vhost_user { let socket = net_cfg.vhost_socket.as_ref().unwrap().clone(); @@ -3020,7 +3020,7 @@ impl DeviceManager { // Add virtio-rng if required let rng_config = self.config.lock().unwrap().rng.clone(); if let Some(rng_path) = rng_config.src.to_str() { - info!("Creating virtio-rng device: {:?}", rng_config); + info!("Creating virtio-rng device: {rng_config:?}"); let id = String::from(RNG_DEVICE_NAME); let virtio_rng_device = Arc::new(Mutex::new( @@ -3070,7 +3070,7 @@ impl DeviceManager { id }; - info!("Creating virtio-fs device: {:?}", fs_cfg); + info!("Creating virtio-fs device: {fs_cfg:?}"); let mut node = device_node!(id); @@ -3137,14 +3137,14 @@ impl DeviceManager { id }; - info!("Creating virtio-pmem device: {:?}", pmem_cfg); + info!("Creating virtio-pmem device: {pmem_cfg:?}"); let mut node = device_node!(id); // Look for the id in the device tree. If it can be found, that means // the device is being restored, otherwise it's created from scratch. let region_range = if let Some(node) = self.device_tree.lock().unwrap().get(&id) { - info!("Restoring virtio-pmem {} resources", id); + info!("Restoring virtio-pmem {id} resources"); let mut region_range: Option<(u64, u64)> = None; for resource in node.resources.iter() { @@ -3157,7 +3157,7 @@ impl DeviceManager { region_range = Some((*base, *size)); } _ => { - error!("Unexpected resource {:?} for {}", resource, id); + error!("Unexpected resource {resource:?} for {id}"); } } } @@ -3323,7 +3323,7 @@ impl DeviceManager { id }; - info!("Creating virtio-vsock device: {:?}", vsock_cfg); + info!("Creating virtio-vsock device: {vsock_cfg:?}"); let socket_path = vsock_cfg .socket @@ -3387,7 +3387,7 @@ impl DeviceManager { let mut mm = mm.lock().unwrap(); for (memory_zone_id, memory_zone) in mm.memory_zones_mut().iter_mut() { if let Some(virtio_mem_zone) = memory_zone.virtio_mem_zone_mut() { - info!("Creating virtio-mem device: id = {}", memory_zone_id); + info!("Creating virtio-mem device: id = {memory_zone_id}"); let node_id = numa_node_id_from_memory_zone_id(&self.numa_nodes, memory_zone_id) .map(|i| i as u16); @@ -3486,7 +3486,7 @@ impl DeviceManager { if let Some(balloon_config) = &self.config.lock().unwrap().balloon { let id = String::from(BALLOON_DEVICE_NAME); - info!("Creating virtio-balloon device: id = {}", id); + info!("Creating virtio-balloon device: id = {id}"); let virtio_balloon_device = Arc::new(Mutex::new( virtio_devices::Balloon::new( @@ -3532,7 +3532,7 @@ impl DeviceManager { } let id = String::from(WATCHDOG_DEVICE_NAME); - info!("Creating virtio-watchdog device: id = {}", id); + info!("Creating virtio-watchdog device: id = {id}"); let virtio_watchdog_device = Arc::new(Mutex::new( virtio_devices::Watchdog::new( @@ -3576,7 +3576,7 @@ impl DeviceManager { id }; - info!("Creating vDPA device: {:?}", vdpa_cfg); + info!("Creating vDPA device: {vdpa_cfg:?}"); let device_path = vdpa_cfg .path @@ -4189,7 +4189,7 @@ impl DeviceManager { let id = String::from(PVPANIC_DEVICE_NAME); let pci_segment_id = 0x0_u16; - info!("Creating pvpanic device {}", id); + info!("Creating pvpanic device {id}"); let (pci_segment_id, pci_device_bdf, resources) = self.pci_resources(&id, pci_segment_id)?; @@ -4280,7 +4280,7 @@ impl DeviceManager { // the device is being restored, otherwise it's created from scratch. let (pci_device_bdf, resources) = if let Some(node) = self.device_tree.lock().unwrap().get(id) { - info!("Restoring virtio-pci {} resources", id); + info!("Restoring virtio-pci {id} resources"); let pci_device_bdf: PciBdf = node .pci_bdf .ok_or(DeviceManagerError::MissingDeviceNodePciBdf)?; @@ -4529,10 +4529,7 @@ impl DeviceManager { } pub fn eject_device(&mut self, pci_segment_id: u16, device_id: u8) -> DeviceManagerResult<()> { - info!( - "Ejecting device_id = {} on segment_id={}", - device_id, pci_segment_id - ); + info!("Ejecting device_id = {device_id} on segment_id={pci_segment_id}"); // Convert the device ID into the corresponding b/d/f. let pci_device_bdf = PciBdf::new(pci_segment_id, 0, device_id, 0); @@ -5400,16 +5397,10 @@ impl BusDevice for DeviceManager { assert_eq!(data.len(), PSEG_FIELD_SIZE); data.copy_from_slice(&(self.selected_segment as u32).to_le_bytes()); } - _ => error!( - "Accessing unknown location at base 0x{:x}, offset 0x{:x}", - base, offset - ), + _ => error!("Accessing unknown location at base 0x{base:x}, offset 0x{offset:x}"), } - debug!( - "PCI_HP_REG_R: base 0x{:x}, offset 0x{:x}, data {:?}", - base, offset, data - ) + debug!("PCI_HP_REG_R: base 0x{base:x}, offset 0x{offset:x}, data {data:?}") } fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { @@ -5423,7 +5414,7 @@ impl BusDevice for DeviceManager { while slot_bitmap > 0 { let slot_id = slot_bitmap.trailing_zeros(); if let Err(e) = self.eject_device(self.selected_segment as u16, slot_id as u8) { - error!("Failed ejecting device {}: {:?}", slot_id, e); + error!("Failed ejecting device {slot_id}: {e:?}"); } slot_bitmap &= !(1 << slot_id); } @@ -5443,16 +5434,10 @@ impl BusDevice for DeviceManager { } self.selected_segment = selected_segment; } - _ => error!( - "Accessing unknown location at base 0x{:x}, offset 0x{:x}", - base, offset - ), + _ => error!("Accessing unknown location at base 0x{base:x}, offset 0x{offset:x}"), } - debug!( - "PCI_HP_REG_W: base 0x{:x}, offset 0x{:x}, data {:?}", - base, offset, data - ); + debug!("PCI_HP_REG_W: base 0x{base:x}, offset 0x{offset:x}, data {data:?}"); None } @@ -5463,7 +5448,7 @@ impl Drop for DeviceManager { // Wake up the DeviceManager threads (mainly virtio device workers), // to avoid deadlock on waiting for paused/parked worker threads. if let Err(e) = self.resume() { - error!("Error resuming DeviceManager: {:?}", e); + error!("Error resuming DeviceManager: {e:?}"); } for handle in self.virtio_devices.drain(..) { diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 76b63d10be..a16c2480f4 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -449,7 +449,7 @@ pub fn start_event_monitor_thread( apply_filter(&seccomp_filter) .map_err(Error::ApplySeccompFilter) .map_err(|e| { - error!("Error applying seccomp filter: {:?}", e); + error!("Error applying seccomp filter: {e:?}"); exit_event.write(1).ok(); e })?; @@ -460,7 +460,7 @@ pub fn start_event_monitor_thread( .restrict_self() .map_err(Error::ApplyLandlock) .map_err(|e| { - error!("Error applying landlock to event monitor thread: {:?}", e); + error!("Error applying landlock to event monitor thread: {e:?}"); exit_event.write(1).ok(); e })?; @@ -732,7 +732,7 @@ impl Vmm { if !signal_handler_seccomp_filter.is_empty() && let Err(e) = apply_filter(&signal_handler_seccomp_filter) .map_err(Error::ApplySeccompFilter) { - error!("Error applying seccomp filter: {:?}", e); + error!("Error applying seccomp filter: {e:?}"); exit_evt.write(1).ok(); return; } @@ -741,12 +741,12 @@ impl Vmm { match Landlock::new() { Ok(landlock) => { let _ = landlock.restrict_self().map_err(Error::ApplyLandlock).map_err(|e| { - error!("Error applying Landlock to signal handler thread: {:?}", e); + error!("Error applying Landlock to signal handler thread: {e:?}"); exit_evt.write(1).ok(); }); } Err(e) => { - error!("Error creating Landlock object: {:?}", e); + error!("Error creating Landlock object: {e:?}"); exit_evt.write(1).ok(); } }; @@ -764,7 +764,7 @@ impl Vmm { .map_err(Error::SignalHandlerSpawn)?, ); } - Err(e) => error!("Signal not found {}", e), + Err(e) => error!("Signal not found {e}"), } Ok(()) } @@ -845,7 +845,7 @@ impl Vmm { let vm_migration_config: VmMigrationConfig = serde_json::from_slice(&data).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error deserialising config: {}", e)) + MigratableError::MigrateReceive(anyhow!("Error deserialising config: {e}")) })?; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] @@ -857,7 +857,7 @@ impl Vmm { let config = vm_migration_config.vm_config.clone(); self.vm_config = Some(vm_migration_config.vm_config); self.console_info = Some(pre_create_console_devices(self).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error creating console devices: {:?}", e)) + MigratableError::MigrateReceive(anyhow!("Error creating console devices: {e:?}")) })?); if self @@ -869,7 +869,7 @@ impl Vmm { .landlock_enable { apply_landlock(self.vm_config.as_ref().unwrap().clone()).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error applying landlock: {:?}", e)) + MigratableError::MigrateReceive(anyhow!("Error applying landlock: {e:?}")) })?; } @@ -884,8 +884,7 @@ impl Vmm { ) .map_err(|e| { MigratableError::MigrateReceive(anyhow!( - "Error creating hypervisor VM from snapshot: {:?}", - e + "Error creating hypervisor VM from snapshot: {e:?}" )) })?; @@ -909,8 +908,7 @@ impl Vmm { ) .map_err(|e| { MigratableError::MigrateReceive(anyhow!( - "Error creating MemoryManager from snapshot: {:?}", - e + "Error creating MemoryManager from snapshot: {e:?}" )) })?; @@ -935,21 +933,21 @@ impl Vmm { .read_exact(&mut data) .map_err(MigratableError::MigrateSocket)?; let snapshot: Snapshot = serde_json::from_slice(&data).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error deserialising snapshot: {}", e)) + MigratableError::MigrateReceive(anyhow!("Error deserialising snapshot: {e}")) })?; let exit_evt = self.exit_evt.try_clone().map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error cloning exit EventFd: {}", e)) + MigratableError::MigrateReceive(anyhow!("Error cloning exit EventFd: {e}")) })?; let reset_evt = self.reset_evt.try_clone().map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error cloning reset EventFd: {}", e)) + MigratableError::MigrateReceive(anyhow!("Error cloning reset EventFd: {e}")) })?; #[cfg(feature = "guest_debug")] let debug_evt = self.vm_debug_evt.try_clone().map_err(|e| { MigratableError::MigrateReceive(anyhow!("Error cloning debug EventFd: {}", e)) })?; let activate_evt = self.activate_evt.try_clone().map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error cloning activate EventFd: {}", e)) + MigratableError::MigrateReceive(anyhow!("Error cloning activate EventFd: {e}")) })?; #[cfg(not(target_arch = "riscv64"))] @@ -974,13 +972,13 @@ impl Vmm { Some(snapshot), ) .map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error creating VM from snapshot: {:?}", e)) + MigratableError::MigrateReceive(anyhow!("Error creating VM from snapshot: {e:?}")) })?; // Create VM vm.restore().map_err(|e| { Response::error().write_to(socket).ok(); - MigratableError::MigrateReceive(anyhow!("Failed restoring the Vm: {}", e)) + MigratableError::MigrateReceive(anyhow!("Failed restoring the Vm: {e}")) })?; self.vm = Some(vm); @@ -1014,7 +1012,7 @@ impl Vmm { fn socket_url_to_path(url: &str) -> result::Result { url.strip_prefix("unix:") .ok_or_else(|| { - MigratableError::MigrateSend(anyhow!("Could not extract path from URL: {}", url)) + MigratableError::MigrateSend(anyhow!("Could not extract path from URL: {url}")) }) .map(|s| s.into()) } @@ -1023,19 +1021,19 @@ impl Vmm { destination_url: &str, ) -> std::result::Result { if let Some(address) = destination_url.strip_prefix("tcp:") { - info!("Connecting to TCP socket at {}", address); + info!("Connecting to TCP socket at {address}"); let socket = TcpStream::connect(address).map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error connecting to TCP socket: {}", e)) + MigratableError::MigrateSend(anyhow!("Error connecting to TCP socket: {e}")) })?; Ok(SocketStream::Tcp(socket)) } else { let path = Vmm::socket_url_to_path(destination_url)?; - info!("Connecting to UNIX socket at {:?}", path); + info!("Connecting to UNIX socket at {path:?}"); let socket = UnixStream::connect(&path).map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error connecting to UNIX socket: {}", e)) + MigratableError::MigrateSend(anyhow!("Error connecting to UNIX socket: {e}")) })?; Ok(SocketStream::Unix(socket)) @@ -1047,13 +1045,12 @@ impl Vmm { ) -> std::result::Result { if let Some(address) = receiver_url.strip_prefix("tcp:") { let listener = TcpListener::bind(address).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error binding to TCP socket: {}", e)) + MigratableError::MigrateReceive(anyhow!("Error binding to TCP socket: {e}")) })?; let (socket, _addr) = listener.accept().map_err(|e| { MigratableError::MigrateReceive(anyhow!( - "Error accepting connection on TCP socket: {}", - e + "Error accepting connection on TCP socket: {e}" )) })?; @@ -1061,19 +1058,18 @@ impl Vmm { } else { let path = Vmm::socket_url_to_path(receiver_url)?; let listener = UnixListener::bind(&path).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error binding to UNIX socket: {}", e)) + MigratableError::MigrateReceive(anyhow!("Error binding to UNIX socket: {e}")) })?; let (socket, _addr) = listener.accept().map_err(|e| { MigratableError::MigrateReceive(anyhow!( - "Error accepting connection on UNIX socket: {}", - e + "Error accepting connection on UNIX socket: {e}" )) })?; // Remove the UNIX socket file after accepting the connection std::fs::remove_file(&path).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error removing UNIX socket file: {}", e)) + MigratableError::MigrateReceive(anyhow!("Error removing UNIX socket file: {e}")) })?; Ok(SocketStream::Unix(socket)) @@ -1147,7 +1143,7 @@ impl Vmm { }, ) .map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error generating common cpuid': {:?}", e)) + MigratableError::MigrateSend(anyhow!("Error generating common cpuid': {e:?}")) })? }; @@ -1207,7 +1203,7 @@ impl Vmm { // Try at most 5 passes of dirty memory sending const MAX_DIRTY_MIGRATIONS: usize = 5; for i in 0..MAX_DIRTY_MIGRATIONS { - info!("Dirty memory migration {} of {}", i, MAX_DIRTY_MIGRATIONS); + info!("Dirty memory migration {i} of {MAX_DIRTY_MIGRATIONS}"); if !Self::vm_maybe_send_dirty_pages(vm, &mut socket)? { break; } @@ -1285,13 +1281,12 @@ impl Vmm { }, ) .map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error generating common cpuid: {:?}", e)) + MigratableError::MigrateReceive(anyhow!("Error generating common cpuid: {e:?}")) })? }; arch::CpuidFeatureEntry::check_cpuid_compatibility(src_vm_cpuid, dest_cpuid).map_err(|e| { MigratableError::MigrateReceive(anyhow!( - "Error checking cpu feature compatibility': {:?}", - e + "Error checking cpu feature compatibility': {e:?}" )) }) } @@ -1399,7 +1394,7 @@ impl Vmm { match dispatch_event { EpollDispatch::Unknown => { let event = event.data; - warn!("Unknown VMM loop event: {}", event); + warn!("Unknown VMM loop event: {event}"); } EpollDispatch::Exit => { info!("VM exit event"); @@ -1418,10 +1413,7 @@ impl Vmm { EpollDispatch::ActivateVirtioDevices => { if let Some(ref vm) = self.vm { let count = self.activate_evt.read().map_err(Error::EventFdRead)?; - info!( - "Trying to activate pending virtio devices: count = {}", - count - ); + info!("Trying to activate pending virtio devices: count = {count}"); vm.activate_virtio_devices() .map_err(Error::ActivateVirtioDevices)?; } @@ -1641,7 +1633,7 @@ impl RequestHandler for Vmm { self.vm_restore(source_url, vm_config, restore_cfg.prefault) .map_err(|vm_restore_err| { - error!("VM Restore failed: {:?}", vm_restore_err); + error!("VM Restore failed: {vm_restore_err:?}"); // Cleanup the VM being created while vm restore if let Err(e) = self.vm_delete() { @@ -1820,7 +1812,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { if let Err(e) = vm.resize(desired_vcpus, desired_ram, desired_balloon) { - error!("Error when resizing VM: {:?}", e); + error!("Error when resizing VM: {e:?}"); Err(e) } else { Ok(()) @@ -1847,7 +1839,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { if let Err(e) = vm.resize_zone(id, desired_ram) { - error!("Error when resizing VM: {:?}", e); + error!("Error when resizing VM: {e:?}"); Err(e) } else { Ok(()) @@ -1865,7 +1857,7 @@ impl RequestHandler for Vmm { } } - error!("Could not find the memory zone {} for the resize", id); + error!("Could not find the memory zone {id} for the resize"); Err(VmError::ResizeZone) } } @@ -1885,7 +1877,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { let info = vm.add_device(device_cfg).map_err(|e| { - error!("Error when adding new device to the VM: {:?}", e); + error!("Error when adding new device to the VM: {e:?}"); e })?; serde_json::to_vec(&info) @@ -1914,7 +1906,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { let info = vm.add_user_device(device_cfg).map_err(|e| { - error!("Error when adding new user device to the VM: {:?}", e); + error!("Error when adding new user device to the VM: {e:?}"); e })?; serde_json::to_vec(&info) @@ -1931,7 +1923,7 @@ impl RequestHandler for Vmm { fn vm_remove_device(&mut self, id: String) -> result::Result<(), VmError> { if let Some(ref mut vm) = self.vm { if let Err(e) = vm.remove_device(id) { - error!("Error when removing device from the VM: {:?}", e); + error!("Error when removing device from the VM: {e:?}"); Err(e) } else { Ok(()) @@ -1960,7 +1952,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { let info = vm.add_disk(disk_cfg).map_err(|e| { - error!("Error when adding new disk to the VM: {:?}", e); + error!("Error when adding new disk to the VM: {e:?}"); e })?; serde_json::to_vec(&info) @@ -1986,7 +1978,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { let info = vm.add_fs(fs_cfg).map_err(|e| { - error!("Error when adding new fs to the VM: {:?}", e); + error!("Error when adding new fs to the VM: {e:?}"); e })?; serde_json::to_vec(&info) @@ -2012,7 +2004,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { let info = vm.add_pmem(pmem_cfg).map_err(|e| { - error!("Error when adding new pmem device to the VM: {:?}", e); + error!("Error when adding new pmem device to the VM: {e:?}"); e })?; serde_json::to_vec(&info) @@ -2038,7 +2030,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { let info = vm.add_net(net_cfg).map_err(|e| { - error!("Error when adding new network device to the VM: {:?}", e); + error!("Error when adding new network device to the VM: {e:?}"); e })?; serde_json::to_vec(&info) @@ -2064,7 +2056,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { let info = vm.add_vdpa(vdpa_cfg).map_err(|e| { - error!("Error when adding new vDPA device to the VM: {:?}", e); + error!("Error when adding new vDPA device to the VM: {e:?}"); e })?; serde_json::to_vec(&info) @@ -2095,7 +2087,7 @@ impl RequestHandler for Vmm { if let Some(ref mut vm) = self.vm { let info = vm.add_vsock(vsock_cfg).map_err(|e| { - error!("Error when adding new vsock device to the VM: {:?}", e); + error!("Error when adding new vsock device to the VM: {e:?}"); e })?; serde_json::to_vec(&info) @@ -2112,7 +2104,7 @@ impl RequestHandler for Vmm { fn vm_counters(&mut self) -> result::Result>, VmError> { if let Some(ref mut vm) = self.vm { let info = vm.counters().map_err(|e| { - error!("Error when getting counters from the VM: {:?}", e); + error!("Error when getting counters from the VM: {e:?}"); e })?; serde_json::to_vec(&info) @@ -2222,8 +2214,7 @@ impl RequestHandler for Vmm { let mut buf = [0u8; 4]; let (_, file) = unix_socket.recv_with_fd(&mut buf).map_err(|e| { MigratableError::MigrateReceive(anyhow!( - "Error receiving slot from socket: {}", - e + "Error receiving slot from socket: {e}" )) })?; @@ -2302,7 +2293,7 @@ impl RequestHandler for Vmm { send_data_migration.clone(), ) .map_err(|migration_err| { - error!("Migration failed: {:?}", migration_err); + error!("Migration failed: {migration_err:?}"); // Stop logging dirty pages only for non-local migrations if !send_data_migration.local @@ -2323,8 +2314,7 @@ impl RequestHandler for Vmm { // Shutdown the VM after the migration succeeded self.exit_evt.write(1).map_err(|e| { MigratableError::MigrateSend(anyhow!( - "Failed shutting down the VM after migration: {:?}", - e + "Failed shutting down the VM after migration: {e:?}" )) }) } else { diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 74765cf514..9a9b7f23e6 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -472,10 +472,7 @@ impl BusDevice for MemoryManager { } } _ => { - warn!( - "Unexpected offset for accessing memory manager device: {:#}", - offset - ); + warn!("Unexpected offset for accessing memory manager device: {offset:#}"); } } } else { @@ -508,10 +505,7 @@ impl BusDevice for MemoryManager { } } _ => { - warn!( - "Unexpected offset for accessing memory manager device: {:#}", - offset - ); + warn!("Unexpected offset for accessing memory manager device: {offset:#}"); } }; None @@ -786,9 +780,8 @@ impl MemoryManager { if let Some(hotplug_size) = config.hotplug_size { if hotplugged_size > hotplug_size { error!( - "'hotplugged_size' {} can't be bigger than \ - 'hotplug_size' {}", - hotplugged_size, hotplug_size, + "'hotplugged_size' {hotplugged_size} can't be bigger than \ + 'hotplug_size' {hotplug_size}", ); return Err(Error::InvalidMemoryParameters); } @@ -861,9 +854,8 @@ impl MemoryManager { if let Some(hotplug_size) = zone.hotplug_size { if hotplugged_size > hotplug_size { error!( - "'hotplugged_size' {} can't be bigger than \ - 'hotplug_size' {}", - hotplugged_size, hotplug_size, + "'hotplugged_size' {hotplugged_size} can't be bigger than \ + 'hotplug_size' {hotplug_size}", ); return Err(Error::InvalidMemoryParameters); } @@ -1452,10 +1444,7 @@ impl MemoryManager { Self::get_prefault_align_size(backing_file, hugepages, hugepage_size)? as usize; if !is_aligned(size, page_size) { - warn!( - "Prefaulting memory size {} misaligned with page size {}", - size, page_size - ); + warn!("Prefaulting memory size {size} misaligned with page size {page_size}"); } let num_pages = size / page_size; @@ -1484,7 +1473,7 @@ impl MemoryManager { }; if ret != 0 { let e = io::Error::last_os_error(); - warn!("Failed to prefault pages: {}", e); + warn!("Failed to prefault pages: {e}"); } }); } @@ -1501,7 +1490,7 @@ impl MemoryManager { let ret = unsafe { libc::madvise(region.as_ptr() as _, size, libc::MADV_HUGEPAGE) }; if ret != 0 { let e = io::Error::last_os_error(); - warn!("Failed to mark pages as THP eligible: {}", e); + warn!("Failed to mark pages as THP eligible: {e}"); } } @@ -1642,7 +1631,7 @@ impl MemoryManager { } fn hotplug_ram_region(&mut self, size: usize) -> Result, Error> { - info!("Hotplugging new RAM: {}", size); + info!("Hotplugging new RAM: {size}"); // Check that there is a free slot if self.next_hotplug_slot >= HOTPLUG_COUNT { @@ -1739,8 +1728,7 @@ impl MemoryManager { ); info!( - "Creating userspace mapping: {:x} -> {:x} {:x}, slot {}", - guest_phys_addr, userspace_addr, memory_size, slot + "Creating userspace mapping: {guest_phys_addr:x} -> {userspace_addr:x} {memory_size:x}, slot {slot}" ); self.vm @@ -1758,7 +1746,7 @@ impl MemoryManager { }; if ret != 0 { let e = io::Error::last_os_error(); - warn!("Failed to mark mapping as MADV_DONTDUMP: {}", e); + warn!("Failed to mark mapping as MADV_DONTDUMP: {e}"); } // Mark the pages as mergeable if explicitly asked for. @@ -1780,15 +1768,14 @@ impl MemoryManager { if errno == libc::EINVAL { warn!("kernel not configured with CONFIG_KSM"); } else { - warn!("madvise error: {}", err); + warn!("madvise error: {err}"); } warn!("failed to mark pages as mergeable"); } } info!( - "Created userspace mapping: {:x} -> {:x} {:x}", - guest_phys_addr, userspace_addr, memory_size + "Created userspace mapping: {guest_phys_addr:x} -> {userspace_addr:x} {memory_size:x}" ); Ok(slot) @@ -1835,15 +1822,14 @@ impl MemoryManager { if errno == libc::EINVAL { warn!("kernel not configured with CONFIG_KSM"); } else { - warn!("madvise error: {}", err); + warn!("madvise error: {err}"); } warn!("failed to mark pages as unmergeable"); } } info!( - "Removed userspace mapping: {:x} -> {:x} {:x}", - guest_phys_addr, userspace_addr, memory_size + "Removed userspace mapping: {guest_phys_addr:x} -> {userspace_addr:x} {memory_size:x}" ); Ok(()) @@ -2119,8 +2105,7 @@ impl MemoryManager { ) .map_err(|e| { MigratableError::MigrateReceive(anyhow!( - "Error receiving memory from socket: {}", - e + "Error receiving memory from socket: {e}" )) })?; offset += bytes_read as u64; @@ -2574,7 +2559,7 @@ impl Migratable for MemoryManager { // pages touched during our bulk copy are tracked. fn start_dirty_log(&mut self) -> std::result::Result<(), MigratableError> { self.vm.start_dirty_log().map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error starting VM dirty log {}", e)) + MigratableError::MigrateSend(anyhow!("Error starting VM dirty log {e}")) })?; for r in self.guest_memory.memory().iter() { @@ -2586,7 +2571,7 @@ impl Migratable for MemoryManager { fn stop_dirty_log(&mut self) -> std::result::Result<(), MigratableError> { self.vm.stop_dirty_log().map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error stopping VM dirty log {}", e)) + MigratableError::MigrateSend(anyhow!("Error stopping VM dirty log {e}")) })?; Ok(()) @@ -2598,7 +2583,7 @@ impl Migratable for MemoryManager { let mut table = MemoryRangeTable::default(); for r in &self.guest_ram_mappings { let vm_dirty_bitmap = self.vm.get_dirty_log(r.slot, r.gpa, r.size).map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error getting VM dirty log {}", e)) + MigratableError::MigrateSend(anyhow!("Error getting VM dirty log {e}")) })?; let vmm_dirty_bitmap = match self.guest_memory.memory().find_region(GuestAddress(r.gpa)) { diff --git a/vmm/src/migration.rs b/vmm/src/migration.rs index d93b028055..3a8404bfb0 100644 --- a/vmm/src/migration.rs +++ b/vmm/src/migration.rs @@ -21,7 +21,7 @@ pub fn url_to_path(url: &str) -> std::result::Result { let path: PathBuf = url .strip_prefix("file://") .ok_or_else(|| { - MigratableError::MigrateSend(anyhow!("Could not extract path from URL: {}", url)) + MigratableError::MigrateSend(anyhow!("Could not extract path from URL: {url}")) }) .map(|s| s.into())?; diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 9c4255defa..2df1ba3ca6 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -308,7 +308,7 @@ impl SerialManager { match dispatch_event { EpollDispatch::Unknown => { let event = event.data; - warn!("Unknown serial manager loop event: {}", event); + warn!("Unknown serial manager loop event: {event}"); } EpollDispatch::Socket => { // New connection request arrived. diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index f6199fdf19..aaf58945e6 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -448,7 +448,7 @@ impl VmOps for VmOpsHandler { fn mmio_read(&self, gpa: u64, data: &mut [u8]) -> result::Result<(), HypervisorVmError> { if let Err(vm_device::BusError::MissingAddressRange) = self.mmio_bus.read(gpa, data) { - info!("Guest MMIO read to unregistered address 0x{:x}", gpa); + info!("Guest MMIO read to unregistered address 0x{gpa:x}"); } Ok(()) } @@ -456,7 +456,7 @@ impl VmOps for VmOpsHandler { fn mmio_write(&self, gpa: u64, data: &[u8]) -> result::Result<(), HypervisorVmError> { match self.mmio_bus.write(gpa, data) { Err(vm_device::BusError::MissingAddressRange) => { - info!("Guest MMIO write to unregistered address 0x{:x}", gpa); + info!("Guest MMIO write to unregistered address 0x{gpa:x}"); } Ok(Some(barrier)) => { info!("Waiting for barrier"); @@ -471,7 +471,7 @@ impl VmOps for VmOpsHandler { #[cfg(target_arch = "x86_64")] fn pio_read(&self, port: u64, data: &mut [u8]) -> result::Result<(), HypervisorVmError> { if let Err(vm_device::BusError::MissingAddressRange) = self.io_bus.read(port, data) { - info!("Guest PIO read to unregistered address 0x{:x}", port); + info!("Guest PIO read to unregistered address 0x{port:x}"); } Ok(()) } @@ -480,7 +480,7 @@ impl VmOps for VmOpsHandler { fn pio_write(&self, port: u64, data: &[u8]) -> result::Result<(), HypervisorVmError> { match self.io_bus.write(port, data) { Err(vm_device::BusError::MissingAddressRange) => { - info!("Guest PIO write to unregistered address 0x{:x}", port); + info!("Guest PIO write to unregistered address 0x{port:x}"); } Ok(Some(barrier)) => { info!("Waiting for barrier"); @@ -939,7 +939,7 @@ impl Vm { } node.memory_zones.push(memory_zone.clone()); } else { - error!("Unknown memory zone '{}'", memory_zone); + error!("Unknown memory zone '{memory_zone}'"); return Err(Error::InvalidNumaConfig); } } @@ -959,12 +959,12 @@ impl Vm { let dist = distance.distance; if !configs.iter().any(|cfg| cfg.guest_numa_id == dest) { - error!("Unknown destination NUMA node {}", dest); + error!("Unknown destination NUMA node {dest}"); return Err(Error::InvalidNumaConfig); } if node.distances.contains_key(&dest) { - error!("Destination NUMA node {} has been already set", dest); + error!("Destination NUMA node {dest} has been already set"); return Err(Error::InvalidNumaConfig); } @@ -1751,7 +1751,7 @@ impl Vm { } } - error!("Could not find the memory zone {} for the resize", id); + error!("Could not find the memory zone {id} for the resize"); Err(Error::ResizeZone) } @@ -2532,12 +2532,12 @@ impl Vm { Request::memory_fd(std::mem::size_of_val(&slot) as u64) .write_to(socket) .map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error sending memory fd request: {}", e)) + MigratableError::MigrateSend(anyhow!("Error sending memory fd request: {e}")) })?; socket .send_with_fd(&slot.to_le_bytes()[..], fd) .map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error sending memory fd: {}", e)) + MigratableError::MigrateSend(anyhow!("Error sending memory fd: {e}")) })?; Response::read_from(socket)?.ok_or_abandon( @@ -2576,8 +2576,7 @@ impl Vm { ) .map_err(|e| { MigratableError::MigrateSend(anyhow!( - "Error transferring memory to socket: {}", - e + "Error transferring memory to socket: {e}" )) })?; offset += bytes_written as u64; @@ -2766,19 +2765,19 @@ impl Pausable for Vm { let mut state = self .state .try_write() - .map_err(|e| MigratableError::Pause(anyhow!("Could not get VM state: {}", e)))?; + .map_err(|e| MigratableError::Pause(anyhow!("Could not get VM state: {e}")))?; let new_state = VmState::Paused; state .valid_transition(new_state) - .map_err(|e| MigratableError::Pause(anyhow!("Invalid transition: {:?}", e)))?; + .map_err(|e| MigratableError::Pause(anyhow!("Invalid transition: {e:?}")))?; #[cfg(target_arch = "x86_64")] { let mut clock = self .vm .get_clock() - .map_err(|e| MigratableError::Pause(anyhow!("Could not get VM clock: {}", e)))?; + .map_err(|e| MigratableError::Pause(anyhow!("Could not get VM clock: {e}")))?; clock.reset_flags(); self.saved_clock = Some(clock); } @@ -2786,7 +2785,7 @@ impl Pausable for Vm { // Before pausing the vCPUs activate any pending virtio devices that might // need activation between starting the pause (or e.g. a migration it's part of) self.activate_virtio_devices().map_err(|e| { - MigratableError::Pause(anyhow!("Error activating pending virtio devices: {:?}", e)) + MigratableError::Pause(anyhow!("Error activating pending virtio devices: {e:?}")) })?; self.cpu_manager.lock().unwrap().pause()?; @@ -2794,7 +2793,7 @@ impl Pausable for Vm { self.vm .pause() - .map_err(|e| MigratableError::Pause(anyhow!("Could not pause the VM: {}", e)))?; + .map_err(|e| MigratableError::Pause(anyhow!("Could not pause the VM: {e}")))?; *state = new_state; @@ -2808,27 +2807,27 @@ impl Pausable for Vm { let mut state = self .state .try_write() - .map_err(|e| MigratableError::Resume(anyhow!("Could not get VM state: {}", e)))?; + .map_err(|e| MigratableError::Resume(anyhow!("Could not get VM state: {e}")))?; let new_state = VmState::Running; state .valid_transition(new_state) - .map_err(|e| MigratableError::Resume(anyhow!("Invalid transition: {:?}", e)))?; + .map_err(|e| MigratableError::Resume(anyhow!("Invalid transition: {e:?}")))?; self.cpu_manager.lock().unwrap().resume()?; #[cfg(target_arch = "x86_64")] { if let Some(clock) = &self.saved_clock { - self.vm.set_clock(clock).map_err(|e| { - MigratableError::Resume(anyhow!("Could not set VM clock: {}", e)) - })?; + self.vm + .set_clock(clock) + .map_err(|e| MigratableError::Resume(anyhow!("Could not set VM clock: {e}")))?; } } if current_state == VmState::Paused { self.vm .resume() - .map_err(|e| MigratableError::Resume(anyhow!("Could not resume the VM: {}", e)))?; + .map_err(|e| MigratableError::Resume(anyhow!("Could not resume the VM: {e}")))?; } self.device_manager.lock().unwrap().resume()?; @@ -2891,7 +2890,7 @@ impl Snapshottable for Vm { }, ) .map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error generating common cpuid: {:?}", e)) + MigratableError::MigrateReceive(anyhow!("Error generating common cpuid: {e:?}")) })? }; From f1206a19dfc98f9e344e5566431fd23ad10b2bb1 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 25 Sep 2025 08:50:28 +0200 Subject: [PATCH 0203/1893] block: fix error style Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- block/src/async_io.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index aa31c54367..e4d8aaa256 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -80,7 +80,7 @@ pub enum AsyncIoError { #[error("Failed synchronizing file")] Fsync(#[source] std::io::Error), /// Failed submitting batch requests. - #[error("Failed submitting batch requests: {0}")] + #[error("Failed submitting batch requests")] SubmitBatchRequests(#[source] std::io::Error), } From a6426e3615ae2cfa10f4c20e13546bf1e28726c6 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 30 Jun 2025 15:07:04 +0200 Subject: [PATCH 0204/1893] devices: gracefully close preserved FDs on device remove For a graceful resource management of externally provided FDs in Cloud Hypervisor, corresponding FDs need to be closed on a device removal. This is the case for virtio-net devices using external FDs, for example. With the fix introduced in this commit, we allow management software to properly clean up resources, e.g., libvirt can clean up tap devices. PS: CHV uses "added" and "removed", which has the same meaning as hot device attach/hotplug and hot device detach/unplug. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/device_manager.rs | 30 ++++++++++++++++++++++++++++-- vmm/src/vm_config.rs | 4 ++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 9c14f28eb6..a580f5282d 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4512,9 +4512,35 @@ impl DeviceManager { .unwrap() .device_type(), ); + // When the device is added, we close all file descriptors + // opened externally for this device. This allows management + // software to properly clean up resources, e.g., libvirt can clean + // up tap devices. + // + // TODO: once we allow externally opened FDs for other devices as well, + // we should create a descriptive abstraction/function for this + // functionality. match device_type { - VirtioDeviceType::Net - | VirtioDeviceType::Block + VirtioDeviceType::Net => { + let mut config = self.config.lock().unwrap(); + let nets = config.net.as_deref_mut().unwrap(); + let net_dev_cfg = nets + .iter_mut() + .find(|net| net.id.as_ref() == Some(&id)) + // unwrap: the device could not have been removed without an ID + .unwrap(); + let fds = net_dev_cfg.fds.take().unwrap_or(Vec::new()); + + debug!("Closing preserved FDs from virtio-net device: id={id}, fds={fds:?}"); + for fd in fds { + config.preserved_fds.as_mut().unwrap().retain(|x| *x != fd); + // SAFETY: We are closing the only remaining instance of this FD. + unsafe { + libc::close(fd); + } + } + } + VirtioDeviceType::Block | VirtioDeviceType::Pmem | VirtioDeviceType::Fs | VirtioDeviceType::Vsock => {} diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 7021f9e9c6..380c204343 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -926,6 +926,10 @@ pub struct VmConfig { // VmConfig instance, such as FDs for creating TAP devices. // Preserved FDs will stay open as long as the holding VmConfig instance is // valid, and will be closed when the holding VmConfig instance is destroyed. + // + // This is populated as devices are added at runtime. Removing them again + // causes the FDs to be closed early. This allows management software to + // gracefully clean up resources (e.g., libvirt closes tap devices). #[serde(skip)] pub preserved_fds: Option>, #[serde(default)] From bb713e8c018ddfb3cc04ada6ca890e70cd1e2acd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 23:22:08 +0000 Subject: [PATCH 0205/1893] build: Bump crate-ci/typos from 1.36.2 to 1.36.3 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.36.2 to 1.36.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.36.2...v1.36.3) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.36.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index f767909a27..c211ed8b43 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.36.2 + - uses: crate-ci/typos@v1.36.3 From 4c16285dde76dc832aa6a426f1a76588a253e490 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 26 Sep 2025 12:29:43 +0200 Subject: [PATCH 0206/1893] vmm: Fix UB in AMX support checks Rust's aliasing rules do not permit mutating a `usize` through a shared reference. Unsafe blocks and FFI are no exception. This PR fixes such a violation detected in the AMX support checks by passing a raw mutable pointer instead of a shared reference in the syscall. Signed-off-by: Oliver Anderson On-behalf-of: SAP --- vmm/src/cpu.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 3c54a7c4d4..8eb552297e 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -730,11 +730,16 @@ impl CpuManager { if amx_tile != 0 { return Err(Error::AmxEnable(anyhow!("Guest AMX usage not supported"))); } else { - let mask: usize = 0; - // SAFETY: the mask being modified (not marked mutable as it is - // modified in unsafe only which is permitted) isn't in use elsewhere. + let mut mask: usize = 0; + // SAFETY: Syscall with valid parameters. We use a raw mutable pointer to + // the `mask` place in order to ensure that we do not violate Rust's + // aliasing rules. let result = unsafe { - libc::syscall(libc::SYS_arch_prctl, ARCH_GET_XCOMP_GUEST_PERM, &mask) + libc::syscall( + libc::SYS_arch_prctl, + ARCH_GET_XCOMP_GUEST_PERM, + &raw mut mask, + ) }; if result != 0 || (mask & XFEATURE_XTILEDATA_MASK) != XFEATURE_XTILEDATA_MASK { return Err(Error::AmxEnable(anyhow!("Guest AMX usage not supported"))); From 690741de13b9684a058ca39b33b7d1945269cc6f Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 24 Jun 2025 10:31:37 +0200 Subject: [PATCH 0207/1893] hypervisor: kvm/x86_64: get and set nested guest state Since the functionality of [0] was backported to kvm-bindings@v0.12.1 and kvm-ioctls@v0.22.1 [1, 2], we can now save nested KVM state. This way, nesting works across state save/resume and live-migration. [0] https://github.com/rust-vmm/kvm/pull/322 [1] https://github.com/rust-vmm/kvm/pull/349 [2] https://github.com/rust-vmm/kvm/pull/350 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.lock | 8 +++---- Cargo.toml | 4 ++-- hypervisor/src/cpu.rs | 4 ++++ hypervisor/src/kvm/mod.rs | 37 ++++++++++++++++++++++++++++++++ hypervisor/src/kvm/x86_64/mod.rs | 4 ++++ vmm/src/seccomp_filters.rs | 6 ++++++ 6 files changed, 57 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cb893abb8..926a941f34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1073,9 +1073,9 @@ dependencies = [ [[package]] name = "kvm-bindings" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b153a59bb3ca930ff8148655b2ef68c34259a623ae08cf2fb9b570b2e45363" +checksum = "9a537873e15e8daabb416667e606d9b0abc2a8fb9a45bd5853b888ae0ead82f9" dependencies = [ "serde", "vmm-sys-util", @@ -1084,9 +1084,9 @@ dependencies = [ [[package]] name = "kvm-ioctls" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b702df98508cb63ad89dd9beb9f6409761b30edca10d48e57941d3f11513a006" +checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d" dependencies = [ "bitflags 2.9.4", "kvm-bindings", diff --git a/Cargo.toml b/Cargo.toml index a64b8c3c90..7d520be544 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,8 +107,8 @@ package.edition = "2024" [workspace.dependencies] # rust-vmm crates acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" } -kvm-bindings = "0.12.0" -kvm-ioctls = "0.22.0" +kvm-bindings = "0.12.1" +kvm-ioctls = "0.22.1" # TODO: update to 0.13.1+ linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" } mshv-bindings = "0.6.0" diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index 5c377c5d1d..2093297d86 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -334,6 +334,10 @@ pub enum HypervisorCpuError { /// #[error("Failed to inject NMI")] Nmi(#[source] anyhow::Error), + #[error("Failed to get nested guest state")] + GetNestedState(#[source] anyhow::Error), + #[error("Failed to set nested guest state")] + SetNestedState(#[source] anyhow::Error), } #[derive(Debug)] diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index a4a062b3ea..ece84566ca 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -86,6 +86,8 @@ use std::mem; /// #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] pub use kvm_bindings::kvm_vcpu_events as VcpuEvents; +#[cfg(target_arch = "x86_64")] +use kvm_bindings::nested::KvmNestedStateBuffer; pub use kvm_bindings::{ KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_IRQ_ROUTING_IRQCHIP, KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID, kvm_clock_data, @@ -2442,6 +2444,7 @@ impl cpu::Vcpu for KvmVcpu { let xcrs = self.get_xcrs()?; let lapic_state = self.get_lapic()?; let fpu = self.get_fpu()?; + let nested_state = self.nested_state()?; // Try to get all MSRs based on the list previously retrieved from KVM. // If the number of MSRs obtained from GET_MSRS is different from the @@ -2516,6 +2519,7 @@ impl cpu::Vcpu for KvmVcpu { xcrs, mp_state, tsc_khz, + nested_state, } .into()) } @@ -2683,6 +2687,9 @@ impl cpu::Vcpu for KvmVcpu { self.set_xcrs(&state.xcrs)?; self.set_lapic(&state.lapic_state)?; self.set_fpu(&state.fpu)?; + if let Some(nested_state) = state.nested_state { + self.set_nested_state(&nested_state)?; + } if let Some(freq) = state.tsc_khz { self.set_tsc_khz(freq)?; @@ -3036,6 +3043,36 @@ impl KvmVcpu { .set_vcpu_events(events) .map_err(|e| cpu::HypervisorCpuError::SetVcpuEvents(e.into())) } + + /// Get the state of the nested guest from the current vCPU, + /// if there is any. + #[cfg(target_arch = "x86_64")] + fn nested_state(&self) -> cpu::Result> { + let mut buffer = KvmNestedStateBuffer::empty(); + + let maybe_size = self + .fd + .lock() + .unwrap() + .get_nested_state(&mut buffer) + .map_err(|e| cpu::HypervisorCpuError::GetNestedState(e.into()))?; + + if let Some(_size) = maybe_size { + Ok(Some(buffer)) + } else { + Ok(None) + } + } + + /// Sets the state of the nested guest for the current vCPU. + #[cfg(target_arch = "x86_64")] + fn set_nested_state(&self, state: &KvmNestedStateBuffer) -> cpu::Result<()> { + self.fd + .lock() + .unwrap() + .set_nested_state(state) + .map_err(|e| cpu::HypervisorCpuError::GetNestedState(e.into())) + } } #[cfg(test)] diff --git a/hypervisor/src/kvm/x86_64/mod.rs b/hypervisor/src/kvm/x86_64/mod.rs index a01fb9d727..c1bda9d9be 100644 --- a/hypervisor/src/kvm/x86_64/mod.rs +++ b/hypervisor/src/kvm/x86_64/mod.rs @@ -19,6 +19,7 @@ pub use { kvm_bindings::kvm_msr_entry, kvm_bindings::kvm_regs, kvm_bindings::kvm_segment, kvm_bindings::kvm_sregs, kvm_bindings::kvm_vcpu_events as VcpuEvents, kvm_bindings::kvm_xcrs as ExtendedControlRegisters, kvm_bindings::kvm_xsave, + kvm_bindings::nested::KvmNestedStateBuffer, }; use crate::arch::x86::{ @@ -75,6 +76,9 @@ pub struct VcpuKvmState { pub xcrs: ExtendedControlRegisters, pub mp_state: MpState, pub tsc_khz: Option, + // Option to prevent useless 8K (de)serialization when no nested + // state exists. + pub nested_state: Option, } impl From for kvm_segment { diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 3dacd30463..8fff3925bc 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -103,6 +103,8 @@ mod kvm { pub const KVM_GET_REG_LIST: u64 = 0xc008_aeb0; pub const KVM_MEMORY_ENCRYPT_OP: u64 = 0xc008_aeba; pub const KVM_NMI: u64 = 0xae9a; + pub const KVM_GET_NESTED_STATE: u64 = 3229658814; + pub const KVM_SET_NESTED_STATE: u64 = 1082175167; } // MSHV IOCTL code. This is unstable until the kernel code has been declared stable. @@ -232,6 +234,8 @@ fn create_vmm_ioctl_seccomp_rule_common_kvm() -> Result, Backen and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_VCPU_EVENTS,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_NMI)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_NESTED_STATE)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_NESTED_STATE)?], ]) } @@ -697,6 +701,8 @@ fn create_vcpu_ioctl_seccomp_rule_kvm() -> Result, BackendError and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_RUN,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_NMI)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_NESTED_STATE)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_NESTED_STATE)?], ]) } From 39e540ebb1e62dd39db8fcbdc9524a1af6147d65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:13:59 +0000 Subject: [PATCH 0208/1893] build: Bump the non-rust-vmm group across 2 directories with 20 updates Bumps the non-rust-vmm group with 12 updates in the / directory: | Package | From | To | | --- | --- | --- | | [serde](https://github.com/serde-rs/serde) | `1.0.226` | `1.0.228` | | [libc](https://github.com/rust-lang/libc) | `0.2.175` | `0.2.176` | | [thiserror](https://github.com/dtolnay/thiserror) | `2.0.16` | `2.0.17` | | [anstyle](https://github.com/rust-cli/anstyle) | `1.0.11` | `1.0.13` | | [async-channel](https://github.com/smol-rs/async-channel) | `2.3.1` | `2.5.0` | | [backtrace](https://github.com/rust-lang/backtrace-rs) | `0.3.75` | `0.3.76` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.38` | `1.2.39` | | [hashbrown](https://github.com/rust-lang/hashbrown) | `0.15.5` | `0.16.0` | | [memchr](https://github.com/BurntSushi/memchr) | `2.7.5` | `2.7.6` | | [quote](https://github.com/dtolnay/quote) | `1.0.40` | `1.0.41` | | [regex](https://github.com/rust-lang/regex) | `1.11.2` | `1.11.3` | | [rustix](https://github.com/bytecodealliance/rustix) | `1.0.7` | `1.1.2` | Bumps the non-rust-vmm group with 8 updates in the /fuzz directory: | Package | From | To | | --- | --- | --- | | [serde](https://github.com/serde-rs/serde) | `1.0.226` | `1.0.228` | | [libc](https://github.com/rust-lang/libc) | `0.2.175` | `0.2.176` | | [thiserror](https://github.com/dtolnay/thiserror) | `2.0.16` | `2.0.17` | | [anstyle](https://github.com/rust-cli/anstyle) | `1.0.11` | `1.0.13` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.38` | `1.2.39` | | [memchr](https://github.com/BurntSushi/memchr) | `2.7.5` | `2.7.6` | | [quote](https://github.com/dtolnay/quote) | `1.0.40` | `1.0.41` | | [windows-targets](https://github.com/microsoft/windows-rs) | `0.53.3` | `0.53.4` | Updates `serde` from 1.0.226 to 1.0.228 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.226...v1.0.228) Updates `libc` from 0.2.175 to 0.2.176 - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.176/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.175...0.2.176) Updates `thiserror` from 2.0.16 to 2.0.17 - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.16...2.0.17) Updates `anstyle` from 1.0.11 to 1.0.13 - [Commits](https://github.com/rust-cli/anstyle/compare/v1.0.11...v1.0.13) Updates `async-channel` from 2.3.1 to 2.5.0 - [Release notes](https://github.com/smol-rs/async-channel/releases) - [Changelog](https://github.com/smol-rs/async-channel/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/async-channel/compare/v2.3.1...v2.5.0) Updates `backtrace` from 0.3.75 to 0.3.76 - [Release notes](https://github.com/rust-lang/backtrace-rs/releases) - [Changelog](https://github.com/rust-lang/backtrace-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.75...backtrace-v0.3.76) Updates `cc` from 1.2.38 to 1.2.39 - [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.38...cc-v1.2.39) Updates `gimli` from 0.31.1 to 0.32.3 - [Changelog](https://github.com/gimli-rs/gimli/blob/master/CHANGELOG.md) - [Commits](https://github.com/gimli-rs/gimli/compare/0.31.1...0.32.3) Updates `hashbrown` from 0.15.5 to 0.16.0 - [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.15.5...v0.16.0) Updates `memchr` from 2.7.5 to 2.7.6 - [Commits](https://github.com/BurntSushi/memchr/compare/2.7.5...2.7.6) Updates `object` from 0.36.7 to 0.37.3 - [Changelog](https://github.com/gimli-rs/object/blob/master/CHANGELOG.md) - [Commits](https://github.com/gimli-rs/object/compare/0.36.7...0.37.3) Updates `quote` from 1.0.40 to 1.0.41 - [Release notes](https://github.com/dtolnay/quote/releases) - [Commits](https://github.com/dtolnay/quote/compare/1.0.40...1.0.41) Updates `regex` from 1.11.2 to 1.11.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.11.2...1.11.3) Updates `regex-automata` from 0.4.10 to 0.4.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/commits) Updates `rustix` from 1.0.7 to 1.1.2 - [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.0.7...v1.1.2) Updates `serde_core` from 1.0.226 to 1.0.228 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.226...v1.0.228) Updates `serde_derive` from 1.0.226 to 1.0.228 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.226...v1.0.228) Updates `windows-sys` from 0.59.0 to 0.60.2 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `serde` from 1.0.226 to 1.0.228 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.226...v1.0.228) Updates `libc` from 0.2.175 to 0.2.176 - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.176/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.175...0.2.176) Updates `thiserror` from 2.0.16 to 2.0.17 - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.16...2.0.17) Updates `bitfield-struct` from 0.10.1 to 0.11.0 - [Release notes](https://github.com/wrenger/bitfield-struct-rs/releases) - [Commits](https://github.com/wrenger/bitfield-struct-rs/compare/0.10.1...0.11.0) Updates `anstyle` from 1.0.11 to 1.0.13 - [Commits](https://github.com/rust-cli/anstyle/compare/v1.0.11...v1.0.13) Updates `cc` from 1.2.38 to 1.2.39 - [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.38...cc-v1.2.39) Updates `memchr` from 2.7.5 to 2.7.6 - [Commits](https://github.com/BurntSushi/memchr/compare/2.7.5...2.7.6) Updates `quote` from 1.0.40 to 1.0.41 - [Release notes](https://github.com/dtolnay/quote/releases) - [Commits](https://github.com/dtolnay/quote/compare/1.0.40...1.0.41) Updates `serde_core` from 1.0.226 to 1.0.228 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.226...v1.0.228) Updates `serde_derive` from 1.0.226 to 1.0.228 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.226...v1.0.228) Updates `windows-targets` from 0.53.3 to 0.53.4 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) --- updated-dependencies: - dependency-name: serde dependency-version: 1.0.228 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: libc dependency-version: 0.2.176 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: thiserror dependency-version: 2.0.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle dependency-version: 1.0.13 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: async-channel dependency-version: 2.5.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: backtrace dependency-version: 0.3.76 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.39 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: gimli dependency-version: 0.32.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: hashbrown dependency-version: 0.16.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: memchr dependency-version: 2.7.6 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: object dependency-version: 0.37.3 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: quote dependency-version: 1.0.41 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: regex dependency-version: 1.11.3 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: regex-automata dependency-version: 0.4.11 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: rustix dependency-version: 1.1.2 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: serde_core dependency-version: 1.0.228 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_derive dependency-version: 1.0.228 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows-sys dependency-version: 0.60.2 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: serde dependency-version: 1.0.228 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: libc dependency-version: 0.2.176 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: thiserror dependency-version: 2.0.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: bitfield-struct dependency-version: 0.11.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: anstyle dependency-version: 1.0.13 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.39 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: memchr dependency-version: 2.7.6 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: quote dependency-version: 1.0.41 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_core dependency-version: 1.0.228 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_derive dependency-version: 1.0.228 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows-targets dependency-version: 0.53.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm ... Signed-off-by: dependabot[bot] --- Cargo.lock | 151 +++++++++++++++++++++++------------------------- Cargo.toml | 6 +- fuzz/Cargo.lock | 94 +++++++++++++++--------------- fuzz/Cargo.toml | 2 +- 4 files changed, 122 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 926a941f34..850ea0274c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,9 +12,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -94,7 +94,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" name = "api_client" version = "0.1.0" dependencies = [ - "thiserror 2.0.16", + "thiserror 2.0.17", "vmm-sys-util", ] @@ -116,7 +116,7 @@ dependencies = [ "linux-loader", "log", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "uuid", "vm-fdt", "vm-memory", @@ -137,9 +137,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", @@ -268,9 +268,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -278,7 +278,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -327,7 +327,7 @@ dependencies = [ "remain", "serde", "smallvec", - "thiserror 2.0.16", + "thiserror 2.0.17", "uuid", "virtio-bindings", "virtio-queue", @@ -363,9 +363,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.38" +version = "1.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", "shlex", @@ -432,7 +432,7 @@ dependencies = [ "serde_json", "signal-hook", "test_infra", - "thiserror 2.0.16", + "thiserror 2.0.17", "tpm", "tracer", "vm-memory", @@ -550,7 +550,7 @@ dependencies = [ "num_enum", "pci", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "tpm", "vm-allocator", "vm-device", @@ -594,7 +594,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -670,7 +670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -893,9 +893,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" @@ -905,9 +905,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hermit-abi" @@ -945,7 +945,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -979,7 +979,7 @@ dependencies = [ "open-enum", "range_map_vec", "static_assertions", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "zerocopy", ] @@ -1102,7 +1102,7 @@ checksum = "affe8b77dce5b172f8e290bd801b12832a77cd1942d1ea98259916e89d5829d6" dependencies = [ "enumflags2", "libc", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -1113,9 +1113,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libredox" @@ -1163,9 +1163,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" @@ -1191,9 +1191,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" @@ -1250,7 +1250,7 @@ checksum = "aefaab4c067cf5226a917227640d835327b25b71a8d465f815f74f490344e10a" dependencies = [ "libc", "mshv-bindings", - "thiserror 2.0.16", + "thiserror 2.0.17", "vmm-sys-util", ] @@ -1284,7 +1284,7 @@ dependencies = [ "rate_limiter", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", "virtio-bindings", "virtio-queue", "vm-memory", @@ -1344,9 +1344,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1415,7 +1415,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" name = "option_parser" version = "0.1.0" dependencies = [ - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -1473,7 +1473,7 @@ dependencies = [ "libc", "log", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-bindings", "vfio-ioctls", "vfio_user", @@ -1493,7 +1493,7 @@ dependencies = [ "serde", "serde_json", "test_infra", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -1674,9 +1674,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -1729,7 +1729,7 @@ dependencies = [ "epoll", "libc", "log", - "thiserror 2.0.16", + "thiserror 2.0.17", "vmm-sys-util", ] @@ -1750,14 +1750,14 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] name = "regex" -version = "1.11.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -1767,9 +1767,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -1807,15 +1807,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags 2.9.4", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1847,9 +1847,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1857,18 +1857,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2017,7 +2017,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -2039,7 +2039,7 @@ dependencies = [ "libc", "serde_json", "ssh2", - "thiserror 2.0.16", + "thiserror 2.0.17", "vmm-sys-util", "wait-timeout", ] @@ -2055,11 +2055,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -2075,9 +2075,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -2128,7 +2128,7 @@ dependencies = [ "libc", "log", "net_gen", - "thiserror 2.0.16", + "thiserror 2.0.17", "vmm-sys-util", ] @@ -2236,7 +2236,7 @@ dependencies = [ "log", "mshv-bindings", "mshv-ioctls", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2254,7 +2254,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2298,7 +2298,7 @@ dependencies = [ "libc", "log", "option_parser", - "thiserror 2.0.16", + "thiserror 2.0.17", "vhost", "vhost-user-backend", "virtio-bindings", @@ -2318,7 +2318,7 @@ dependencies = [ "log", "net_util", "option_parser", - "thiserror 2.0.16", + "thiserror 2.0.17", "vhost", "vhost-user-backend", "virtio-bindings", @@ -2351,7 +2351,7 @@ dependencies = [ "serde", "serde_with", "serial_buffer", - "thiserror 2.0.16", + "thiserror 2.0.17", "vhost", "virtio-bindings", "virtio-queue", @@ -2390,7 +2390,7 @@ version = "0.1.0" dependencies = [ "hypervisor", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -2420,7 +2420,7 @@ dependencies = [ "anyhow", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", "vm-memory", ] @@ -2472,7 +2472,7 @@ dependencies = [ "serde_json", "serial_buffer", "signal-hook", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracer", "uuid", "vfio-ioctls", @@ -2612,15 +2612,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" diff --git a/Cargo.toml b/Cargo.toml index 7d520be544..0f85a0afe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,7 +131,7 @@ igvm = { git = "https://github.com/microsoft/igvm", branch = "main" } igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates -serde = "1.0.226" +serde = "1.0.228" serde_json = "1.0.145" serde_with = { version = "3.14.1", default-features = false } @@ -146,10 +146,10 @@ dirs = "6.0.0" env_logger = "0.11.8" epoll = "4.4.0" flume = "0.11.1" -libc = "0.2.167" +libc = "0.2.176" log = "0.4.28" signal-hook = "0.3.18" -thiserror = "2.0.16" +thiserror = "2.0.17" uuid = { version = "1.18.1" } wait-timeout = "0.2.1" zerocopy = { version = "0.8.27", default-features = false } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 32cfbd990e..439c74872e 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -27,9 +27,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -90,7 +90,7 @@ dependencies = [ "linux-loader", "log", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "uuid", "vm-fdt", "vm-memory", @@ -105,9 +105,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitfield-struct" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be5a46ba01b60005ae2c51a36a29cfe134bcacae2dd5cedcd4615fbaad1494b" +checksum = "d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8" dependencies = [ "proc-macro2", "quote", @@ -137,7 +137,7 @@ dependencies = [ "remain", "serde", "smallvec", - "thiserror 2.0.16", + "thiserror 2.0.17", "uuid", "virtio-bindings", "virtio-queue", @@ -160,9 +160,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.38" +version = "1.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", "jobserver", @@ -311,7 +311,7 @@ dependencies = [ "num_enum", "pci", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "tpm", "vm-allocator", "vm-device", @@ -483,7 +483,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -549,9 +549,9 @@ dependencies = [ [[package]] name = "kvm-bindings" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b153a59bb3ca930ff8148655b2ef68c34259a623ae08cf2fb9b570b2e45363" +checksum = "9a537873e15e8daabb416667e606d9b0abc2a8fb9a45bd5853b888ae0ead82f9" dependencies = [ "serde", "vmm-sys-util", @@ -560,9 +560,9 @@ dependencies = [ [[package]] name = "kvm-ioctls" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b702df98508cb63ad89dd9beb9f6409761b30edca10d48e57941d3f11513a006" +checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d" dependencies = [ "bitflags 2.9.4", "kvm-bindings", @@ -578,7 +578,7 @@ checksum = "affe8b77dce5b172f8e290bd801b12832a77cd1942d1ea98259916e89d5829d6" dependencies = [ "enumflags2", "libc", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -589,9 +589,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libfuzzer-sys" @@ -635,9 +635,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "micro_http" @@ -689,7 +689,7 @@ dependencies = [ "net_gen", "rate_limiter", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "virtio-bindings", "virtio-queue", "vm-memory", @@ -764,7 +764,7 @@ dependencies = [ name = "option_parser" version = "0.1.0" dependencies = [ - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -783,7 +783,7 @@ dependencies = [ "libc", "log", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-bindings", "vfio-ioctls", "vfio_user", @@ -823,9 +823,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -872,7 +872,7 @@ dependencies = [ "epoll", "libc", "log", - "thiserror 2.0.16", + "thiserror 2.0.17", "vmm-sys-util", ] @@ -916,9 +916,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -926,18 +926,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1052,11 +1052,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -1072,9 +1072,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -1119,7 +1119,7 @@ dependencies = [ "libc", "log", "net_gen", - "thiserror 2.0.16", + "thiserror 2.0.17", "vmm-sys-util", ] @@ -1177,7 +1177,7 @@ dependencies = [ "kvm-ioctls", "libc", "log", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1195,7 +1195,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1238,7 +1238,7 @@ dependencies = [ "serde", "serde_with", "serial_buffer", - "thiserror 2.0.16", + "thiserror 2.0.17", "vhost", "virtio-bindings", "virtio-queue", @@ -1277,7 +1277,7 @@ version = "0.1.0" dependencies = [ "hypervisor", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -1307,7 +1307,7 @@ dependencies = [ "anyhow", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", "vm-memory", ] @@ -1351,7 +1351,7 @@ dependencies = [ "serde_json", "serial_buffer", "signal-hook", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracer", "uuid", "vfio-ioctls", @@ -1477,9 +1477,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-sys" @@ -1492,9 +1492,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ "windows-link", "windows_aarch64_gnullvm", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 36d0b1749a..7c71e881f7 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.155" +libc = "0.2.176" libfuzzer-sys = "0.4.10" # TODO: update to 0.13.1+ linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main", features = [ From 44f187715f366196a27184e52e9d566c11328848 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 23:21:28 +0000 Subject: [PATCH 0209/1893] build: Bump crate-ci/typos from 1.36.3 to 1.37.0 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.36.3 to 1.37.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.36.3...v1.37.0) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index c211ed8b43..8872c2cd41 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.36.3 + - uses: crate-ci/typos@v1.37.0 From 4961b93e698c246068095564d3a9a6c97e842942 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 1 Oct 2025 08:07:32 +0100 Subject: [PATCH 0210/1893] tests: Fix typo/grammar in live migration tests Signed-off-by: Rob Bradford --- tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration.rs b/tests/integration.rs index e18d7fb7b1..3007ca3d16 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -10914,7 +10914,7 @@ mod live_migration { ); }; - // Post live-migration check to make sure the destination VM is funcational + // Post live-migration check to make sure the destination VM is functioning let r = std::panic::catch_unwind(|| { // Perform same checks to validate VM has been properly migrated assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); From 7eb5a6cd704663599ba2308241d01246fcec5cc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:21:07 +0000 Subject: [PATCH 0211/1893] build: Bump crate-ci/typos from 1.37.0 to 1.37.1 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.37.0 to 1.37.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.37.0...v1.37.1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 8872c2cd41..c925d0fc7b 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.37.0 + - uses: crate-ci/typos@v1.37.1 From 4e008e2dbc3f9b47900b1604b4aa490d449ce176 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 26 Aug 2025 13:20:28 +0100 Subject: [PATCH 0212/1893] tests: Reduce use of focal/jammy variable names In preparation for removing focal support - start by using a more generic "disk_config" variable name for the DiskConfig struct vs embedding the image type. Signed-off-by: Rob Bradford --- tests/integration.rs | 424 +++++++++++++++++++++---------------------- 1 file changed, 212 insertions(+), 212 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 3007ca3d16..6eeeeab20f 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -972,8 +972,8 @@ fn check_latest_events_exact(latest_events: &[&MetaEvent], event_file: &str) -> } fn test_cpu_topology(threads_per_core: u8, cores_per_package: u8, packages: u8, use_fw: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let total_vcpus = threads_per_core * cores_per_package * packages; let direct_kernel_boot_path = direct_kernel_boot_path(); let mut kernel_path = direct_kernel_boot_path.to_str().unwrap(); @@ -1077,8 +1077,8 @@ fn test_cpu_topology(threads_per_core: u8, cores_per_package: u8, packages: u8, #[allow(unused_variables)] fn _test_guest_numa_nodes(acpi: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); @@ -1154,8 +1154,8 @@ fn _test_guest_numa_nodes(acpi: bool) { #[allow(unused_variables)] fn _test_power_button(acpi: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); let api_socket = temp_api_path(&guest.tmp_dir); @@ -1205,8 +1205,8 @@ fn test_vhost_user_net( generate_host_mac: bool, client_mode_daemon: bool, ) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let kernel_path = direct_kernel_boot_path(); @@ -1372,8 +1372,8 @@ fn test_vhost_user_blk( direct: bool, prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>, ) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let kernel_path = direct_kernel_boot_path(); @@ -1514,8 +1514,8 @@ fn test_boot_from_vhost_user_blk( direct: bool, prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>, ) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -1591,8 +1591,8 @@ fn _test_virtio_fs( }; #[cfg(target_arch = "x86_64")] let focal_image = FOCAL_IMAGE_NAME.to_string(); - let focal = UbuntuDiskConfig::new(focal_image); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(focal_image); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let mut workload_path = dirs::home_dir().unwrap(); @@ -1781,8 +1781,8 @@ fn _test_virtio_fs( } fn test_virtio_pmem(discard_writes: bool, specify_size: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -1859,8 +1859,8 @@ fn get_fd_count(pid: u32) -> usize { } fn _test_virtio_vsock(hotplug: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); @@ -1948,8 +1948,8 @@ fn test_memory_mergeable(mergeable: bool) { let ksm_ps_init = get_ksm_pages_shared(); assert!(ksm_ps_init == 0); - let focal1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest1 = Guest::new(Box::new(focal1)); + let disk_config1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest1 = Guest::new(Box::new(disk_config1)); let mut child1 = GuestCommand::new(&guest1) .args(["--cpus", "boot=1"]) .args(["--memory", format!("size=512M,{memory_param}").as_str()]) @@ -1974,8 +1974,8 @@ fn test_memory_mergeable(mergeable: bool) { let ksm_ps_guest1 = get_ksm_pages_shared(); - let focal2 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest2 = Guest::new(Box::new(focal2)); + let disk_config2 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest2 = Guest::new(Box::new(disk_config2)); let mut child2 = GuestCommand::new(&guest2) .args(["--cpus", "boot=1"]) .args(["--memory", format!("size=512M,{memory_param}").as_str()]) @@ -2227,8 +2227,8 @@ fn _test_virtio_iommu(acpi: bool) { let focal_image = FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string(); #[cfg(target_arch = "x86_64")] let focal_image = FOCAL_IMAGE_NAME.to_string(); - let focal = UbuntuDiskConfig::new(focal_image); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(focal_image); + let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); @@ -2605,8 +2605,8 @@ mod common_parallel { #[test] fn test_multi_cpu() { let jammy_image = JAMMY_IMAGE_NAME.to_string(); - let jammy = UbuntuDiskConfig::new(jammy_image); - let guest = Guest::new(Box::new(jammy)); + let disk_config = UbuntuDiskConfig::new(jammy_image); + let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=2,max=4"]) @@ -2660,8 +2660,8 @@ mod common_parallel { #[cfg(target_arch = "x86_64")] #[cfg(not(feature = "mshv"))] fn test_cpu_physical_bits() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let max_phys_bits: u8 = 36; let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("max_phys_bits={max_phys_bits}")]) @@ -2695,8 +2695,8 @@ mod common_parallel { #[test] fn test_cpu_affinity() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); // We need the host to have at least 4 CPUs if we want to be able // to run this test. @@ -2736,8 +2736,8 @@ mod common_parallel { #[test] fn test_virtio_queue_affinity() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); // We need the host to have at least 4 CPUs if we want to be able // to run this test. @@ -2794,8 +2794,8 @@ mod common_parallel { #[test] #[cfg(not(feature = "mshv"))] fn test_large_vm() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=48"]) .args(["--memory", "size=5120M"]) @@ -2833,8 +2833,8 @@ mod common_parallel { #[test] #[cfg(not(feature = "mshv"))] fn test_huge_memory() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=1"]) .args(["--memory", "size=128G"]) @@ -2865,8 +2865,8 @@ mod common_parallel { #[test] fn test_user_defined_memory_regions() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let kernel_path = direct_kernel_boot_path(); @@ -2939,8 +2939,8 @@ mod common_parallel { #[cfg(target_arch = "x86_64")] fn test_iommu_segments() { let focal_image = FOCAL_IMAGE_NAME.to_string(); - let focal = UbuntuDiskConfig::new(focal_image); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(focal_image); + let guest = Guest::new(Box::new(disk_config)); // Prepare another disk file for the virtio-disk device let test_disk_path = String::from( @@ -3017,8 +3017,8 @@ mod common_parallel { #[test] fn test_pci_msi() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=1"]) .args(["--memory", "size=512M"]) @@ -3057,8 +3057,8 @@ mod common_parallel { #[test] fn test_virtio_net_ctrl_queue() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=1"]) .args(["--memory", "size=512M"]) @@ -3104,8 +3104,8 @@ mod common_parallel { #[test] fn test_pci_multiple_segments() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); // Prepare another disk file for the virtio-disk device let test_disk_path = String::from( @@ -3208,8 +3208,8 @@ mod common_parallel { #[test] fn test_pci_multiple_segments_numa_node() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); @@ -3287,8 +3287,8 @@ mod common_parallel { #[test] fn test_direct_kernel_boot() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -3334,8 +3334,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_direct_kernel_boot_bzimage() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut kernel_path = direct_kernel_boot_path(); // Replace the default kernel with the bzImage. @@ -3378,8 +3378,8 @@ mod common_parallel { } fn _test_virtio_block(image_name: &str, disable_io_uring: bool, disable_aio: bool) { - let focal = UbuntuDiskConfig::new(image_name.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(image_name.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut workload_path = dirs::home_dir().unwrap(); workload_path.push("workloads"); @@ -3543,8 +3543,8 @@ mod common_parallel { const FULL_VHDX_FILE_SIZE: u64 = 112 << 20; const DYNAMIC_VHDX_NAME: &str = "dynamic.vhdx"; - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let vhdx_pathbuf = guest.tmp_dir.as_path().join(DYNAMIC_VHDX_NAME); @@ -3624,8 +3624,8 @@ mod common_parallel { #[test] fn test_virtio_block_direct_and_firmware() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); // The OS disk must be copied to a location that is not backed by // tmpfs, otherwise the syscall openat(2) with O_DIRECT simply fails @@ -3748,8 +3748,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_split_irqchip() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=1"]) @@ -3794,8 +3794,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_dmi_serial_number() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=1"]) @@ -3830,8 +3830,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_dmi_uuid() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=1"]) @@ -3866,8 +3866,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_dmi_oem_strings() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let s1 = "io.systemd.credential:xx=yy"; let s2 = "This is a test string"; @@ -3952,8 +3952,8 @@ mod common_parallel { #[test] fn test_boot_from_virtio_pmem() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4007,8 +4007,8 @@ mod common_parallel { #[test] fn test_multiple_network_interfaces() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4055,8 +4055,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "aarch64")] fn test_pmu_on() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=1"]) .args(["--memory", "size=512M"]) @@ -4091,8 +4091,8 @@ mod common_parallel { #[test] fn test_serial_off() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=1"]) .args(["--memory", "size=512M"]) @@ -4128,8 +4128,8 @@ mod common_parallel { #[test] fn test_serial_null() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); #[cfg(target_arch = "x86_64")] let console_str: &str = "console=ttyS0"; @@ -4181,8 +4181,8 @@ mod common_parallel { #[test] fn test_serial_tty() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4240,8 +4240,8 @@ mod common_parallel { #[test] fn test_serial_file() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let serial_path = guest.tmp_dir.as_path().join("serial-output"); #[cfg(target_arch = "x86_64")] @@ -4308,8 +4308,8 @@ mod common_parallel { #[test] fn test_pty_interaction() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let serial_option = if cfg!(target_arch = "x86_64") { " console=ttyS0" @@ -4354,8 +4354,8 @@ mod common_parallel { #[test] fn test_serial_socket_interaction() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let serial_socket = guest.tmp_dir.as_path().join("serial.socket"); let serial_socket_pty = guest.tmp_dir.as_path().join("serial.pty"); let serial_option = if cfg!(target_arch = "x86_64") { @@ -4424,8 +4424,8 @@ mod common_parallel { #[test] fn test_virtio_console() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4470,8 +4470,8 @@ mod common_parallel { #[test] fn test_console_file() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let console_path = guest.tmp_dir.as_path().join("console-output"); let mut child = GuestCommand::new(&guest) @@ -4534,8 +4534,8 @@ mod common_parallel { fn test_vfio() { setup_vfio_network_interfaces(); - let jammy = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new_from_ip_range(Box::new(jammy), "172.18", 0); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new_from_ip_range(Box::new(disk_config), "172.18", 0); let mut workload_path = dirs::home_dir().unwrap(); workload_path.push("workloads"); @@ -4766,8 +4766,8 @@ mod common_parallel { #[test] fn test_direct_kernel_boot_noacpi() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4810,32 +4810,32 @@ mod common_parallel { #[test] fn test_api_http_shutdown() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); _test_api_shutdown(TargetApi::new_http_api(&guest.tmp_dir), guest) } #[test] fn test_api_http_delete() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); _test_api_delete(TargetApi::new_http_api(&guest.tmp_dir), guest); } #[test] fn test_api_http_pause_resume() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); _test_api_pause_resume(TargetApi::new_http_api(&guest.tmp_dir), guest) } #[test] fn test_api_http_create_boot() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); _test_api_create_boot(TargetApi::new_http_api(&guest.tmp_dir), guest) } @@ -4856,8 +4856,8 @@ mod common_parallel { // properly probed first, then removing it, and adding it again by doing a // rescan. fn test_pci_bar_reprogramming() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); @@ -4957,8 +4957,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_cpu_hotplug() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let console_str = "console=ttyS0"; @@ -5050,8 +5050,8 @@ mod common_parallel { let focal_image = FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string(); #[cfg(target_arch = "x86_64")] let focal_image = FOCAL_IMAGE_NAME.to_string(); - let focal = UbuntuDiskConfig::new(focal_image); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(focal_image); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); #[cfg(target_arch = "aarch64")] @@ -5133,8 +5133,8 @@ mod common_parallel { #[test] fn test_virtio_mem() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let kernel_path = direct_kernel_boot_path(); @@ -5207,8 +5207,8 @@ mod common_parallel { #[cfg(target_arch = "x86_64")] // Test both vCPU and memory resizing together fn test_resize() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let kernel_path = direct_kernel_boot_path(); @@ -5267,8 +5267,8 @@ mod common_parallel { #[test] fn test_memory_overhead() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -5305,8 +5305,8 @@ mod common_parallel { // the path for the hotplug disk is not pre-added to Landlock rules, this // the test will result in a failure. fn test_landlock() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); @@ -5368,8 +5368,8 @@ mod common_parallel { } fn _test_disk_hotplug(landlock_enabled: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); @@ -5686,8 +5686,8 @@ mod common_parallel { #[test] fn test_virtio_block_topology() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let test_disk_path = guest.tmp_dir.as_path().join("test.img"); @@ -5779,8 +5779,8 @@ mod common_parallel { #[test] fn test_virtio_balloon_deflate_on_oom() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -5837,8 +5837,8 @@ mod common_parallel { #[test] fn test_virtio_balloon_free_page_reporting() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); //Let's start a 4G guest with balloon occupied 2G memory let mut child = GuestCommand::new(&guest) @@ -5913,8 +5913,8 @@ mod common_parallel { let focal_image = FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string(); #[cfg(target_arch = "x86_64")] let focal_image = FOCAL_IMAGE_NAME.to_string(); - let focal = UbuntuDiskConfig::new(focal_image); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(focal_image); + let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); @@ -6054,8 +6054,8 @@ mod common_parallel { } fn _test_net_hotplug(pci_segment: Option) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); @@ -6199,8 +6199,8 @@ mod common_parallel { #[test] fn test_initramfs() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut workload_path = dirs::home_dir().unwrap(); workload_path.push("workloads"); @@ -6248,8 +6248,8 @@ mod common_parallel { #[test] fn test_counters() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let mut cmd = GuestCommand::new(&guest); @@ -6287,8 +6287,8 @@ mod common_parallel { #[test] #[cfg(feature = "guest_debug")] fn test_coredump() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let mut cmd = GuestCommand::new(&guest); @@ -6335,8 +6335,8 @@ mod common_parallel { #[test] #[cfg(feature = "guest_debug")] fn test_coredump_no_pause() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let mut cmd = GuestCommand::new(&guest); @@ -6371,8 +6371,8 @@ mod common_parallel { #[test] fn test_watchdog() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let kernel_path = direct_kernel_boot_path(); @@ -6454,8 +6454,8 @@ mod common_parallel { #[test] fn test_pvpanic() { - let jammy = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(jammy)); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let event_path = temp_event_monitor_path(&guest.tmp_dir); @@ -6502,8 +6502,8 @@ mod common_parallel { #[test] fn test_tap_from_fd() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); // Create a TAP interface with multi-queue enabled @@ -6585,8 +6585,8 @@ mod common_parallel { // guest VM and the host machine. // Details: https://wiki.libvirt.org/page/TroubleshootMacvtapHostFail fn _test_macvtap(hotplug: bool, guest_macvtap_name: &str, host_macvtap_name: &str) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); #[cfg(target_arch = "x86_64")] @@ -6742,11 +6742,11 @@ mod common_parallel { #[test] #[cfg(not(feature = "mshv"))] fn test_ovs_dpdk() { - let focal1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest1 = Guest::new(Box::new(focal1)); + let disk_config1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest1 = Guest::new(Box::new(disk_config1)); - let focal2 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest2 = Guest::new(Box::new(focal2)); + let disk_config2 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest2 = Guest::new(Box::new(disk_config2)); let api_socket_source = format!("{}.1", temp_api_path(&guest2.tmp_dir)); let (mut child1, mut child2) = @@ -6926,8 +6926,8 @@ mod common_parallel { #[test] fn test_vfio_user() { let jammy_image = JAMMY_IMAGE_NAME.to_string(); - let jammy = UbuntuDiskConfig::new(jammy_image); - let guest = Guest::new(Box::new(jammy)); + let disk_config = UbuntuDiskConfig::new(jammy_image); + let guest = Guest::new(Box::new(disk_config)); let spdk_nvme_dir = guest.tmp_dir.as_path().join("test-vfio-user"); let mut spdk_child = setup_spdk_nvme(spdk_nvme_dir.as_path()); @@ -7018,8 +7018,8 @@ mod common_parallel { // Before trying to run the test, verify the vdpa_sim_blk module is correctly loaded. assert!(exec_host_command_status("lsmod | grep vdpa_sim_blk").success()); - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let kernel_path = direct_kernel_boot_path(); @@ -7145,8 +7145,8 @@ mod common_parallel { return; } - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -7219,8 +7219,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_tpm() { - let focal = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let (mut swtpm_command, swtpm_socket_path) = prepare_swtpm_daemon(&guest.tmp_dir); @@ -7263,8 +7263,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_double_tty() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); let api_socket = temp_api_path(&guest.tmp_dir); let tty_str: &str = "console=hvc0 earlyprintk=ttyS0 "; @@ -7315,8 +7315,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_nmi() { - let jammy = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(jammy)); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let event_path = temp_event_monitor_path(&guest.tmp_dir); @@ -7371,8 +7371,8 @@ mod dbus_api { // booted again. #[test] fn test_api_dbus_and_http_interleaved() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let dbus_api = TargetApi::new_dbus_api(&guest.tmp_dir); let http_api = TargetApi::new_http_api(&guest.tmp_dir); @@ -7441,32 +7441,32 @@ mod dbus_api { #[test] fn test_api_dbus_create_boot() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); _test_api_create_boot(TargetApi::new_dbus_api(&guest.tmp_dir), guest) } #[test] fn test_api_dbus_shutdown() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); _test_api_shutdown(TargetApi::new_dbus_api(&guest.tmp_dir), guest) } #[test] fn test_api_dbus_delete() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); _test_api_delete(TargetApi::new_dbus_api(&guest.tmp_dir), guest); } #[test] fn test_api_dbus_pause_resume() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); _test_api_pause_resume(TargetApi::new_dbus_api(&guest.tmp_dir), guest) } @@ -7481,8 +7481,8 @@ mod ivshmem { use crate::*; fn _test_live_migration_ivshmem(local: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; @@ -7667,8 +7667,8 @@ mod ivshmem { #[test] fn test_ivshmem() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let kernel_path = direct_kernel_boot_path(); @@ -7722,8 +7722,8 @@ mod ivshmem { #[test] fn test_snapshot_restore_ivshmem() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); @@ -7955,8 +7955,8 @@ mod common_sequential { } fn _test_snapshot_restore(use_hotplug: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); @@ -8209,8 +8209,8 @@ mod common_sequential { #[test] #[cfg_attr(target_arch = "aarch64", ignore = "See #6970")] fn test_snapshot_restore_with_fd() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); @@ -8436,8 +8436,8 @@ mod common_sequential { } fn _test_snapshot_restore_devices(pvpanic: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); @@ -9595,8 +9595,8 @@ mod vfio { const NVIDIA_VFIO_DEVICE: &str = "/sys/bus/pci/devices/0002:00:01.0"; fn test_nvidia_card_memory_hotplug(hotplug_method: &str) { - let jammy = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(jammy)); + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let mut child = GuestCommand::new(&guest) @@ -9649,8 +9649,8 @@ mod vfio { #[test] fn test_nvidia_card_pci_hotplug() { - let jammy = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(jammy)); + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let mut child = GuestCommand::new(&guest) @@ -9693,8 +9693,8 @@ mod vfio { #[test] fn test_nvidia_card_reboot() { - let jammy = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(jammy)); + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let mut child = GuestCommand::new(&guest) @@ -9732,8 +9732,8 @@ mod vfio { #[test] fn test_nvidia_card_iommu_address_width() { - let jammy = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(jammy)); + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let mut child = GuestCommand::new(&guest) @@ -9917,8 +9917,8 @@ mod live_migration { // live migration; // Note: This test does not use vsock as we can't create two identical vsock on the same host. fn _test_live_migration(upgrade_test: bool, local: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; @@ -10071,8 +10071,8 @@ mod live_migration { } fn _test_live_migration_balloon(upgrade_test: bool, local: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; @@ -10260,8 +10260,8 @@ mod live_migration { } fn _test_live_migration_numa(upgrade_test: bool, local: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; @@ -10507,8 +10507,8 @@ mod live_migration { } fn _test_live_migration_watchdog(upgrade_test: bool, local: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; @@ -10706,11 +10706,11 @@ mod live_migration { } fn _test_live_migration_ovs_dpdk(upgrade_test: bool, local: bool) { - let ovs_focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let ovs_guest = Guest::new(Box::new(ovs_focal)); + let ovs_disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let ovs_guest = Guest::new(Box::new(ovs_disk_config)); - let migration_focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let migration_guest = Guest::new(Box::new(migration_focal)); + let migration_disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let migration_guest = Guest::new(Box::new(migration_disk_config)); let src_api_socket = temp_api_path(&migration_guest.tmp_dir); // Start two VMs that are connected through ovs-dpdk and one of the VMs is the source VM for live-migration @@ -10814,8 +10814,8 @@ mod live_migration { // 6. Ensure Landlock is enabled on destination VM by hotplugging a disk. As the path for // this disk is not known to the destination VM this step will fail. fn _test_live_migration_with_landlock() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let net_id = "net123"; let net_params = format!( @@ -11021,8 +11021,8 @@ mod live_migration { } fn _test_live_migration_tcp() { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; @@ -11371,8 +11371,8 @@ mod rate_limiter { } fn _test_rate_limiter_net(rx: bool) { - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let test_timeout = 10; let num_queues = 2; @@ -11437,8 +11437,8 @@ mod rate_limiter { let bw_refill_time = 100; // ms let limit_rate = (bw_size * 1000) as f64 / bw_refill_time as f64; - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let test_img_dir = TempDir::new_with_prefix("/var/tmp/ch").unwrap(); let blk_rate_limiter_test_img = @@ -11524,8 +11524,8 @@ mod rate_limiter { let bw_refill_time = 100; // ms let limit_rate = (bw_size * 1000) as f64 / bw_refill_time as f64; - let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(focal)); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let test_img_dir = TempDir::new_with_prefix("/var/tmp/ch").unwrap(); @@ -11655,8 +11655,8 @@ mod fw_cfg { #[test] fn test_fw_cfg() { - let jammy = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(jammy)); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); let kernel_path = direct_kernel_boot_path(); From 017a366a6e85dbb50f47366d46c34381735aeaa0 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 26 Aug 2025 14:22:37 +0100 Subject: [PATCH 0213/1893] tests: First pass at replacing focal images with jammy Signed-off-by: Rob Bradford --- tests/integration.rs | 160 +++++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 6eeeeab20f..c8b9ffd163 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1077,7 +1077,7 @@ fn test_cpu_topology(threads_per_core: u8, cores_per_package: u8, packages: u8, #[allow(unused_variables)] fn _test_guest_numa_nodes(acpi: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); #[cfg(target_arch = "x86_64")] @@ -1154,7 +1154,7 @@ fn _test_guest_numa_nodes(acpi: bool) { #[allow(unused_variables)] fn _test_power_button(acpi: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); let api_socket = temp_api_path(&guest.tmp_dir); @@ -1205,7 +1205,7 @@ fn test_vhost_user_net( generate_host_mac: bool, client_mode_daemon: bool, ) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -1372,7 +1372,7 @@ fn test_vhost_user_blk( direct: bool, prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>, ) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -1514,7 +1514,7 @@ fn test_boot_from_vhost_user_blk( direct: bool, prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>, ) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -1781,7 +1781,7 @@ fn _test_virtio_fs( } fn test_virtio_pmem(discard_writes: bool, specify_size: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -1859,7 +1859,7 @@ fn get_fd_count(pid: u32) -> usize { } fn _test_virtio_vsock(hotplug: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] @@ -2660,7 +2660,7 @@ mod common_parallel { #[cfg(target_arch = "x86_64")] #[cfg(not(feature = "mshv"))] fn test_cpu_physical_bits() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let max_phys_bits: u8 = 36; let mut child = GuestCommand::new(&guest) @@ -2695,7 +2695,7 @@ mod common_parallel { #[test] fn test_cpu_affinity() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); // We need the host to have at least 4 CPUs if we want to be able @@ -2736,7 +2736,7 @@ mod common_parallel { #[test] fn test_virtio_queue_affinity() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); // We need the host to have at least 4 CPUs if we want to be able @@ -2794,7 +2794,7 @@ mod common_parallel { #[test] #[cfg(not(feature = "mshv"))] fn test_large_vm() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=48"]) @@ -2833,7 +2833,7 @@ mod common_parallel { #[test] #[cfg(not(feature = "mshv"))] fn test_huge_memory() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=1"]) @@ -2865,7 +2865,7 @@ mod common_parallel { #[test] fn test_user_defined_memory_regions() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -3017,7 +3017,7 @@ mod common_parallel { #[test] fn test_pci_msi() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=1"]) @@ -3057,7 +3057,7 @@ mod common_parallel { #[test] fn test_virtio_net_ctrl_queue() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=1"]) @@ -3104,7 +3104,7 @@ mod common_parallel { #[test] fn test_pci_multiple_segments() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); // Prepare another disk file for the virtio-disk device @@ -3208,7 +3208,7 @@ mod common_parallel { #[test] fn test_pci_multiple_segments_numa_node() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); #[cfg(target_arch = "x86_64")] @@ -3287,7 +3287,7 @@ mod common_parallel { #[test] fn test_direct_kernel_boot() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -3334,7 +3334,7 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_direct_kernel_boot_bzimage() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut kernel_path = direct_kernel_boot_path(); @@ -3543,7 +3543,7 @@ mod common_parallel { const FULL_VHDX_FILE_SIZE: u64 = 112 << 20; const DYNAMIC_VHDX_NAME: &str = "dynamic.vhdx"; - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -3624,7 +3624,7 @@ mod common_parallel { #[test] fn test_virtio_block_direct_and_firmware() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); // The OS disk must be copied to a location that is not backed by @@ -3748,7 +3748,7 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_split_irqchip() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) @@ -3794,7 +3794,7 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_dmi_serial_number() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) @@ -3830,7 +3830,7 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_dmi_uuid() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) @@ -3866,7 +3866,7 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_dmi_oem_strings() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let s1 = "io.systemd.credential:xx=yy"; @@ -3952,7 +3952,7 @@ mod common_parallel { #[test] fn test_boot_from_virtio_pmem() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4007,7 +4007,7 @@ mod common_parallel { #[test] fn test_multiple_network_interfaces() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4055,7 +4055,7 @@ mod common_parallel { #[test] #[cfg(target_arch = "aarch64")] fn test_pmu_on() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=1"]) @@ -4091,7 +4091,7 @@ mod common_parallel { #[test] fn test_serial_off() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=1"]) @@ -4128,7 +4128,7 @@ mod common_parallel { #[test] fn test_serial_null() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); #[cfg(target_arch = "x86_64")] @@ -4181,7 +4181,7 @@ mod common_parallel { #[test] fn test_serial_tty() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4240,7 +4240,7 @@ mod common_parallel { #[test] fn test_serial_file() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let serial_path = guest.tmp_dir.as_path().join("serial-output"); @@ -4308,7 +4308,7 @@ mod common_parallel { #[test] fn test_pty_interaction() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let serial_option = if cfg!(target_arch = "x86_64") { @@ -4354,7 +4354,7 @@ mod common_parallel { #[test] fn test_serial_socket_interaction() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let serial_socket = guest.tmp_dir.as_path().join("serial.socket"); let serial_socket_pty = guest.tmp_dir.as_path().join("serial.pty"); @@ -4424,7 +4424,7 @@ mod common_parallel { #[test] fn test_virtio_console() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4470,7 +4470,7 @@ mod common_parallel { #[test] fn test_console_file() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let console_path = guest.tmp_dir.as_path().join("console-output"); @@ -4766,7 +4766,7 @@ mod common_parallel { #[test] fn test_direct_kernel_boot_noacpi() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -4810,7 +4810,7 @@ mod common_parallel { #[test] fn test_api_http_shutdown() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); _test_api_shutdown(TargetApi::new_http_api(&guest.tmp_dir), guest) @@ -4818,7 +4818,7 @@ mod common_parallel { #[test] fn test_api_http_delete() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); _test_api_delete(TargetApi::new_http_api(&guest.tmp_dir), guest); @@ -4826,7 +4826,7 @@ mod common_parallel { #[test] fn test_api_http_pause_resume() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); _test_api_pause_resume(TargetApi::new_http_api(&guest.tmp_dir), guest) @@ -4834,7 +4834,7 @@ mod common_parallel { #[test] fn test_api_http_create_boot() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); _test_api_create_boot(TargetApi::new_http_api(&guest.tmp_dir), guest) @@ -4856,7 +4856,7 @@ mod common_parallel { // properly probed first, then removing it, and adding it again by doing a // rescan. fn test_pci_bar_reprogramming() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] @@ -4957,7 +4957,7 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_cpu_hotplug() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let console_str = "console=ttyS0"; @@ -5133,7 +5133,7 @@ mod common_parallel { #[test] fn test_virtio_mem() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -5207,7 +5207,7 @@ mod common_parallel { #[cfg(target_arch = "x86_64")] // Test both vCPU and memory resizing together fn test_resize() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -5267,7 +5267,7 @@ mod common_parallel { #[test] fn test_memory_overhead() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -5305,7 +5305,7 @@ mod common_parallel { // the path for the hotplug disk is not pre-added to Landlock rules, this // the test will result in a failure. fn test_landlock() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] @@ -5368,7 +5368,7 @@ mod common_parallel { } fn _test_disk_hotplug(landlock_enabled: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] @@ -5686,7 +5686,7 @@ mod common_parallel { #[test] fn test_virtio_block_topology() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -5779,7 +5779,7 @@ mod common_parallel { #[test] fn test_virtio_balloon_deflate_on_oom() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -5837,7 +5837,7 @@ mod common_parallel { #[test] fn test_virtio_balloon_free_page_reporting() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); //Let's start a 4G guest with balloon occupied 2G memory @@ -6054,7 +6054,7 @@ mod common_parallel { } fn _test_net_hotplug(pci_segment: Option) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); #[cfg(target_arch = "x86_64")] @@ -6199,7 +6199,7 @@ mod common_parallel { #[test] fn test_initramfs() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut workload_path = dirs::home_dir().unwrap(); workload_path.push("workloads"); @@ -6248,7 +6248,7 @@ mod common_parallel { #[test] fn test_counters() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -6287,7 +6287,7 @@ mod common_parallel { #[test] #[cfg(feature = "guest_debug")] fn test_coredump() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -6335,7 +6335,7 @@ mod common_parallel { #[test] #[cfg(feature = "guest_debug")] fn test_coredump_no_pause() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -6502,7 +6502,7 @@ mod common_parallel { #[test] fn test_tap_from_fd() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -6585,7 +6585,7 @@ mod common_parallel { // guest VM and the host machine. // Details: https://wiki.libvirt.org/page/TroubleshootMacvtapHostFail fn _test_macvtap(hotplug: bool, guest_macvtap_name: &str, host_macvtap_name: &str) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -7018,7 +7018,7 @@ mod common_parallel { // Before trying to run the test, verify the vdpa_sim_blk module is correctly loaded. assert!(exec_host_command_status("lsmod | grep vdpa_sim_blk").success()); - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -7145,7 +7145,7 @@ mod common_parallel { return; } - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -7263,7 +7263,7 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_double_tty() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); let api_socket = temp_api_path(&guest.tmp_dir); @@ -7371,7 +7371,7 @@ mod dbus_api { // booted again. #[test] fn test_api_dbus_and_http_interleaved() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let dbus_api = TargetApi::new_dbus_api(&guest.tmp_dir); let http_api = TargetApi::new_http_api(&guest.tmp_dir); @@ -7441,7 +7441,7 @@ mod dbus_api { #[test] fn test_api_dbus_create_boot() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); _test_api_create_boot(TargetApi::new_dbus_api(&guest.tmp_dir), guest) @@ -7449,7 +7449,7 @@ mod dbus_api { #[test] fn test_api_dbus_shutdown() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); _test_api_shutdown(TargetApi::new_dbus_api(&guest.tmp_dir), guest) @@ -7457,7 +7457,7 @@ mod dbus_api { #[test] fn test_api_dbus_delete() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); _test_api_delete(TargetApi::new_dbus_api(&guest.tmp_dir), guest); @@ -7465,7 +7465,7 @@ mod dbus_api { #[test] fn test_api_dbus_pause_resume() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); _test_api_pause_resume(TargetApi::new_dbus_api(&guest.tmp_dir), guest) @@ -7481,7 +7481,7 @@ mod ivshmem { use crate::*; fn _test_live_migration_ivshmem(local: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); @@ -7667,7 +7667,7 @@ mod ivshmem { #[test] fn test_ivshmem() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -7722,7 +7722,7 @@ mod ivshmem { #[test] fn test_snapshot_restore_ivshmem() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -7955,7 +7955,7 @@ mod common_sequential { } fn _test_snapshot_restore(use_hotplug: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -8209,7 +8209,7 @@ mod common_sequential { #[test] #[cfg_attr(target_arch = "aarch64", ignore = "See #6970")] fn test_snapshot_restore_with_fd() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -8436,7 +8436,7 @@ mod common_sequential { } fn _test_snapshot_restore_devices(pvpanic: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -9917,7 +9917,7 @@ mod live_migration { // live migration; // Note: This test does not use vsock as we can't create two identical vsock on the same host. fn _test_live_migration(upgrade_test: bool, local: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); @@ -10071,7 +10071,7 @@ mod live_migration { } fn _test_live_migration_balloon(upgrade_test: bool, local: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); @@ -10260,7 +10260,7 @@ mod live_migration { } fn _test_live_migration_numa(upgrade_test: bool, local: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); @@ -10814,7 +10814,7 @@ mod live_migration { // 6. Ensure Landlock is enabled on destination VM by hotplugging a disk. As the path for // this disk is not known to the destination VM this step will fail. fn _test_live_migration_with_landlock() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let net_id = "net123"; @@ -11021,7 +11021,7 @@ mod live_migration { } fn _test_live_migration_tcp() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); let console_text = String::from("On a branch floating down river a cricket, singing."); @@ -11371,7 +11371,7 @@ mod rate_limiter { } fn _test_rate_limiter_net(rx: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let test_timeout = 10; @@ -11437,7 +11437,7 @@ mod rate_limiter { let bw_refill_time = 100; // ms let limit_rate = (bw_size * 1000) as f64 / bw_refill_time as f64; - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let test_img_dir = TempDir::new_with_prefix("/var/tmp/ch").unwrap(); @@ -11524,7 +11524,7 @@ mod rate_limiter { let bw_refill_time = 100; // ms let limit_rate = (bw_size * 1000) as f64 / bw_refill_time as f64; - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); let test_img_dir = TempDir::new_with_prefix("/var/tmp/ch").unwrap(); From 15ca4751b72d2fb5d061237cda09f0e3cf6bb8e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:28:17 +0000 Subject: [PATCH 0214/1893] build: Bump crate-ci/typos from 1.37.1 to 1.38.0 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.37.1 to 1.38.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.37.1...v1.38.0) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index c925d0fc7b..c4e98fe7f7 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.37.1 + - uses: crate-ci/typos@v1.38.0 From 15db3212459d4fe6986fc619f80c165c93be6e44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:52:55 +0000 Subject: [PATCH 0215/1893] build: Bump the non-rust-vmm group across 2 directories with 20 updates Bumps the non-rust-vmm group with 8 updates in the / directory: | Package | From | To | | --- | --- | --- | | [serde_with](https://github.com/jonasbb/serde_with) | `3.14.1` | `3.15.0` | | [anstream](https://github.com/rust-cli/anstyle) | `0.6.20` | `0.6.21` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.39` | `1.2.40` | | [lock_api](https://github.com/Amanieu/parking_lot) | `0.4.13` | `0.4.14` | | [openssl-src](https://github.com/alexcrichton/openssl-src-rs) | `300.5.2+3.5.2` | `300.5.3+3.5.4` | | [parking_lot](https://github.com/Amanieu/parking_lot) | `0.12.4` | `0.12.5` | | redox_syscall | `0.5.17` | `0.5.18` | | [windows-link](https://github.com/microsoft/windows-rs) | `0.2.0` | `0.2.1` | Bumps the non-rust-vmm group with 14 updates in the /fuzz directory: | Package | From | To | | --- | --- | --- | | [serde_with](https://github.com/jonasbb/serde_with) | `3.14.1` | `3.15.0` | | [anstream](https://github.com/rust-cli/anstyle) | `0.6.20` | `0.6.21` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.39` | `1.2.40` | | [lock_api](https://github.com/Amanieu/parking_lot) | `0.4.13` | `0.4.14` | | [windows-link](https://github.com/microsoft/windows-rs) | `0.2.0` | `0.2.1` | | [windows-targets](https://github.com/microsoft/windows-rs) | `0.53.4` | `0.53.5` | | [windows_aarch64_gnullvm](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_aarch64_msvc](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_i686_gnu](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_i686_gnullvm](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_i686_msvc](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_x86_64_gnu](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_x86_64_gnullvm](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_x86_64_msvc](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | Updates `serde_with` from 3.14.1 to 3.15.0 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.14.1...v3.15.0) Updates `anstream` from 0.6.20 to 0.6.21 - [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.20...anstream-v0.6.21) Updates `cc` from 1.2.39 to 1.2.40 - [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.39...cc-v1.2.40) Updates `find-msvc-tools` from 0.1.2 to 0.1.3 - [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.2...find-msvc-tools-v0.1.3) Updates `lock_api` from 0.4.13 to 0.4.14 - [Release notes](https://github.com/Amanieu/parking_lot/releases) - [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md) - [Commits](https://github.com/Amanieu/parking_lot/compare/lock_api-v0.4.13...lock_api-v0.4.14) Updates `openssl-src` from 300.5.2+3.5.2 to 300.5.3+3.5.4 - [Release notes](https://github.com/alexcrichton/openssl-src-rs/releases) - [Commits](https://github.com/alexcrichton/openssl-src-rs/commits) Updates `parking_lot` from 0.12.4 to 0.12.5 - [Release notes](https://github.com/Amanieu/parking_lot/releases) - [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md) - [Commits](https://github.com/Amanieu/parking_lot/compare/parking_lot-v0.12.4...parking_lot-v0.12.5) Updates `parking_lot_core` from 0.9.11 to 0.9.12 - [Release notes](https://github.com/Amanieu/parking_lot/releases) - [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md) - [Commits](https://github.com/Amanieu/parking_lot/compare/parking_lot_core-v0.9.11...parking_lot_core-v0.9.12) Updates `redox_syscall` from 0.5.17 to 0.5.18 Updates `serde_with_macros` from 3.14.1 to 3.15.0 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.14.1...v3.15.0) Updates `windows-link` from 0.2.0 to 0.2.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows-targets` from 0.52.6 to 0.53.2 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_gnullvm` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_msvc` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_gnu` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_gnullvm` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_msvc` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnu` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnullvm` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_msvc` from 0.52.6 to 0.53.0 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `serde_with` from 3.14.1 to 3.15.0 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.14.1...v3.15.0) Updates `anstream` from 0.6.20 to 0.6.21 - [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.20...anstream-v0.6.21) Updates `cc` from 1.2.39 to 1.2.40 - [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.39...cc-v1.2.40) Updates `find-msvc-tools` from 0.1.2 to 0.1.3 - [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.2...find-msvc-tools-v0.1.3) Updates `lock_api` from 0.4.13 to 0.4.14 - [Release notes](https://github.com/Amanieu/parking_lot/releases) - [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md) - [Commits](https://github.com/Amanieu/parking_lot/compare/lock_api-v0.4.13...lock_api-v0.4.14) Updates `serde_with_macros` from 3.14.1 to 3.15.0 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.14.1...v3.15.0) Updates `windows-link` from 0.2.0 to 0.2.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows-targets` from 0.53.4 to 0.53.5 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_gnullvm` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_msvc` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_gnu` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_gnullvm` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_msvc` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnu` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnullvm` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_msvc` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) --- updated-dependencies: - dependency-name: serde_with dependency-version: 3.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: anstream dependency-version: 0.6.21 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.40 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: find-msvc-tools dependency-version: 0.1.3 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: lock_api dependency-version: 0.4.14 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: openssl-src dependency-version: 300.5.3+3.5.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: parking_lot dependency-version: 0.12.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: parking_lot_core dependency-version: 0.9.12 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: redox_syscall dependency-version: 0.5.18 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with_macros dependency-version: 3.15.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows-link dependency-version: 0.2.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows-targets dependency-version: 0.53.2 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_aarch64_gnullvm dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_aarch64_msvc dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_i686_gnu dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_i686_gnullvm dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_i686_msvc dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnu dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnullvm dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows_x86_64_msvc dependency-version: 0.53.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: serde_with dependency-version: 3.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: anstream dependency-version: 0.6.21 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.40 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: find-msvc-tools dependency-version: 0.1.3 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: lock_api dependency-version: 0.4.14 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with_macros dependency-version: 3.15.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: windows-link dependency-version: 0.2.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows-targets dependency-version: 0.53.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_aarch64_gnullvm dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_aarch64_msvc dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_i686_gnu dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_i686_gnullvm dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_i686_msvc dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnu dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnullvm dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_x86_64_msvc dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm ... Signed-off-by: dependabot[bot] --- Cargo.lock | 140 +++++++++++++----------------------------------- Cargo.toml | 2 +- fuzz/Cargo.lock | 68 ++++++++++++----------- 3 files changed, 71 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 850ea0274c..251b12b1d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,9 +36,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -363,9 +363,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.39" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "shlex", @@ -594,7 +594,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -670,7 +670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -718,9 +718,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "flume" @@ -1169,11 +1169,10 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1385,9 +1384,9 @@ dependencies = [ [[package]] name = "openssl-src" -version = "300.5.2+3.5.2" +version = "300.5.3+3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4" +checksum = "dc6bad8cd0233b63971e232cc9c5e83039375b8586d2312f31fda85db8f888c2" dependencies = [ "cc", ] @@ -1436,9 +1435,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1446,15 +1445,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -1735,9 +1734,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] @@ -1815,7 +1814,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -1901,20 +1900,19 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.1" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" +checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" dependencies = [ - "serde", - "serde_derive", + "serde_core", "serde_with_macros", ] [[package]] name = "serde_with_macros" -version = "3.14.1" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" +checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" dependencies = [ "darling", "proc-macro2", @@ -2017,7 +2015,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2608,9 +2606,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -2618,7 +2616,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets", ] [[package]] @@ -2630,128 +2628,64 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - [[package]] name = "windows-targets" version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "windows_x86_64_msvc" version = "0.53.0" diff --git a/Cargo.toml b/Cargo.toml index 0f85a0afe7..58c1eb11af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,7 +133,7 @@ igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates serde = "1.0.228" serde_json = "1.0.145" -serde_with = { version = "3.14.1", default-features = false } +serde_with = { version = "3.15.0", default-features = false } # other crates anyhow = "1.0.99" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 439c74872e..151910aa65 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -12,9 +12,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -160,9 +160,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.39" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "jobserver", @@ -374,9 +374,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "flume" @@ -613,11 +613,10 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -959,20 +958,19 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.1" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" +checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" dependencies = [ - "serde", - "serde_derive", + "serde_core", "serde_with_macros", ] [[package]] name = "serde_with_macros" -version = "3.14.1" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" +checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" dependencies = [ "darling", "proc-macro2", @@ -1477,9 +1475,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -1492,9 +1490,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.4" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", "windows_aarch64_gnullvm", @@ -1509,51 +1507,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" From 76bd1418a2ba6681f9249f870e0443f97a6a1b06 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 7 Oct 2025 16:23:40 +0200 Subject: [PATCH 0216/1893] api_client: fix clippy Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- api_client/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api_client/src/lib.rs b/api_client/src/lib.rs index 0ee7fa1d0b..8ced48f7c1 100644 --- a/api_client/src/lib.rs +++ b/api_client/src/lib.rs @@ -188,8 +188,8 @@ pub fn simple_api_full_command_with_fds( request_fds, )?; - if response.is_some() { - println!("{}", response.unwrap()); + if let Some(response) = response { + println!("{response}"); } Ok(()) From 5549d846da6f6c13eade15bad94f49b0885caf4a Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Tue, 12 Aug 2025 17:00:45 +0000 Subject: [PATCH 0217/1893] vmm: support firmware boot on mshv arm64 MSHV (Microsoft Hypervisor) doesn't expose an ITS to guests. Instead it exposes a GICv2m MSI Frame for mapping MSIs to SPIs. Update the MADT generation code to create the GIC MSI Frame structure instead of the ITS structure whenever the guest vGIC is using a GICv2m frame. This allows booting guests via firmware on MSHV. Signed-off-by: Anirudh Rayabharam --- vmm/src/acpi.rs | 17 ++++++++++++++- vmm/src/cpu.rs | 56 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index de2f581294..5a8ab25801 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -39,6 +39,8 @@ pub const ACPI_APIC_GENERIC_CPU_INTERFACE: u8 = 11; #[cfg(target_arch = "aarch64")] pub const ACPI_APIC_GENERIC_DISTRIBUTOR: u8 = 12; #[cfg(target_arch = "aarch64")] +pub const ACPI_APIC_GIC_MSI_FRAME: u8 = 13; +#[cfg(target_arch = "aarch64")] pub const ACPI_APIC_GENERIC_REDISTRIBUTOR: u8 = 14; #[cfg(target_arch = "aarch64")] pub const ACPI_APIC_GENERIC_TRANSLATOR: u8 = 15; @@ -649,7 +651,20 @@ fn create_acpi_tables_internal( xsdt_table_pointers.push(facp_addr.0); // MADT - let madt = cpu_manager.lock().unwrap().create_madt(); + #[cfg(target_arch = "aarch64")] + let vgic = device_manager + .lock() + .unwrap() + .get_interrupt_controller() + .unwrap() + .lock() + .unwrap() + .get_vgic() + .unwrap(); + let madt = cpu_manager.lock().unwrap().create_madt( + #[cfg(target_arch = "aarch64")] + vgic, + ); let madt_addr = facp_addr.checked_add(facp.len() as u64).unwrap(); tables_bytes.extend_from_slice(madt.as_slice()); xsdt_table_pointers.push(madt_addr.0); diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 8eb552297e..01a51f152d 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -41,6 +41,8 @@ use hypervisor::CpuVendor; use hypervisor::HypervisorType; #[cfg(feature = "guest_debug")] use hypervisor::StandardRegisters; +#[cfg(target_arch = "aarch64")] +use hypervisor::arch::aarch64::gic::Vgic; #[cfg(all(target_arch = "aarch64", feature = "guest_debug"))] use hypervisor::arch::aarch64::regs::{ID_AA64MMFR0_EL1, TCR_EL1, TTBR1_EL1}; #[cfg(target_arch = "x86_64")] @@ -274,6 +276,21 @@ struct GicD { pub reserved1: [u8; 3], } +#[cfg(target_arch = "aarch64")] +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(IntoBytes, Immutable, FromBytes)] +struct GicMsiFrame { + pub r#type: u8, + pub length: u8, + pub reserved0: u16, + pub msi_frame_id: u32, + pub base_address: u64, + pub flags: u32, + pub spi_count: u16, + pub spi_base: u16, +} + #[cfg(target_arch = "aarch64")] #[allow(dead_code)] #[repr(C, packed)] @@ -1471,7 +1488,7 @@ impl CpuManager { } #[cfg(not(target_arch = "riscv64"))] - pub fn create_madt(&self) -> Sdt { + pub fn create_madt(&self, #[cfg(target_arch = "aarch64")] vgic: Arc>) -> Sdt { use crate::acpi; // This is also checked in the commandline parsing. assert!(self.config.boot_vcpus <= self.config.max_vcpus); @@ -1520,6 +1537,8 @@ impl CpuManager { #[cfg(target_arch = "aarch64")] { + use arch::layout::{GIC_V2M_COMPATIBLE, SPI_BASE, SPI_NUM}; + /* Notes: * Ignore Local Interrupt Controller Address at byte offset 36 of MADT table. */ @@ -1585,16 +1604,31 @@ impl CpuManager { }; madt.append(gicr); - // See 5.2.12.18 GIC Interrupt Translation Service (ITS) Structure in ACPI spec. - let gicits = GicIts { - r#type: acpi::ACPI_APIC_GENERIC_TRANSLATOR, - length: 20, - reserved0: 0, - translation_id: 0, - base_address: vgic_config.msi_addr, - reserved1: 0, - }; - madt.append(gicits); + if vgic.lock().unwrap().msi_compatibility() == GIC_V2M_COMPATIBLE { + // See 5.2.12.16 GIC MSI Frame Structure in ACPI spec. + let gic_msi_frame = GicMsiFrame { + r#type: acpi::ACPI_APIC_GIC_MSI_FRAME, + length: 24, + reserved0: 0, + msi_frame_id: 0, + base_address: vgic_config.msi_addr, + flags: 1, + spi_count: SPI_NUM as u16, + spi_base: SPI_BASE as u16, + }; + madt.append(gic_msi_frame); + } else { + // See 5.2.12.18 GIC Interrupt Translation Service (ITS) Structure in ACPI spec. + let gicits = GicIts { + r#type: acpi::ACPI_APIC_GENERIC_TRANSLATOR, + length: 20, + reserved0: 0, + translation_id: 0, + base_address: vgic_config.msi_addr, + reserved1: 0, + }; + madt.append(gicits); + } madt.update_checksum(); } From 8e40413886a8f1665e90d6377c7fc76009f81f8b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 2 Oct 2025 14:18:49 -0700 Subject: [PATCH 0218/1893] vmm: validate payload correctly when IGVM is provided While an IGVM is provided validation fails as there is no kernel or firmware. This patch fixes the sev_snp boot failure. Signed-off-by: Muminul Islam --- vmm/src/vm_config.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 380c204343..70c3579025 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -691,6 +691,10 @@ pub enum PayloadConfigError { /// No bootitem provided: neither firmware nor kernel. #[error("No bootitem provided: neither firmware nor kernel")] MissingBootitem, + #[cfg(feature = "igvm")] + /// Specifying a kernel or firmware is not supported when an igvm is provided. + #[error("Specifying a kernel or firmware is not supported when an igvm is provided")] + IgvmPlusOtherPayloads, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -790,6 +794,15 @@ impl PayloadConfig { /// Succeeds if Cloud Hypervisor will be able to boot the configuration. /// Further, warns for some odd configurations. pub fn validate(&mut self) -> Result<(), PayloadConfigError> { + #[cfg(feature = "igvm")] + { + if self.igvm.is_some() { + if self.firmware.is_some() || self.kernel.is_some() { + return Err(PayloadConfigError::IgvmPlusOtherPayloads); + } + return Ok(()); + } + } match (&self.firmware, &self.kernel) { (Some(_firmware), Some(_kernel)) => Err(PayloadConfigError::FirmwarePlusOtherPayloads), (Some(_firmware), None) => { From d760301c8d5c948f623978e000ecd7dac580a245 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 9 Oct 2025 08:35:49 +0100 Subject: [PATCH 0219/1893] tests: Reduce parallelism on x86-64 testing Only use 75% of the available threads - this will reduce dislk and memory pressure. Reducing the chance of flaky tests. See: #7405 Signed-off-by: Rob Bradford --- scripts/run_integration_tests_x86_64.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 3f28e23cdf..7580789ed7 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -178,7 +178,7 @@ ulimit -l unlimited ulimit -n 4096 export RUST_BACKTRACE=1 -time cargo test --release --target "$BUILD_TARGET" $test_features "common_parallel::$test_filter" -- ${test_binary_args[*]} +time cargo test --release --target "$BUILD_TARGET" $test_features "common_parallel::$test_filter" -- ${test_binary_args[*]} --test-threads=$((($(nproc) * 3) / 4)) RES=$? # Run some tests in sequence since the result could be affected by other tests From 205e62aaa8ee53dd38bdb79887479e42a0329295 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 9 Oct 2025 21:44:25 +0000 Subject: [PATCH 0220/1893] scripts: Download jammy images for rate-limiter tests We recently moved many of our tests to use focal to jammy as the guest images, including rate-limiter tests (#7367). We forgot to update the rate-limiter scripts to reflect such change. Our CI pipeline failed to report such error because our self-host runner happened to be not working at the time we land the changes (see #7405). Signed-off-by: Bo Chen --- scripts/run_integration_tests_rate_limiter.sh | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/run_integration_tests_rate_limiter.sh b/scripts/run_integration_tests_rate_limiter.sh index 56fb91e6e0..fd9f689a3f 100755 --- a/scripts/run_integration_tests_rate_limiter.sh +++ b/scripts/run_integration_tests_rate_limiter.sh @@ -20,25 +20,25 @@ fi cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR" -FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2" -FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME" -FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME" -if [ ! -f "$FOCAL_OS_IMAGE" ]; then +JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2" +JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME" +JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1 + time wget --quiet $JAMMY_OS_IMAGE_URL || exit 1 popd || exit fi -FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.raw" -FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME" -if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then +JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.raw" +JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1 + time qemu-img convert -p -f qcow2 -O raw $JAMMY_OS_IMAGE_NAME $JAMMY_OS_RAW_IMAGE_NAME || exit 1 popd || exit fi pushd "$WORKLOADS_DIR" || exit -if ! grep focal sha1sums-x86_64 | sha1sum --check; then +if ! grep jammy sha1sums-x86_64 | sha1sum --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi From 256f0c39e9151766e97dad6175139a30a8599126 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Wed, 17 Sep 2025 22:54:07 +0000 Subject: [PATCH 0221/1893] vmm: Drop 'vfio container' when no active vfio devices This provides clearer life-cycle management of resources around vfio, and aligns better with the kernel behavior as reported below with vfio legacy mode (with vfio container/group). Fixes: #7328 Signed-off-by: Bo Chen Signed-off-by: Yi Wang --- vmm/src/device_manager.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index a580f5282d..992133f7ab 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4965,6 +4965,14 @@ impl DeviceManager { pub(crate) fn acpi_platform_addresses(&self) -> &AcpiPlatformAddresses { &self.acpi_platform_addresses } + + fn cleanup_vfio_container(&mut self) { + // Drop the 'vfio container' instance when "Self" is the only reference + if let Some(1) = self.vfio_container.as_ref().map(Arc::strong_count) { + debug!("Drop 'vfio container' given no active 'vfio devices'."); + self.vfio_container = None; + } + } } #[cfg(feature = "ivshmem")] @@ -5442,6 +5450,7 @@ impl BusDevice for DeviceManager { if let Err(e) = self.eject_device(self.selected_segment as u16, slot_id as u8) { error!("Failed ejecting device {slot_id}: {e:?}"); } + self.cleanup_vfio_container(); slot_bitmap &= !(1 << slot_id); } } From 9cdcd920b21e09b330b6d094621a4c8117b6b8c5 Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Thu, 28 Aug 2025 14:48:34 +0530 Subject: [PATCH 0222/1893] ci: add azure infra setup workflow Add workflow to setup Azure infra to validate MSHV. This is used to provision the environment before executing tests on it. Signed-off-by: AASTHA RAWAT --- .github/workflows/mshv-infra.yaml | 238 ++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 .github/workflows/mshv-infra.yaml diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml new file mode 100644 index 0000000000..2567ebd3c1 --- /dev/null +++ b/.github/workflows/mshv-infra.yaml @@ -0,0 +1,238 @@ +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: + PRIVATE_IP: + description: 'Private IP of the VM' + value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + infra-setup: + name: ${{ inputs.ARCH }} VM Provision + runs-on: + - self-hosted + - Linux + outputs: + 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 "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 + + 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 \ No newline at end of file From 0e50cc320e30c5fb3abc1c31e0123404e85ab6b9 Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Thu, 28 Aug 2025 16:52:56 +0530 Subject: [PATCH 0223/1893] ci: add mshv integration tests workflow Add workflow to run integration tests on mshv. It calls the azure infra setup workflow and executes integration tests in the provisioned environment. Signed-off-by: AASTHA RAWAT --- .github/workflows/mshv-integration.yaml | 122 ++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 .github/workflows/mshv-integration.yaml diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml new file mode 100644 index 0000000000..cb640ff0cd --- /dev/null +++ b/.github/workflows/mshv-integration.yaml @@ -0,0 +1,122 @@ +name: MSHV Integration Tests +on: + push: + branches: + - test_mshv_ci + pull_request: + workflow_dispatch: + inputs: + branch: + description: 'Branch to run integration tests on' + required: true + default: 'main' + +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 + needs: infra-setup + if: ${{ always() && needs.infra-setup.result == 'success' }} + runs-on: + - self-hosted + - Linux + steps: + - name: Determine branch to build + run: | + echo "Determining branch to build and test..." + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "BRANCH=${{ github.event.pull_request.head.ref }}" >> $GITHUB_ENV + else + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + fi + + - name: Run integration tests + env: + BRANCH_NAME: ${{ env.BRANCH }} + KEY: azure_key_${{ github.run_id }} + PR_NUMBER: ${{ github.event.pull_request.number }} + 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" + echo "${BRANCH_NAME}" + git clone --depth 1 --single-branch --branch "$BRANCH_NAME" https://github.com/cloud-hypervisor/cloud-hypervisor.git + cd cloud-hypervisor + + 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 -- -- --skip common_parallel::test_tpm --skip common_parallel::test_cpu_topology_421 --skip common_parallel::test_cpu_topology_142 --skip common_parallel::test_cpu_topology_262 --skip common_sequential::test_snapshot_restore_basic --skip common_sequential::test_snapshot_restore_with_fd --skip common_sequential::test_snapshot_restore_pvpanic --skip virtio_net_latency_us --skip common_parallel::test_cpu_hotplug + EOF + + cleanup: + name: Cleanup + needs: run-tests + if: always() + runs-on: + - self-hosted + - Linux + 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." \ No newline at end of file From 3cf310229c350d1d3624ac17cbd47e9f3f4bed5a Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Mon, 1 Sep 2025 17:03:37 +0530 Subject: [PATCH 0224/1893] ci: add tag for mshv runner Use mshv runner for mshv workflows. Disable manual trigger. Signed-off-by: AASTHA RAWAT --- .github/workflows/mshv-infra.yaml | 4 +--- .github/workflows/mshv-integration.yaml | 14 ++------------ 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index 2567ebd3c1..f0ee482489 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -43,9 +43,7 @@ concurrency: jobs: infra-setup: name: ${{ inputs.ARCH }} VM Provision - runs-on: - - self-hosted - - Linux + runs-on: mshv outputs: PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }} steps: diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index cb640ff0cd..6e2e4e7ca1 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -4,12 +4,6 @@ on: branches: - test_mshv_ci pull_request: - workflow_dispatch: - inputs: - branch: - description: 'Branch to run integration tests on' - required: true - default: 'main' jobs: infra-setup: @@ -32,9 +26,7 @@ jobs: name: Integration Tests needs: infra-setup if: ${{ always() && needs.infra-setup.result == 'success' }} - runs-on: - - self-hosted - - Linux + runs-on: mshv steps: - name: Determine branch to build run: | @@ -94,9 +86,7 @@ jobs: name: Cleanup needs: run-tests if: always() - runs-on: - - self-hosted - - Linux + runs-on: mshv steps: - name: Delete RG env: From bb9d3529dba9175b9181d11a6c0a4e6c6a96d066 Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Fri, 12 Sep 2025 18:12:15 +0530 Subject: [PATCH 0225/1893] ci: update PR trigger for mshv workflow Pull requests from forked repos cannot access GitHub secrets which results in failure of MSHV CI. Switching to pull_request_target resolves this. It allows the workflow to run with access to repo secrets and ensures that code from the base branch is used instead of forked code, preventing potential security risks. Signed-off-by: AASTHA RAWAT --- .github/workflows/mshv-integration.yaml | 34 +++++++++++-------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index 6e2e4e7ca1..3faa8748e0 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -1,9 +1,5 @@ name: MSHV Integration Tests -on: - push: - branches: - - test_mshv_ci - pull_request: +on: [pull_request_target, merge_group] jobs: infra-setup: @@ -27,21 +23,14 @@ jobs: needs: infra-setup if: ${{ always() && needs.infra-setup.result == 'success' }} runs-on: mshv + continue-on-error: true steps: - - name: Determine branch to build - run: | - echo "Determining branch to build and test..." - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "BRANCH=${{ github.event.pull_request.head.ref }}" >> $GITHUB_ENV - else - echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV - fi - - name: Run integration tests env: - BRANCH_NAME: ${{ env.BRANCH }} 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 }} @@ -52,11 +41,18 @@ jobs: set -e echo "Logged in successfully." export PATH="\$HOME/.cargo/bin:\$PATH" - echo "${BRANCH_NAME}" - git clone --depth 1 --single-branch --branch "$BRANCH_NAME" https://github.com/cloud-hypervisor/cloud-hypervisor.git - cd cloud-hypervisor - echo "Loading VDPA kernel modules..." + 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 From d3b8300e9c87285aa0ee563b23531e03c74b1bbe Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Mon, 13 Oct 2025 02:56:44 +0000 Subject: [PATCH 0226/1893] build: Update linux-loader to v0.13.1 Required kernel header are made public in v0.13.1, let's bump to consume the change. Signed-off-by: Ruoqing He --- Cargo.lock | 5 +++-- Cargo.toml | 3 +-- fuzz/Cargo.lock | 5 +++-- fuzz/Cargo.toml | 7 +------ 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 251b12b1d9..11a9ac46af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1155,8 +1155,9 @@ dependencies = [ [[package]] name = "linux-loader" -version = "0.13.0" -source = "git+https://github.com/rust-vmm/linux-loader?branch=main#5fdaed87ddafc89d6abf0b50195a12d19133000d" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53802c0b111faf302a16fa20a2e3a33bd0eab408f60fc34cbfe052f6b153791e" dependencies = [ "vm-memory", ] diff --git a/Cargo.toml b/Cargo.toml index 58c1eb11af..50c22bf427 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,8 +109,7 @@ package.edition = "2024" acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" } kvm-bindings = "0.12.1" kvm-ioctls = "0.22.1" -# TODO: update to 0.13.1+ -linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" } +linux-loader = "0.13.1" mshv-bindings = "0.6.0" mshv-ioctls = "0.6.0" seccompiler = "0.5.0" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 151910aa65..cbcd92e702 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -605,8 +605,9 @@ dependencies = [ [[package]] name = "linux-loader" -version = "0.13.0" -source = "git+https://github.com/rust-vmm/linux-loader?branch=main#5fdaed87ddafc89d6abf0b50195a12d19133000d" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53802c0b111faf302a16fa20a2e3a33bd0eab408f60fc34cbfe052f6b153791e" dependencies = [ "vm-memory", ] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 7c71e881f7..4cd3c7dd45 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -23,12 +23,7 @@ epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } libc = "0.2.176" libfuzzer-sys = "0.4.10" -# TODO: update to 0.13.1+ -linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main", features = [ - "bzimage", - "elf", - "pe", -] } +linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } mshv-bindings = "0.6.0" net_util = { path = "../net_util" } From 0a785c1db27b9439527d1382d1cbec35ee7df5e5 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Mon, 13 Oct 2025 03:01:23 +0000 Subject: [PATCH 0227/1893] build: Use v0.3.0 of vm-fdt instead of main branch We are actually using v0.3.0 of vm-fdt since main branch of that crate already includes everything. Let's pin vm-fdt to v0.3.0 instead. Signed-off-by: Ruoqing He --- Cargo.lock | 3 ++- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11a9ac46af..6793e5ece9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2398,7 +2398,8 @@ dependencies = [ [[package]] name = "vm-fdt" version = "0.3.0" -source = "git+https://github.com/rust-vmm/vm-fdt?branch=main#ef5bd734f5f66fb07722d766981adbc915f0d941" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23" [[package]] name = "vm-memory" diff --git a/Cargo.toml b/Cargo.toml index 50c22bf427..71e9bb498b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,7 +120,7 @@ vhost = { version = "0.14.0", default-features = false } vhost-user-backend = { version = "0.20.0", default-features = false } virtio-bindings = "0.2.6" virtio-queue = "0.16.0" -vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" } +vm-fdt = "0.3.0" vm-memory = "0.16.1" vmm-sys-util = "0.14.0" From f256317ac6fd70b12c516216f960f57f35f00e97 Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Tue, 14 Oct 2025 19:37:20 +0530 Subject: [PATCH 0228/1893] ci: add timeout to mshv integration test workflow Add step-level timeout for running integration tests so that the workflow doesn't get stuck on any test. Signed-off-by: AASTHA RAWAT --- .github/workflows/mshv-integration.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index 3faa8748e0..fcbb02b908 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -26,6 +26,7 @@ jobs: 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 }} From 68f9e82447b2c83fa4a07e1cac1a6d42bc0855fd Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Tue, 14 Oct 2025 19:39:53 +0530 Subject: [PATCH 0229/1893] ci: set continue-on-error for mshv infra workflow Set `continue-on-error` for infra-setup job so that the entire workflow doesn't fail. Signed-off-by: AASTHA RAWAT --- .github/workflows/mshv-infra.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index f0ee482489..887a3ca4e9 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -44,6 +44,7 @@ jobs: infra-setup: name: ${{ inputs.ARCH }} VM Provision runs-on: mshv + continue-on-error: true outputs: PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }} steps: From 4dcd689cd9ee450c76c640fc7a0a4c5813224349 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Thu, 16 Oct 2025 09:18:20 +0000 Subject: [PATCH 0230/1893] ci: update MSHV workflow & job names Rename the MSHV CI workflow to better align with the existing naming scheme and also make the arch explicit. Signed-off-by: Anirudh Rayabharam --- .github/workflows/mshv-integration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index fcbb02b908..b7e8323ca0 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -1,4 +1,4 @@ -name: MSHV Integration Tests +name: Cloud Hypervisor Tests (MSHV) (x86_64) on: [pull_request_target, merge_group] jobs: @@ -19,7 +19,7 @@ jobs: USERNAME: ${{ secrets.MSHV_USERNAME }} run-tests: - name: Integration Tests + name: Integration Tests (x86_64) needs: infra-setup if: ${{ always() && needs.infra-setup.result == 'success' }} runs-on: mshv From d10f6dd3570f6a6922715df527ac4f86f392e994 Mon Sep 17 00:00:00 2001 From: Can Zhang Date: Thu, 4 Sep 2025 17:57:17 +0000 Subject: [PATCH 0231/1893] vmm: Enable RISC-V ACPI support Add necessary definitions and RISC-V ACPI tables to enable ACPI feature. More specifically, this commit add MADT definitions for RISC-V AIA interrupt chips. Signed-off-by: Can Zhang --- arch/src/riscv64/layout.rs | 7 ++- arch/src/riscv64/mod.rs | 5 +++ vmm/src/acpi.rs | 4 ++ vmm/src/cpu.rs | 92 +++++++++++++++++++++++++++++++++++++- vmm/src/device_manager.rs | 14 +++--- vmm/src/lib.rs | 1 - 6 files changed, 111 insertions(+), 12 deletions(-) diff --git a/arch/src/riscv64/layout.rs b/arch/src/riscv64/layout.rs index 3ef7eddf2c..0b9de1bc54 100644 --- a/arch/src/riscv64/layout.rs +++ b/arch/src/riscv64/layout.rs @@ -98,7 +98,12 @@ pub const CMDLINE_MAX_SIZE: usize = 1024; pub const FDT_START: GuestAddress = RAM_START; pub const FDT_MAX_SIZE: u64 = 0x1_0000; -/// Kernel start after FDT +/// Put ACPI table above dtb +pub const ACPI_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE); +pub const ACPI_MAX_SIZE: u64 = 0x20_0000; +pub const RSDP_POINTER: GuestAddress = ACPI_START; + +/// Kernel start after FDT and ACPI pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE); /// Pci high memory base diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index 6a0342b3cd..e6d70d38ad 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -22,6 +22,11 @@ use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic}; pub use self::fdt::DeviceInfoForFdt; use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType}; +pub const CLOUDHV_IRQCHIP_NUM_MSIS: u16 = 255; +pub const CLOUDHV_IRQCHIP_NUM_SOURCES: u8 = 96; +pub const CLOUDHV_IRQCHIP_NUM_PRIO_BITS: u8 = 3; +pub const CLOUDHV_IRQCHIP_MAX_GUESTS_BITS: u8 = 3; +pub const CLOUDHV_IRQCHIP_MAX_GUESTS: u8 = (1 << CLOUDHV_IRQCHIP_MAX_GUESTS_BITS) - 1; pub const _NSIG: i32 = 65; /// Errors thrown while configuring riscv64 system. diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 5a8ab25801..fa107d037d 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -44,6 +44,10 @@ pub const ACPI_APIC_GIC_MSI_FRAME: u8 = 13; pub const ACPI_APIC_GENERIC_REDISTRIBUTOR: u8 = 14; #[cfg(target_arch = "aarch64")] pub const ACPI_APIC_GENERIC_TRANSLATOR: u8 = 15; +#[cfg(target_arch = "riscv64")] +pub const ACPI_RISC_V_IMSIC: u8 = 0x19; +#[cfg(target_arch = "riscv64")] +pub const ACPI_RISC_V_APLIC: u8 = 0x1A; #[allow(dead_code)] #[repr(C, packed)] diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 01a51f152d..a6d1f42605 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -21,7 +21,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Barrier, Mutex}; use std::{cmp, io, result, thread}; -#[cfg(not(target_arch = "riscv64"))] use acpi_tables::sdt::Sdt; use acpi_tables::{Aml, aml}; use anyhow::anyhow; @@ -330,6 +329,47 @@ struct ProcessorHierarchyNode { pub num_private_resources: u32, } +#[cfg(target_arch = "riscv64")] +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(IntoBytes, Immutable, FromBytes)] +struct Imsic { + pub r#type: u8, + pub length: u8, + pub version: u8, + pub reserved: u8, + pub flags: u32, + /// Number of supervisor mode Interrupt Identities + pub n_supervisor_int_id: u16, + /// Number of guest mode Interrupt Identities + pub n_guest_int_id: u16, + pub geust_index_bits: u8, + pub hart_index_bits: u8, + pub group_index_bits: u8, + pub group_index_shift: u8, +} + +#[cfg(target_arch = "riscv64")] +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(IntoBytes, Immutable, FromBytes)] +struct Aplic { + pub r#type: u8, + pub length: u8, + pub version: u8, + pub aplic_id: u8, + pub flags: u32, + pub hardware_id: u64, + /// Number of Interrupt Delivery Control (IDC) structures + pub n_idc: u16, + /// Total External Interrupt Sources Supported + pub n_external_inc_sources: u16, + /// Global System Interrupt Base + pub gsi_inc_base: u32, + pub aplic_addr: u64, + pub aplic_size: u32, +} + #[allow(dead_code)] #[repr(C, packed)] #[derive(Default, IntoBytes, Immutable, FromBytes)] @@ -1487,7 +1527,6 @@ impl CpuManager { }) } - #[cfg(not(target_arch = "riscv64"))] pub fn create_madt(&self, #[cfg(target_arch = "aarch64")] vgic: Arc>) -> Sdt { use crate::acpi; // This is also checked in the commandline parsing. @@ -1633,6 +1672,55 @@ impl CpuManager { madt.update_checksum(); } + #[cfg(target_arch = "riscv64")] + { + /* Notes: + * Ignore Local Interrupt Controller Address at byte offset 36 of MADT table. + */ + + let mut hart_index_bits = 0; + while (1 << hart_index_bits) < self.config.boot_vcpus { + hart_index_bits += 1; + } + + // See section 5.2.12.28. RISC-V Incoming MSI Controller (IMSIC) + // Structure in ACPI spec. + let imsic = Imsic { + r#type: acpi::ACPI_RISC_V_IMSIC, + length: 16, + version: 1, + reserved: 0, + flags: 0, + n_supervisor_int_id: arch::riscv64::CLOUDHV_IRQCHIP_NUM_MSIS, + n_guest_int_id: arch::riscv64::CLOUDHV_IRQCHIP_NUM_MSIS, + geust_index_bits: 1, + hart_index_bits: hart_index_bits, + group_index_bits: 1, + // IMSIC_MMIO_GROUP_MIN_SHIFT + group_index_shift: 24, + }; + madt.append(imsic); + + // See section 5.2.12.29. RISC-V Advanced Platform Level Interrupt + // Controller (APLIC) Structure in ACPI spec. + let aplic = Aplic { + r#type: acpi::ACPI_RISC_V_APLIC, + length: 36, + version: 1, + aplic_id: 0, + flags: 0, + hardware_id: 0, + n_idc: 0, + n_external_inc_sources: arch::riscv64::CLOUDHV_IRQCHIP_NUM_SOURCES as u16, + gsi_inc_base: arch::layout::IRQ_BASE, + aplic_addr: arch::layout::APLIC_START.0, + aplic_size: arch::layout::APLIC_SIZE as u32, + }; + madt.append(aplic); + + madt.update_checksum(); + } + madt } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 992133f7ab..4b04ee8ebe 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -22,7 +22,6 @@ use std::sync::{Arc, Mutex}; use std::time::Instant; use acpi_tables::sdt::GenericAddress; -#[cfg(not(target_arch = "riscv64"))] use acpi_tables::{Aml, aml}; #[cfg(not(target_arch = "riscv64"))] use anyhow::anyhow; @@ -1043,7 +1042,6 @@ pub struct DeviceManager { // activation and thus start the threads from the VMM thread activate_evt: EventFd, - #[cfg(not(target_arch = "riscv64"))] acpi_address: GuestAddress, selected_segment: usize, @@ -1083,7 +1081,6 @@ pub struct DeviceManager { // Pending activations pending_activations: Arc>>, - #[cfg(not(target_arch = "riscv64"))] // Addresses for ACPI platform devices e.g. ACPI PM timer, sleep/reset registers acpi_platform_addresses: AcpiPlatformAddresses, @@ -1339,7 +1336,6 @@ impl DeviceManager { activate_evt: activate_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, - #[cfg(not(target_arch = "riscv64"))] acpi_address, selected_segment: 0, serial_manager: None, @@ -1358,7 +1354,6 @@ impl DeviceManager { #[cfg(not(target_arch = "riscv64"))] timestamp, pending_activations: Arc::new(Mutex::new(Vec::default())), - #[cfg(not(target_arch = "riscv64"))] acpi_platform_addresses: AcpiPlatformAddresses::default(), snapshot, rate_limit_groups, @@ -4961,7 +4956,6 @@ impl DeviceManager { Ok(()) } - #[cfg(not(target_arch = "riscv64"))] pub(crate) fn acpi_platform_addresses(&self) -> &AcpiPlatformAddresses { &self.acpi_platform_addresses } @@ -5088,11 +5082,12 @@ impl Aml for TpmDevice { } } -#[cfg(not(target_arch = "riscv64"))] impl Aml for DeviceManager { fn to_aml_bytes(&self, sink: &mut dyn acpi_tables::AmlSink) { #[cfg(target_arch = "aarch64")] use arch::aarch64::DeviceInfoForFdt; + #[cfg(target_arch = "riscv64")] + use arch::riscv64::DeviceInfoForFdt; let mut pci_scan_methods = Vec::new(); for i in 0..self.pci_segments.len() { @@ -5218,7 +5213,7 @@ impl Aml for DeviceManager { // Serial device #[cfg(target_arch = "x86_64")] let serial_irq = 4; - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] let serial_irq = if self.config.lock().unwrap().serial.clone().mode != ConsoleOutputMode::Off { self.get_device_info() @@ -5240,6 +5235,8 @@ impl Aml for DeviceManager { &aml::EISAName::new("PNP0501"), #[cfg(target_arch = "aarch64")] &"ARMH0011", + #[cfg(target_arch = "riscv64")] + &"RISCV011", ), &aml::Name::new("_UID".into(), &aml::ZERO), &aml::Name::new("_DDN".into(), &"COM1"), @@ -5273,6 +5270,7 @@ impl Aml for DeviceManager { ) .to_aml_bytes(sink); + #[cfg(not(target_arch = "riscv64"))] if self.config.lock().unwrap().tpm.is_some() { // Add tpm device TpmDevice {}.to_aml_bytes(sink); diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index a16c2480f4..bac3b3e134 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -67,7 +67,6 @@ use crate::vm_config::{ VmConfig, VsockConfig, }; -#[cfg(not(target_arch = "riscv64"))] mod acpi; pub mod api; mod clone3; From a8335d0b06469fb4d2b52757016080aeef00d84e Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Mon, 20 Oct 2025 23:17:19 +0000 Subject: [PATCH 0232/1893] ci: Extend typos to accept MSIS Previous RISC-V ACPI related change introduced MSIS which will be rejected by typo check, let's extend the typos to accept it. Signed-off-by: Ruoqing He --- .typos.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/.typos.toml b/.typos.toml index de411b118b..b59130fe70 100644 --- a/.typos.toml +++ b/.typos.toml @@ -11,6 +11,7 @@ CLASSE = "CLASSE" Dake = "Dake" EXTINT = "EXTINT" INOUT = "INOUT" +MSIS = "MSIS" # MSIs (Message Signaled Interrupt) SME = "SME" # Secure Memory Encryption THR = "THR" # Transmitter Holding Register TRANSLATER = "TRANSLATER" From 4290d4f20e24fa8b40122aa2fb4e96ec783b11c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:07:50 +0000 Subject: [PATCH 0233/1893] build: Bump the non-rust-vmm group across 2 directories with 25 updates Bumps the non-rust-vmm group with 20 updates in the / directory: | Package | From | To | | --- | --- | --- | | [igvm](https://github.com/microsoft/igvm) | ``9b67b6f`` | ``5d2b5a5`` | | [clap](https://github.com/clap-rs/clap) | `4.5.48` | `4.5.49` | | [libc](https://github.com/rust-lang/libc) | `0.2.176` | `0.2.177` | | [gdbstub](https://github.com/daniel5151/gdbstub) | `0.7.7` | `0.7.8` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.40` | `1.2.41` | | [clap_lex](https://github.com/clap-rs/clap) | `0.7.5` | `0.7.6` | | [env_filter](https://github.com/rust-cli/env_logger) | `0.1.3` | `0.1.4` | | [regex](https://github.com/rust-lang/regex) | `1.11.3` | `1.12.2` | | [regex-syntax](https://github.com/rust-lang/regex) | `0.8.6` | `0.8.8` | | [toml_datetime](https://github.com/toml-rs/toml) | `0.7.2` | `0.7.3` | | [toml_edit](https://github.com/toml-rs/toml) | `0.23.6` | `0.23.7` | | [windows-targets](https://github.com/microsoft/windows-rs) | `0.53.2` | `0.53.5` | | [windows_aarch64_gnullvm](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_aarch64_msvc](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_i686_gnu](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_i686_gnullvm](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_i686_msvc](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_x86_64_gnu](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_x86_64_gnullvm](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | | [windows_x86_64_msvc](https://github.com/microsoft/windows-rs) | `0.53.0` | `0.53.1` | Bumps the non-rust-vmm group with 7 updates in the /fuzz directory: | Package | From | To | | --- | --- | --- | | [clap](https://github.com/clap-rs/clap) | `4.5.48` | `4.5.49` | | [libc](https://github.com/rust-lang/libc) | `0.2.176` | `0.2.177` | | [gdbstub](https://github.com/daniel5151/gdbstub) | `0.7.7` | `0.7.8` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.40` | `1.2.41` | | [clap_lex](https://github.com/clap-rs/clap) | `0.7.5` | `0.7.6` | | [toml_datetime](https://github.com/toml-rs/toml) | `0.7.2` | `0.7.3` | | [toml_edit](https://github.com/toml-rs/toml) | `0.23.6` | `0.23.7` | Updates `igvm` from `9b67b6f` to `5d2b5a5` - [Release notes](https://github.com/microsoft/igvm/releases) - [Commits](https://github.com/microsoft/igvm/compare/9b67b6fa774f4a842718c5c70645973fe73ef616...5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8) Updates `igvm_defs` from `9b67b6f` to `5d2b5a5` - [Release notes](https://github.com/microsoft/igvm/releases) - [Commits](https://github.com/microsoft/igvm/compare/9b67b6fa774f4a842718c5c70645973fe73ef616...5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8) Updates `clap` from 4.5.48 to 4.5.49 - [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.48...clap_complete-v4.5.49) Updates `libc` from 0.2.176 to 0.2.177 - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.177/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.176...0.2.177) Updates `gdbstub` from 0.7.7 to 0.7.8 - [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.7...0.7.8) Updates `cc` from 1.2.40 to 1.2.41 - [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.40...cc-v1.2.41) Updates `clap_builder` from 4.5.48 to 4.5.49 - [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.48...v4.5.49) Updates `clap_lex` from 0.7.5 to 0.7.6 - [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.5...clap_lex-v0.7.6) Updates `env_filter` from 0.1.3 to 0.1.4 - [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.3...env_filter-v0.1.4) Updates `find-msvc-tools` from 0.1.3 to 0.1.4 - [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.3...find-msvc-tools-v0.1.4) Updates `regex` from 1.11.3 to 1.12.2 - [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.11.3...1.12.2) Updates `regex-automata` from 0.4.11 to 0.4.13 - [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.11...regex-automata-0.4.13) Updates `regex-syntax` from 0.8.6 to 0.8.8 - [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.6...regex-syntax-0.8.8) Updates `toml_datetime` from 0.7.2 to 0.7.3 - [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.7.2...toml_datetime-v0.7.3) Updates `toml_edit` from 0.23.6 to 0.23.7 - [Commits](https://github.com/toml-rs/toml/compare/v0.23.6...v0.23.7) Updates `toml_parser` from 1.0.3 to 1.0.4 - [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.0.3...toml_parser-v1.0.4) Updates `windows-targets` from 0.53.2 to 0.53.5 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_gnullvm` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_aarch64_msvc` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_gnu` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_gnullvm` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_i686_msvc` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnu` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_gnullvm` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `windows_x86_64_msvc` from 0.53.0 to 0.53.1 - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) Updates `clap` from 4.5.48 to 4.5.49 - [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.48...clap_complete-v4.5.49) Updates `libc` from 0.2.176 to 0.2.177 - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.177/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.176...0.2.177) Updates `gdbstub` from 0.7.7 to 0.7.8 - [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.7...0.7.8) Updates `cc` from 1.2.40 to 1.2.41 - [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.40...cc-v1.2.41) Updates `clap_builder` from 4.5.48 to 4.5.49 - [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.48...v4.5.49) Updates `clap_lex` from 0.7.5 to 0.7.6 - [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.5...clap_lex-v0.7.6) Updates `find-msvc-tools` from 0.1.3 to 0.1.4 - [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.3...find-msvc-tools-v0.1.4) Updates `toml_datetime` from 0.7.2 to 0.7.3 - [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.7.2...toml_datetime-v0.7.3) Updates `toml_edit` from 0.23.6 to 0.23.7 - [Commits](https://github.com/toml-rs/toml/compare/v0.23.6...v0.23.7) Updates `toml_parser` from 1.0.3 to 1.0.4 - [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.0.3...toml_parser-v1.0.4) --- updated-dependencies: - dependency-name: igvm dependency-version: 5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8 dependency-type: direct:production dependency-group: non-rust-vmm - dependency-name: igvm_defs dependency-version: 5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8 dependency-type: direct:production dependency-group: non-rust-vmm - dependency-name: clap dependency-version: 4.5.49 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: libc dependency-version: 0.2.177 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: gdbstub dependency-version: 0.7.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.41 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap_builder dependency-version: 4.5.49 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap_lex dependency-version: 0.7.6 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: env_filter dependency-version: 0.1.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: find-msvc-tools dependency-version: 0.1.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: regex dependency-version: 1.12.2 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: regex-automata dependency-version: 0.4.13 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: regex-syntax dependency-version: 0.8.8 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: toml_datetime dependency-version: 0.7.3 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: toml_edit dependency-version: 0.23.7 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: toml_parser dependency-version: 1.0.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows-targets dependency-version: 0.53.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_aarch64_gnullvm dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_aarch64_msvc dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_i686_gnu dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_i686_gnullvm dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_i686_msvc dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnu dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_x86_64_gnullvm dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: windows_x86_64_msvc dependency-version: 0.53.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap dependency-version: 4.5.49 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: libc dependency-version: 0.2.177 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: gdbstub dependency-version: 0.7.8 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.41 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap_builder dependency-version: 4.5.49 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap_lex dependency-version: 0.7.6 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: find-msvc-tools dependency-version: 0.1.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: toml_datetime dependency-version: 0.7.3 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: toml_edit dependency-version: 0.23.7 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: toml_parser dependency-version: 1.0.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm ... Signed-off-by: dependabot[bot] --- Cargo.lock | 97 +++++++++++++++++++++++++------------------------ Cargo.toml | 4 +- fuzz/Cargo.lock | 43 +++++++++++----------- fuzz/Cargo.toml | 2 +- vmm/Cargo.toml | 2 +- 5 files changed, 75 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6793e5ece9..c1b2507b4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,9 +363,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.40" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "shlex", @@ -385,18 +385,18 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" dependencies = [ "anstream", "anstyle", @@ -407,9 +407,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cloud-hypervisor" @@ -626,9 +626,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -718,9 +718,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flume" @@ -844,9 +844,9 @@ dependencies = [ [[package]] name = "gdbstub" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b686b198dfaa4109ebd0443d2841bc521e4b4b2915f1d84b3bb50332a8cdc1ae" +checksum = "72742d2b395902caf8a5d520d0dd3334ba6d1138938429200e58d5174e275f3f" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -970,7 +970,7 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "igvm" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#9b67b6fa774f4a842718c5c70645973fe73ef616" +source = "git+https://github.com/microsoft/igvm?branch=main#5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8" dependencies = [ "bitfield-struct 0.10.1", "crc32fast", @@ -987,7 +987,7 @@ dependencies = [ [[package]] name = "igvm_defs" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#9b67b6fa774f4a842718c5c70645973fe73ef616" +source = "git+https://github.com/microsoft/igvm?branch=main#5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8" dependencies = [ "bitfield-struct 0.10.1", "open-enum", @@ -1113,9 +1113,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libredox" @@ -1755,9 +1755,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1767,9 +1767,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1778,9 +1778,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "remain" @@ -2091,18 +2091,18 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.6" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", @@ -2112,9 +2112,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] @@ -2632,10 +2632,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ + "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", @@ -2648,51 +2649,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" diff --git a/Cargo.toml b/Cargo.toml index 71e9bb498b..2426b3c0bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,13 +139,13 @@ anyhow = "1.0.99" bitflags = "2.9.4" byteorder = "1.5.0" cfg-if = "1.0.3" -clap = "4.5.48" +clap = "4.5.49" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" epoll = "4.4.0" flume = "0.11.1" -libc = "0.2.176" +libc = "0.2.177" log = "0.4.28" signal-hook = "0.3.18" thiserror = "2.0.17" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index cbcd92e702..8c0a8eae76 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -160,9 +160,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.40" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "jobserver", @@ -178,18 +178,18 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "clap" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" dependencies = [ "anstream", "anstyle", @@ -199,9 +199,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cloud-hypervisor-fuzz" @@ -374,9 +374,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flume" @@ -410,9 +410,9 @@ checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "gdbstub" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b686b198dfaa4109ebd0443d2841bc521e4b4b2915f1d84b3bb50332a8cdc1ae" +checksum = "72742d2b395902caf8a5d520d0dd3334ba6d1138938429200e58d5174e275f3f" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -589,9 +589,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libfuzzer-sys" @@ -1082,18 +1082,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.6" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", @@ -1103,9 +1103,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] @@ -1285,7 +1285,8 @@ dependencies = [ [[package]] name = "vm-fdt" version = "0.3.0" -source = "git+https://github.com/rust-vmm/vm-fdt?branch=main#ef5bd734f5f66fb07722d766981adbc915f0d941" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23" [[package]] name = "vm-memory" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 4cd3c7dd45..319a4ef321 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.176" +libc = "0.2.177" libfuzzer-sys = "0.4.10" linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index de64f91e3c..b7035f030c 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -48,7 +48,7 @@ epoll = { workspace = true } event_monitor = { path = "../event_monitor" } flume = { workspace = true } futures = { version = "0.3.31", optional = true } -gdbstub = { version = "0.7.7", optional = true } +gdbstub = { version = "0.7.8", optional = true } gdbstub_arch = { version = "0.3.2", optional = true } hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } From 11eabaf97de28c5a7130d4a7f74947e4be75d65a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 21 Oct 2025 15:37:48 +0100 Subject: [PATCH 0234/1893] tests: Add second network interface definition to guest Add a second L1 network interface definition to the guest Cloud Init configuration, including an additional host IP. Do this by splitting the network range into two /25s. For clarity the network struct members have also been renamed. Signed-off-by: Rob Bradford --- performance-metrics/src/performance_tests.rs | 8 +- test_data/cloud-init/ubuntu/ci/network-config | 14 ++- test_infra/src/lib.rs | 60 ++++++----- tests/integration.rs | 100 +++++++++--------- 4 files changed, 99 insertions(+), 83 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index e29dca1743..f52156c70f 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -119,8 +119,8 @@ pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 { let num_queues = control.num_queues.unwrap(); let queue_size = control.queue_size.unwrap(); let net_params = format!( - "tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={}", - guest.network.guest_mac, guest.network.host_ip, num_queues, queue_size, + "tap=,mac={},ip={},mask=255.255.255.128,num_queues={},queue_size={}", + guest.network.guest_mac0, guest.network.host_ip0, num_queues, queue_size, ); let mut child = GuestCommand::new(&guest) @@ -160,8 +160,8 @@ pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 { let num_queues = control.num_queues.unwrap(); let queue_size = control.queue_size.unwrap(); let net_params = format!( - "tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={}", - guest.network.guest_mac, guest.network.host_ip, num_queues, queue_size, + "tap=,mac={},ip={},mask=255.255.255.128,num_queues={},queue_size={}", + guest.network.guest_mac0, guest.network.host_ip0, num_queues, queue_size, ); let mut child = GuestCommand::new(&guest) diff --git a/test_data/cloud-init/ubuntu/ci/network-config b/test_data/cloud-init/ubuntu/ci/network-config index ce5ed03f54..4ec7e03302 100644 --- a/test_data/cloud-init/ubuntu/ci/network-config +++ b/test_data/cloud-init/ubuntu/ci/network-config @@ -4,23 +4,29 @@ ethernets: match: macaddress: 12:34:56:78:90:ab addresses: - - 192.168.2.2/24 + - 192.168.2.2/25 gateway4: 192.168.2.1 id1: match: macaddress: de:ad:be:ef:12:34 addresses: - - 192.168.2.3/24 + - 192.168.2.3/25 gateway4: 192.168.2.1 id2: match: macaddress: de:ad:be:ef:34:56 addresses: - - 192.168.2.4/24 + - 192.168.2.4/25 gateway4: 192.168.2.1 id3: match: macaddress: de:ad:be:ef:56:78 addresses: - - 192.168.2.5/24 + - 192.168.2.5/25 gateway4: 192.168.2.1 + id4: + match: + macaddress: de:ad:be:ef:78:90 + addresses: + - 192.168.2.130/25 + gateway4: 192.168.2.129 diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 8b260a0a63..24a818c194 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -57,12 +57,15 @@ pub enum Error { } pub struct GuestNetworkConfig { - pub guest_ip: String, + pub guest_ip0: String, + pub host_ip0: String, + pub guest_mac0: String, + pub guest_ip1: String, + pub host_ip1: String, + pub guest_mac1: String, pub l2_guest_ip1: String, pub l2_guest_ip2: String, pub l2_guest_ip3: String, - pub host_ip: String, - pub guest_mac: String, pub l2_guest_mac1: String, pub l2_guest_mac2: String, pub l2_guest_mac3: String, @@ -92,7 +95,7 @@ impl GuestNetworkConfig { let start = std::time::Instant::now(); // The 'port' is unique per 'GUEST' and listening to wild-card ip avoids retrying on 'TcpListener::bind()' let listen_addr = format!("0.0.0.0:{}", self.tcp_listener_port); - let expected_guest_addr = self.guest_ip.as_str(); + let expected_guest_addr = self.guest_ip0.as_str(); let mut s = String::new(); let timeout = match custom_timeout { Some(t) => t, @@ -277,7 +280,7 @@ impl DiskConfig for UbuntuDiskConfig { "@DEFAULT_TCP_LISTENER_MESSAGE", DEFAULT_TCP_LISTENER_MESSAGE, ); - user_data_string = user_data_string.replace("@HOST_IP", &network.host_ip); + user_data_string = user_data_string.replace("@HOST_IP", &network.host_ip0); user_data_string = user_data_string.replace("@TCP_LISTENER_PORT", &network.tcp_listener_port.to_string()); @@ -293,13 +296,17 @@ impl DiskConfig for UbuntuDiskConfig { .read_to_string(&mut network_config_string) .expect("Expected reading network-config file to succeed"); - network_config_string = network_config_string.replace("192.168.2.1", &network.host_ip); - network_config_string = network_config_string.replace("192.168.2.2", &network.guest_ip); + network_config_string = network_config_string.replace("192.168.2.1", &network.host_ip0); + network_config_string = network_config_string.replace("192.168.2.2", &network.guest_ip0); + network_config_string = network_config_string.replace("192.168.2.129", &network.host_ip1); + network_config_string = network_config_string.replace("192.168.2.130", &network.guest_ip1); network_config_string = network_config_string.replace("192.168.2.3", &network.l2_guest_ip1); network_config_string = network_config_string.replace("192.168.2.4", &network.l2_guest_ip2); network_config_string = network_config_string.replace("192.168.2.5", &network.l2_guest_ip3); network_config_string = - network_config_string.replace("12:34:56:78:90:ab", &network.guest_mac); + network_config_string.replace("12:34:56:78:90:ab", &network.guest_mac0); + network_config_string = + network_config_string.replace("de:ad:be:ef:78:90", &network.guest_mac1); network_config_string = network_config_string.replace("de:ad:be:ef:12:34", &network.l2_guest_mac1); network_config_string = @@ -858,12 +865,15 @@ impl Guest { let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); let network = GuestNetworkConfig { - guest_ip: format!("{class}.{id}.2"), + guest_ip0: format!("{class}.{id}.2"), + host_ip0: format!("{class}.{id}.1"), + guest_mac0: format!("12:34:56:78:90:{id:02x}"), + guest_ip1: format!("{class}.{id}.130"), + host_ip1: format!("{class}.{id}.129"), + guest_mac1: format!("de:ad:be:ef:78:{id:02x}"), l2_guest_ip1: format!("{class}.{id}.3"), l2_guest_ip2: format!("{class}.{id}.4"), l2_guest_ip3: format!("{class}.{id}.5"), - host_ip: format!("{class}.{id}.1"), - guest_mac: format!("12:34:56:78:90:{id:02x}"), l2_guest_mac1: format!("de:ad:be:ef:12:{id:02x}"), l2_guest_mac2: format!("de:ad:be:ef:34:{id:02x}"), l2_guest_mac3: format!("de:ad:be:ef:56:{id:02x}"), @@ -889,29 +899,29 @@ impl Guest { pub fn default_net_string(&self) -> String { format!( - "tap=,mac={},ip={},mask=255.255.255.0", - self.network.guest_mac, self.network.host_ip + "tap=,mac={},ip={},mask=255.255.255.128", + self.network.guest_mac0, self.network.host_ip0 ) } pub fn default_net_string_w_iommu(&self) -> String { format!( - "tap=,mac={},ip={},mask=255.255.255.0,iommu=on", - self.network.guest_mac, self.network.host_ip + "tap=,mac={},ip={},mask=255.255.255.128,iommu=on", + self.network.guest_mac0, self.network.host_ip0 ) } pub fn default_net_string_w_mtu(&self, mtu: u16) -> String { format!( - "tap=,mac={},ip={},mask=255.255.255.0,mtu={}", - self.network.guest_mac, self.network.host_ip, mtu + "tap=,mac={},ip={},mask=255.255.255.128,mtu={}", + self.network.guest_mac0, self.network.host_ip0, mtu ) } pub fn ssh_command(&self, command: &str) -> Result { ssh_command_ip( command, - &self.network.guest_ip, + &self.network.guest_ip0, DEFAULT_SSH_RETRIES, DEFAULT_SSH_TIMEOUT, ) @@ -921,7 +931,7 @@ impl Guest { pub fn ssh_command_l1(&self, command: &str) -> Result { ssh_command_ip( command, - &self.network.guest_ip, + &self.network.guest_ip0, DEFAULT_SSH_RETRIES, DEFAULT_SSH_TIMEOUT, ) @@ -963,8 +973,8 @@ impl Guest { cpu_count, kernel_path, kernel_cmd, - self.network.host_ip, - self.network.guest_mac, + self.network.host_ip0, + self.network.guest_mac0, self.disk_config.disk(DiskType::OperatingSystem).unwrap().as_str(), self.disk_config.disk(DiskType::CloudInit).unwrap().as_str(), } @@ -1109,7 +1119,7 @@ impl Guest { pub fn check_vsock(&self, socket: &str) { // Listen from guest on vsock CID=3 PORT=16 // SOCKET-LISTEN::: - let guest_ip = self.network.guest_ip.clone(); + let guest_ip = self.network.guest_ip0.clone(); let listen_socat = thread::spawn(move || { ssh_command_ip("sudo socat - SOCKET-LISTEN:40:0:x00x00x10x00x00x00x03x00x00x00x00x00x00x00 > vsock_log", &guest_ip, DEFAULT_SSH_RETRIES, DEFAULT_SSH_TIMEOUT).unwrap(); }); @@ -1612,7 +1622,7 @@ pub fn measure_virtio_net_throughput( cmd.args([ "-J", // Output in JSON format "-c", - &guest.network.guest_ip, + &guest.network.guest_ip0, "-p", &format!("{}", default_port + n), "-t", @@ -1710,7 +1720,7 @@ pub fn measure_virtio_net_latency(guest: &Guest, test_timeout: u32) -> Result Result std::process::Child { - let listen_address = format!("--listen-address={}", self.guest.network.host_ip); + let listen_address = format!("--listen-address={}", self.guest.network.host_ip0); let dhcp_host = format!( "--dhcp-host={},{}", - self.guest.network.guest_mac, self.guest.network.guest_ip + self.guest.network.guest_mac0, self.guest.network.guest_ip0 ); let dhcp_range = format!( "--dhcp-range=eth,{},{}", - self.guest.network.guest_ip, self.guest.network.guest_ip + self.guest.network.guest_ip0, self.guest.network.guest_ip0 ); Command::new("dnsmasq") @@ -8820,7 +8820,7 @@ mod windows { let out = ssh_command_ip_with_auth( cmd, &self.auth, - &self.guest.network.guest_ip, + &self.guest.network.guest_ip0, { let mut ret = 1; let mut tmo_acc = 0; @@ -8954,9 +8954,9 @@ mod windows { .args([ "--net", format!( - "tap=,mac={},ip={},mask=255.255.255.0,num_queues=8", - windows_guest.guest().network.guest_mac, - windows_guest.guest().network.host_ip + "tap=,mac={},ip={},mask=255.255.255.128,num_queues=8", + windows_guest.guest().network.guest_mac0, + windows_guest.guest().network.host_ip0 ) .as_str(), ]) @@ -9923,8 +9923,8 @@ mod live_migration { let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.0", - net_id, guest.network.guest_mac, guest.network.host_ip + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 ); let memory_param: &[&str] = if local { @@ -10077,8 +10077,8 @@ mod live_migration { let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.0", - net_id, guest.network.guest_mac, guest.network.host_ip + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 ); let memory_param: &[&str] = if local { @@ -10266,8 +10266,8 @@ mod live_migration { let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.0", - net_id, guest.network.guest_mac, guest.network.host_ip + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 ); let memory_param: &[&str] = if local { @@ -10513,8 +10513,8 @@ mod live_migration { let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.0", - net_id, guest.network.guest_mac, guest.network.host_ip + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 ); let memory_param: &[&str] = if local { @@ -10771,7 +10771,7 @@ mod live_migration { let r = std::panic::catch_unwind(|| { // Perform same checks to validate VM has been properly migrated // Spawn a new netcat listener in the OVS VM - let guest_ip = ovs_guest.network.guest_ip.clone(); + let guest_ip = ovs_guest.network.guest_ip0.clone(); thread::spawn(move || { ssh_command_ip( "nc -l 12345", @@ -10819,8 +10819,8 @@ mod live_migration { let kernel_path = direct_kernel_boot_path(); let net_id = "net123"; let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.0", - net_id, guest.network.guest_mac, guest.network.host_ip + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 ); let boot_vcpus = 2; @@ -11027,8 +11027,8 @@ mod live_migration { let console_text = String::from("On a branch floating down river a cricket, singing."); let net_id = "net123"; let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.0", - net_id, guest.network.guest_mac, guest.network.host_ip + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 ); let memory_param: &[&str] = &["--memory", "size=4G,shared=on"]; let boot_vcpus = 2; @@ -11382,9 +11382,9 @@ mod rate_limiter { let limit_bps = (bw_size * 8 * 1000) as f64 / bw_refill_time as f64; let net_params = format!( - "tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={},bw_size={},bw_refill_time={}", - guest.network.guest_mac, - guest.network.host_ip, + "tap=,mac={},ip={},mask=255.255.255.128,num_queues={},queue_size={},bw_size={},bw_refill_time={}", + guest.network.guest_mac0, + guest.network.host_ip0, num_queues, queue_size, bw_size, From 3ed44c41a6e87baea52b65de603f835ac718f5f0 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 21 Oct 2025 16:14:57 +0100 Subject: [PATCH 0235/1893] tests: Use a secondary network interface for test_net_hotplug This gives a reliable way of identifying if the VM has booted as well as a reliable way to validate the addition of the network interface. Signed-off-by: Rob Bradford --- tests/integration.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 872800a078..263071fdaf 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -6072,6 +6072,7 @@ mod common_parallel { .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_net() .default_disks() .capture_output(); @@ -6093,8 +6094,9 @@ mod common_parallel { "add-net", Some( format!( - "{}{},id=test0", - guest.default_net_string(), + "id=test0,tap=,mac={},ip={},mask=255.255.255.128{}", + guest.network.guest_mac1, + guest.network.host_ip1, if let Some(pci_segment) = pci_segment { format!(",pci_segment={pci_segment}") } else { @@ -6113,13 +6115,13 @@ mod common_parallel { } else { assert!( String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:05.0\"}") + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") ); } thread::sleep(std::time::Duration::new(5, 0)); - // 1 network interfaces + default localhost ==> 2 interfaces + // 2 network interfaces + default localhost ==> 3 interfaces assert_eq!( guest .ssh_command("ip -o link | wc -l") @@ -6127,20 +6129,22 @@ mod common_parallel { .trim() .parse::() .unwrap_or_default(), - 2 + 3 ); // Remove network assert!(remote_command(&api_socket, "remove-device", Some("test0"),)); thread::sleep(std::time::Duration::new(5, 0)); + // Add network let (cmd_success, cmd_output) = remote_command_w_output( &api_socket, "add-net", Some( format!( - "{}{},id=test1", - guest.default_net_string(), + "id=test1,tap=,mac={},ip={},mask=255.255.255.128{}", + guest.network.guest_mac1, + guest.network.host_ip1, if let Some(pci_segment) = pci_segment { format!(",pci_segment={pci_segment}") } else { @@ -6159,13 +6163,13 @@ mod common_parallel { } else { assert!( String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test1\",\"bdf\":\"0000:00:05.0\"}") + .contains("{\"id\":\"test1\",\"bdf\":\"0000:00:06.0\"}") ); } thread::sleep(std::time::Duration::new(5, 0)); - // 1 network interfaces + default localhost ==> 2 interfaces + // 2 network interfaces + default localhost ==> 3 interfaces assert_eq!( guest .ssh_command("ip -o link | wc -l") @@ -6173,13 +6177,12 @@ mod common_parallel { .trim() .parse::() .unwrap_or_default(), - 2 + 3 ); guest.reboot_linux(0, None); - // Check still there after reboot - // 1 network interfaces + default localhost ==> 2 interfaces + // 2 network interfaces + default localhost ==> 3 interfaces assert_eq!( guest .ssh_command("ip -o link | wc -l") @@ -6187,7 +6190,7 @@ mod common_parallel { .trim() .parse::() .unwrap_or_default(), - 2 + 3 ); }); From 1b4b4fb0c2f7fcf4cfb379fb169a483738dd018d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 21 Oct 2025 18:05:56 +0100 Subject: [PATCH 0236/1893] tests: Validate secondary network connection in test_net_hotplug Test that additional network interface by SSHing in using that secondary IP. Signed-off-by: Rob Bradford --- tests/integration.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 263071fdaf..6c42e04964 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -6132,6 +6132,21 @@ mod common_parallel { 3 ); + // Test the same using the added network interface's IP + assert_eq!( + ssh_command_ip( + "ip -o link | wc -l", + &guest.network.guest_ip1, + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT + ) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + // Remove network assert!(remote_command(&api_socket, "remove-device", Some("test0"),)); thread::sleep(std::time::Duration::new(5, 0)); @@ -6192,6 +6207,21 @@ mod common_parallel { .unwrap_or_default(), 3 ); + + // Test the same using the added network interface's IP + assert_eq!( + ssh_command_ip( + "ip -o link | wc -l", + &guest.network.guest_ip1, + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT + ) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); }); kill_child(&mut child); From 99d08a2b7f79aa1af049f32e633ce84714ccdee5 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 21 Oct 2025 18:40:27 +0100 Subject: [PATCH 0237/1893] tests: Replace fixed wait for VM to boot in test_net_hotplug Use the notification mechanism now that the VM has it's primary network attached. Signed-off-by: Rob Bradford --- tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration.rs b/tests/integration.rs index 6c42e04964..1225d68a8a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -6085,7 +6085,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); - thread::sleep(std::time::Duration::new(20, 0)); + guest.wait_vm_boot(None).unwrap(); let r = std::panic::catch_unwind(|| { // Add network From 221cbeba5ab17086c2ed057dfc977709cc0f01ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:54:10 +0000 Subject: [PATCH 0238/1893] build: Bump fsfe/reuse-action from 5 to 6 Bumps [fsfe/reuse-action](https://github.com/fsfe/reuse-action) from 5 to 6. - [Release notes](https://github.com/fsfe/reuse-action/releases) - [Commits](https://github.com/fsfe/reuse-action/compare/v5...v6) --- updated-dependencies: - dependency-name: fsfe/reuse-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/reuse.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reuse.yaml b/.github/workflows/reuse.yaml index 3a463eedcd..9977759e01 100644 --- a/.github/workflows/reuse.yaml +++ b/.github/workflows/reuse.yaml @@ -9,4 +9,4 @@ jobs: steps: - uses: actions/checkout@v5 - name: REUSE Compliance Check - uses: fsfe/reuse-action@v5 + uses: fsfe/reuse-action@v6 From e6ddfe07c325e2cf616b477dd37388911ae10291 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 1 Sep 2025 18:57:48 +0200 Subject: [PATCH 0239/1893] vmm: remove noop The vCPU lifecycle is already complicated. Let's remove dead code (the impl is a no-op). Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/cpu.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index a6d1f42605..04dbd73de9 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -2438,10 +2438,9 @@ impl Pausable for CpuManager { self.signal_vcpus(); + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] for vcpu in self.vcpus.iter() { - let mut vcpu = vcpu.lock().unwrap(); - vcpu.pause()?; - #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + let vcpu = vcpu.lock().unwrap(); if !self.config.kvm_hyperv { vcpu.vcpu.notify_guest_clock_paused().map_err(|e| { MigratableError::Pause(anyhow!( @@ -2466,10 +2465,6 @@ impl Pausable for CpuManager { } fn resume(&mut self) -> std::result::Result<(), MigratableError> { - for vcpu in self.vcpus.iter() { - vcpu.lock().unwrap().resume()?; - } - // Toggle the vCPUs pause boolean self.vcpus_pause_signalled.store(false, Ordering::SeqCst); From 8481026b6091fd0c846c87b8bc4c8e246798ed64 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 1 Sep 2025 19:32:08 +0200 Subject: [PATCH 0240/1893] vmm: refactor poor naming These bindings actually refer to atomic bool shared across all vCPUs to instruct single vCPUs with their next action. As there are already enough Arc, this helps while debugging things to see that different bindings refer to the same atomic bool. In other words: This naming really confused us while debugging. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/cpu.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 04dbd73de9..2e93786062 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -1052,9 +1052,9 @@ impl CpuManager { #[cfg(feature = "guest_debug")] let vm_debug_evt = self.vm_debug_evt.try_clone().unwrap(); let panic_exit_evt = self.exit_evt.try_clone().unwrap(); - let vcpu_kill_signalled = self.vcpus_kill_signalled.clone(); - let vcpu_pause_signalled = self.vcpus_pause_signalled.clone(); - let vcpu_kick_signalled = self.vcpus_kick_signalled.clone(); + let vcpus_kill_signalled = self.vcpus_kill_signalled.clone(); + let vcpus_pause_signalled = self.vcpus_pause_signalled.clone(); + let vcpus_kick_signalled = self.vcpus_kick_signalled.clone(); let vcpu_kill = self.vcpu_states[usize::try_from(vcpu_id).unwrap()] .kill @@ -1147,7 +1147,7 @@ impl CpuManager { // loads and stores to different atomics and we need // to see them in a consistent order in all threads - if vcpu_pause_signalled.load(Ordering::SeqCst) { + if vcpus_pause_signalled.load(Ordering::SeqCst) { // As a pause can be caused by PIO & MMIO exits then we need to ensure they are // completed by returning to KVM_RUN. From the kernel docs: // @@ -1176,13 +1176,13 @@ impl CpuManager { vcpu_run_interrupted.store(true, Ordering::SeqCst); vcpu_paused.store(true, Ordering::SeqCst); - while vcpu_pause_signalled.load(Ordering::SeqCst) { + while vcpus_pause_signalled.load(Ordering::SeqCst) { thread::park(); } vcpu_run_interrupted.store(false, Ordering::SeqCst); } - if vcpu_kick_signalled.load(Ordering::SeqCst) { + if vcpus_kick_signalled.load(Ordering::SeqCst) { vcpu_run_interrupted.store(true, Ordering::SeqCst); #[cfg(target_arch = "x86_64")] match vcpu.lock().as_ref().unwrap().vcpu.nmi() { @@ -1195,7 +1195,7 @@ impl CpuManager { } // We've been told to terminate - if vcpu_kill_signalled.load(Ordering::SeqCst) + if vcpus_kill_signalled.load(Ordering::SeqCst) || vcpu_kill.load(Ordering::SeqCst) { vcpu_run_interrupted.store(true, Ordering::SeqCst); @@ -1214,7 +1214,7 @@ impl CpuManager { info!("VmExit::Debug"); #[cfg(feature = "guest_debug")] { - vcpu_pause_signalled.store(true, Ordering::SeqCst); + vcpus_pause_signalled.store(true, Ordering::SeqCst); let raw_tid = get_raw_tid(vcpu_id as usize); vm_debug_evt.write(raw_tid as u64).unwrap(); } @@ -1275,7 +1275,7 @@ impl CpuManager { } // We've been told to terminate - if vcpu_kill_signalled.load(Ordering::SeqCst) + if vcpus_kill_signalled.load(Ordering::SeqCst) || vcpu_kill.load(Ordering::SeqCst) { vcpu_run_interrupted.store(true, Ordering::SeqCst); From d39b56544a492c1d11be18955e55e5b71c551647 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 1 Sep 2025 18:55:57 +0200 Subject: [PATCH 0241/1893] vmm: fix CpuManager::resume(): gracefully wait for run vCPU loop ACK Fix a race condition that happens in resume()-pause() cycles. It is odd that for pause(), the CpuManager waited via `state.paused` for the vCPU thread to ACK the state change but not for `resume()`. In the `resume()` case, oddly CpuManager "owned" the state change in `state.paused`. This commit changes this so that the vCPU ACKs its state change itself in `state.paused` when it transitions from pause->run. Further, `CpuManager::resume()` now gracefully waits for the vCPU to be resumed. More technical: This change ensures proper synchronization and prevents situations in that park() follows right after unpark(), causing deadlocks and other weird behavior due to race conditions. Calling resume() now takes slightly longer, very similar to pause(). This is, however, even for 254 vCPUs in the range of less than 10ms, and ultimately we now have correct behaviour. ## Reproducer Since [0] is merged, the underlying problem can be tested without this commit by modifying the pause() API call to run `CpuManager::pause()` and `CpuManager::resume()` in a loop a thousand times. `ch-remote --api-socket ... pause` ```patch diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index d7bba25cc..35557d58f 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2687,6 +2687,10 @@ impl Pausable for Vm { MigratableError::Pause(anyhow!("Error activating pending virtio devices: {:?}", e)) })?; + for _ in 0..1000 { + self.cpu_manager.lock().unwrap().pause()?; + self.cpu_manager.lock().unwrap().resume()?; + } self.cpu_manager.lock().unwrap().pause()?; self.device_manager.lock().unwrap().pause()?; ``` ## Outlook Decades of experience in VMM development showed us that using many AtomicBools is a footgun. They are not synchronized with each other at all. On the long term, we might want to refactor things to have a single shared AtomicU64 with different bits having different meanings. [0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7290 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/cpu.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 2e93786062..d1eb4ddaf6 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -684,6 +684,7 @@ struct VcpuState { handle: Option>, kill: Arc, vcpu_run_interrupted: Arc, + /// Used to ACK state changes from the run vCPU loop to the CPU Manager. paused: Arc, } @@ -1179,6 +1180,7 @@ impl CpuManager { while vcpus_pause_signalled.load(Ordering::SeqCst) { thread::park(); } + vcpu_paused.store(false, Ordering::SeqCst); vcpu_run_interrupted.store(false, Ordering::SeqCst); } @@ -2454,6 +2456,7 @@ impl Pausable for CpuManager { // activated vCPU change their state to ensure they have parked. for state in self.vcpu_states.iter() { if state.active() { + // wait for vCPU to update state while !state.paused.load(Ordering::SeqCst) { // To avoid a priority inversion with the vCPU thread thread::sleep(std::time::Duration::from_millis(1)); @@ -2465,16 +2468,26 @@ impl Pausable for CpuManager { } fn resume(&mut self) -> std::result::Result<(), MigratableError> { - // Toggle the vCPUs pause boolean + // Ensure that vCPUs keep running after being unpark() in + // their run vCPU loop. self.vcpus_pause_signalled.store(false, Ordering::SeqCst); - // Unpark all the VCPU threads. - // Once unparked, the next thing they will do is checking for the pause - // boolean. Since it'll be set to false, they will exit their pause loop - // and go back to vmx root. - for state in self.vcpu_states.iter() { - state.paused.store(false, Ordering::SeqCst); - state.unpark_thread(); + // Unpark all the vCPU threads. + // Step 1/2: signal each thread + { + for state in self.vcpu_states.iter() { + state.unpark_thread(); + } + } + // Step 2/2: wait for state ACK + { + for state in self.vcpu_states.iter() { + // wait for vCPU to update state + while state.paused.load(Ordering::SeqCst) { + // To avoid a priority inversion with the vCPU thread + thread::sleep(std::time::Duration::from_millis(1)); + } + } } Ok(()) } From 7d6c450cee7df7a580e665b4c942906c3f179262 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 20 Oct 2025 12:12:04 +0100 Subject: [PATCH 0242/1893] virtio-devices: pci_common_config: Remove warning for feature pages > 2 The virtio spec defines the feature bit range and 4 x 32-bit pages. However no features are currently defined with bits > 63. However Linux has now started acking features in those higher pages. Since this is harmless (we drop the acks to those feature pages) and quietly return 0 for available features in those higher pages this warning can be removed. Fixes: #7414 Signed-off-by: Rob Bradford --- virtio-devices/src/transport/pci_common_config.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index dcf5891f12..0680b4a303 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -330,11 +330,6 @@ impl VirtioPciCommonConfig { let mut locked_device = device.lock().unwrap(); locked_device .ack_features(u64::from(value) << (self.driver_feature_select * 32)); - } else { - warn!( - "invalid ack_features (page {}, value 0x{:x})", - self.driver_feature_select, value - ); } } 0x20 => self.with_queue_mut(queues, |q| q.set_desc_table_address(Some(value), None)), From 0a25a77095622452d910ef5d17d60baa37aff63d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:16:47 +0000 Subject: [PATCH 0243/1893] build: Bump crate-ci/typos from 1.38.0 to 1.38.1 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.0 to 1.38.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.38.0...v1.38.1) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.38.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index c4e98fe7f7..57584d936b 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.38.0 + - uses: crate-ci/typos@v1.38.1 From cb5aaca8095e76ac13a2e5b3030ee141957bd47d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 23 Oct 2025 17:38:46 +0100 Subject: [PATCH 0244/1893] hypervisor, vmm: Remove inner Mutex protecting VcpuFd This was added in 7be69edf5179e948b892693f6c88cba9626a0795 to deal with changes to the KVM bindings that made run() and set_immediate_exit() take &mut self. Instead adopt a Box<> value in Vcpu allowing the removal of this internal Mutex. Signed-off-by: Rob Bradford --- arch/src/aarch64/mod.rs | 2 +- arch/src/riscv64/mod.rs | 2 +- arch/src/x86_64/interrupts.rs | 3 +- arch/src/x86_64/mod.rs | 2 +- arch/src/x86_64/regs.rs | 9 +- hypervisor/src/cpu.rs | 4 +- hypervisor/src/kvm/mod.rs | 179 ++++------------------------------ hypervisor/src/mshv/mod.rs | 6 +- hypervisor/src/vm.rs | 2 +- vmm/src/cpu.rs | 39 +++----- vmm/src/vm.rs | 2 +- 11 files changed, 50 insertions(+), 200 deletions(-) diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index f98942b83a..cfb7e1ff5f 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -66,7 +66,7 @@ pub struct EntryPoint { /// Configure the specified VCPU, and return its MPIDR. pub fn configure_vcpu( - vcpu: &Arc, + vcpu: &dyn hypervisor::Vcpu, id: u32, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, ) -> super::Result { diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index e6d70d38ad..53aa479e23 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -63,7 +63,7 @@ pub struct EntryPoint { /// Configure the specified VCPU, and return its MPIDR. pub fn configure_vcpu( - vcpu: &Arc, + vcpu: &dyn hypervisor::Vcpu, id: u32, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, ) -> super::Result<()> { diff --git a/arch/src/x86_64/interrupts.rs b/arch/src/x86_64/interrupts.rs index 1ca322ec71..70534c8346 100644 --- a/arch/src/x86_64/interrupts.rs +++ b/arch/src/x86_64/interrupts.rs @@ -6,7 +6,6 @@ // found in the LICENSE-BSD-3-Clause file. use std::result; -use std::sync::Arc; pub type Result = result::Result; @@ -24,7 +23,7 @@ pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 { /// /// # Arguments /// * `vcpu` - The VCPU object to configure. -pub fn set_lint(vcpu: &Arc) -> Result<()> { +pub fn set_lint(vcpu: &dyn hypervisor::Vcpu) -> Result<()> { let mut klapic = vcpu.get_lapic()?; let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0); diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index f1503c39ef..d720e69b6e 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -775,7 +775,7 @@ pub fn generate_common_cpuid( } pub fn configure_vcpu( - vcpu: &Arc, + vcpu: &dyn hypervisor::Vcpu, id: u32, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, cpuid: Vec, diff --git a/arch/src/x86_64/regs.rs b/arch/src/x86_64/regs.rs index 706dcd0622..1993985706 100644 --- a/arch/src/x86_64/regs.rs +++ b/arch/src/x86_64/regs.rs @@ -6,7 +6,6 @@ // 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::sync::Arc; use std::{mem, result}; use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt}; @@ -67,7 +66,7 @@ pub type Result = result::Result; /// # Arguments /// /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. -pub fn setup_fpu(vcpu: &Arc) -> Result<()> { +pub fn setup_fpu(vcpu: &dyn hypervisor::Vcpu) -> Result<()> { let fpu: FpuState = FpuState { fcw: 0x37f, mxcsr: 0x1f80, @@ -82,7 +81,7 @@ pub fn setup_fpu(vcpu: &Arc) -> Result<()> { /// # Arguments /// /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. -pub fn setup_msrs(vcpu: &Arc) -> Result<()> { +pub fn setup_msrs(vcpu: &dyn hypervisor::Vcpu) -> Result<()> { vcpu.set_msrs(&vcpu.boot_msr_entries()) .map_err(Error::SetModelSpecificRegisters)?; @@ -95,7 +94,7 @@ pub fn setup_msrs(vcpu: &Arc) -> Result<()> { /// /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. /// * `entry_point` - Description of the boot entry to set up. -pub fn setup_regs(vcpu: &Arc, entry_point: EntryPoint) -> Result<()> { +pub fn setup_regs(vcpu: &dyn hypervisor::Vcpu, entry_point: EntryPoint) -> Result<()> { let mut regs = vcpu.create_standard_regs(); match entry_point.setup_header { None => { @@ -121,7 +120,7 @@ pub fn setup_regs(vcpu: &Arc, entry_point: EntryPoint) -> /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. pub fn setup_sregs( mem: &GuestMemoryMmap, - vcpu: &Arc, + vcpu: &dyn hypervisor::Vcpu, enable_x2_apic_mode: bool, ) -> Result<()> { let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?; diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index 2093297d86..0d52d3281b 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -526,7 +526,7 @@ pub trait Vcpu: Send + Sync { /// /// Triggers the running of the current virtual CPU returning an exit reason. /// - fn run(&self) -> std::result::Result; + fn run(&mut self) -> std::result::Result; #[cfg(target_arch = "x86_64")] /// /// Translate guest virtual address to guest physical address @@ -542,7 +542,7 @@ pub trait Vcpu: Send + Sync { /// /// Set the "immediate_exit" state /// - fn set_immediate_exit(&self, _exit: bool) {} + fn set_immediate_exit(&mut self, _exit: bool) {} #[cfg(feature = "tdx")] /// /// Returns the details about TDX exit reason diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index ece84566ca..dfdf8fa197 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -19,9 +19,11 @@ use std::os::unix::io::AsRawFd; #[cfg(feature = "tdx")] use std::os::unix::io::RawFd; use std::result; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +use std::sync::Mutex; #[cfg(target_arch = "x86_64")] use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, RwLock}; use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd}; use vmm_sys_util::eventfd::EventFd; @@ -603,20 +605,20 @@ impl vm::Vm for KvmVm { &self, id: u32, vm_ops: Option>, - ) -> vm::Result> { + ) -> vm::Result> { let fd = self .fd .create_vcpu(id as u64) .map_err(|e| vm::HypervisorVmError::CreateVcpu(e.into()))?; let vcpu = KvmVcpu { - fd: Arc::new(Mutex::new(fd)), + fd, #[cfg(target_arch = "x86_64")] msrs: self.msrs.clone(), vm_ops, #[cfg(target_arch = "x86_64")] hyperv_synic: AtomicBool::new(false), }; - Ok(Arc::new(vcpu)) + Ok(Box::new(vcpu)) } #[cfg(target_arch = "aarch64")] @@ -1333,7 +1335,7 @@ impl hypervisor::Hypervisor for KvmHypervisor { /// Vcpu struct for KVM pub struct KvmVcpu { - fd: Arc>, + fd: VcpuFd, #[cfg(target_arch = "x86_64")] msrs: Vec, vm_ops: Option>, @@ -1375,8 +1377,6 @@ impl cpu::Vcpu for KvmVcpu { fn get_regs(&self) -> cpu::Result { Ok(self .fd - .lock() - .unwrap() .get_regs() .map_err(|e| cpu::HypervisorCpuError::GetStandardRegs(e.into()))? .into()) @@ -1398,8 +1398,6 @@ impl cpu::Vcpu for KvmVcpu { for i in 0..31 { let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.regs.regs[i] = u64::from_le_bytes(bytes); @@ -1411,8 +1409,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(user_pt_regs, sp); let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.regs.sp = u64::from_le_bytes(bytes); @@ -1421,8 +1417,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(user_pt_regs, pc); let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.regs.pc = u64::from_le_bytes(bytes); @@ -1431,8 +1425,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(user_pt_regs, pstate); let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.regs.pstate = u64::from_le_bytes(bytes); @@ -1441,8 +1433,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_regs, sp_el1); let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.sp_el1 = u64::from_le_bytes(bytes); @@ -1452,8 +1442,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_regs, elr_el1); let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.elr_el1 = u64::from_le_bytes(bytes); @@ -1463,8 +1451,6 @@ impl cpu::Vcpu for KvmVcpu { for i in 0..KVM_NR_SPSR as usize { let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.spsr[i] = u64::from_le_bytes(bytes); @@ -1477,8 +1463,6 @@ impl cpu::Vcpu for KvmVcpu { for i in 0..32 { let mut bytes = [0_u8; 16]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U128, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.fp_regs.vregs[i] = u128::from_le_bytes(bytes); @@ -1489,8 +1473,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_regs, fp_regs.fpsr); let mut bytes = [0_u8; 4]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U32, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.fp_regs.fpsr = u32::from_le_bytes(bytes); @@ -1499,8 +1481,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_regs, fp_regs.fpcr); let mut bytes = [0_u8; 4]; self.fd - .lock() - .unwrap() .get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U32, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetAarchCoreRegister(e.into()))?; state.fp_regs.fpcr = u32::from_le_bytes(bytes); @@ -1523,8 +1503,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_riscv_core, mode); let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(riscv64_reg_id!(KVM_REG_RISCV_CORE, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetRiscvCoreRegister(e.into()))?; state.mode = u64::from_le_bytes(bytes); @@ -1533,8 +1511,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_riscv_core, regs.$reg_name); let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(riscv64_reg_id!(KVM_REG_RISCV_CORE, off), &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetRiscvCoreRegister(e.into()))?; state.regs.$reg_name = u64::from_le_bytes(bytes); @@ -1585,8 +1561,6 @@ impl cpu::Vcpu for KvmVcpu { fn set_regs(&self, regs: &StandardRegisters) -> cpu::Result<()> { let regs = (*regs).into(); self.fd - .lock() - .unwrap() .set_regs(®s) .map_err(|e| cpu::HypervisorCpuError::SetStandardRegs(e.into())) } @@ -1604,8 +1578,6 @@ impl cpu::Vcpu for KvmVcpu { let mut off = offset_of!(user_pt_regs, regs); for i in 0..31 { self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &kvm_regs_state.regs.regs[i].to_le_bytes(), @@ -1616,8 +1588,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(user_pt_regs, sp); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &kvm_regs_state.regs.sp.to_le_bytes(), @@ -1626,8 +1596,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(user_pt_regs, pc); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &kvm_regs_state.regs.pc.to_le_bytes(), @@ -1636,8 +1604,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(user_pt_regs, pstate); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &kvm_regs_state.regs.pstate.to_le_bytes(), @@ -1646,8 +1612,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_regs, sp_el1); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &kvm_regs_state.sp_el1.to_le_bytes(), @@ -1656,8 +1620,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_regs, elr_el1); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &kvm_regs_state.elr_el1.to_le_bytes(), @@ -1667,8 +1629,6 @@ impl cpu::Vcpu for KvmVcpu { let mut off = offset_of!(kvm_regs, spsr); for i in 0..KVM_NR_SPSR as usize { self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, off), &kvm_regs_state.spsr[i].to_le_bytes(), @@ -1680,8 +1640,6 @@ impl cpu::Vcpu for KvmVcpu { let mut off = offset_of!(kvm_regs, fp_regs.vregs); for i in 0..32 { self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U128, off), &kvm_regs_state.fp_regs.vregs[i].to_le_bytes(), @@ -1692,8 +1650,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_regs, fp_regs.fpsr); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U32, off), &kvm_regs_state.fp_regs.fpsr.to_le_bytes(), @@ -1702,8 +1658,6 @@ impl cpu::Vcpu for KvmVcpu { let off = offset_of!(kvm_regs, fp_regs.fpcr); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U32, off), &kvm_regs_state.fp_regs.fpcr.to_le_bytes(), @@ -1729,8 +1683,6 @@ impl cpu::Vcpu for KvmVcpu { (mode) => { let off = offset_of!(kvm_riscv_core, mode); self.fd - .lock() - .unwrap() .set_one_reg( riscv64_reg_id!(KVM_REG_RISCV_CORE, off), &kvm_regs_state.mode.to_le_bytes(), @@ -1740,8 +1692,6 @@ impl cpu::Vcpu for KvmVcpu { ($reg_name:ident) => { let off = offset_of!(kvm_riscv_core, regs.$reg_name); self.fd - .lock() - .unwrap() .set_one_reg( riscv64_reg_id!(KVM_REG_RISCV_CORE, off), &kvm_regs_state.regs.$reg_name.to_le_bytes(), @@ -1794,8 +1744,6 @@ impl cpu::Vcpu for KvmVcpu { fn get_sregs(&self) -> cpu::Result { Ok(self .fd - .lock() - .unwrap() .get_sregs() .map_err(|e| cpu::HypervisorCpuError::GetSpecialRegs(e.into()))? .into()) @@ -1808,8 +1756,6 @@ impl cpu::Vcpu for KvmVcpu { fn set_sregs(&self, sregs: &SpecialRegisters) -> cpu::Result<()> { let sregs = (*sregs).into(); self.fd - .lock() - .unwrap() .set_sregs(&sregs) .map_err(|e| cpu::HypervisorCpuError::SetSpecialRegs(e.into())) } @@ -1821,8 +1767,6 @@ impl cpu::Vcpu for KvmVcpu { fn get_fpu(&self) -> cpu::Result { Ok(self .fd - .lock() - .unwrap() .get_fpu() .map_err(|e| cpu::HypervisorCpuError::GetFloatingPointRegs(e.into()))? .into()) @@ -1835,8 +1779,6 @@ impl cpu::Vcpu for KvmVcpu { fn set_fpu(&self, fpu: &FpuState) -> cpu::Result<()> { let fpu: kvm_bindings::kvm_fpu = (*fpu).clone().into(); self.fd - .lock() - .unwrap() .set_fpu(&fpu) .map_err(|e| cpu::HypervisorCpuError::SetFloatingPointRegs(e.into())) } @@ -1852,8 +1794,6 @@ impl cpu::Vcpu for KvmVcpu { .map_err(|_| cpu::HypervisorCpuError::SetCpuid(anyhow!("failed to create CpuId")))?; self.fd - .lock() - .unwrap() .set_cpuid2(&kvm_cpuid) .map_err(|e| cpu::HypervisorCpuError::SetCpuid(e.into())) } @@ -1872,8 +1812,6 @@ impl cpu::Vcpu for KvmVcpu { ..Default::default() }; self.fd - .lock() - .unwrap() .enable_cap(&cap) .map_err(|e| cpu::HypervisorCpuError::EnableHyperVSyncIc(e.into())) } @@ -1885,8 +1823,6 @@ impl cpu::Vcpu for KvmVcpu { fn get_cpuid2(&self, num_entries: usize) -> cpu::Result> { let kvm_cpuid = self .fd - .lock() - .unwrap() .get_cpuid2(num_entries) .map_err(|e| cpu::HypervisorCpuError::GetCpuid(e.into()))?; @@ -1902,8 +1838,6 @@ impl cpu::Vcpu for KvmVcpu { fn get_lapic(&self) -> cpu::Result { Ok(self .fd - .lock() - .unwrap() .get_lapic() .map_err(|e| cpu::HypervisorCpuError::GetlapicState(e.into()))? .into()) @@ -1916,8 +1850,6 @@ impl cpu::Vcpu for KvmVcpu { fn set_lapic(&self, klapic: &LapicState) -> cpu::Result<()> { let klapic: kvm_bindings::kvm_lapic_state = (*klapic).clone().into(); self.fd - .lock() - .unwrap() .set_lapic(&klapic) .map_err(|e| cpu::HypervisorCpuError::SetLapicState(e.into())) } @@ -1931,8 +1863,6 @@ impl cpu::Vcpu for KvmVcpu { let mut kvm_msrs = MsrEntries::from_entries(&kvm_msrs).unwrap(); let succ = self .fd - .lock() - .unwrap() .get_msrs(&mut kvm_msrs) .map_err(|e| cpu::HypervisorCpuError::GetMsrEntries(e.into()))?; @@ -1955,8 +1885,6 @@ impl cpu::Vcpu for KvmVcpu { let kvm_msrs: Vec = msrs.iter().map(|e| (*e).into()).collect(); let kvm_msrs = MsrEntries::from_entries(&kvm_msrs).unwrap(); self.fd - .lock() - .unwrap() .set_msrs(&kvm_msrs) .map_err(|e| cpu::HypervisorCpuError::SetMsrEntries(e.into())) } @@ -1967,8 +1895,6 @@ impl cpu::Vcpu for KvmVcpu { fn get_mp_state(&self) -> cpu::Result { Ok(self .fd - .lock() - .unwrap() .get_mp_state() .map_err(|e| cpu::HypervisorCpuError::GetMpState(e.into()))? .into()) @@ -1979,8 +1905,6 @@ impl cpu::Vcpu for KvmVcpu { /// fn set_mp_state(&self, mp_state: MpState) -> cpu::Result<()> { self.fd - .lock() - .unwrap() .set_mp_state(mp_state.into()) .map_err(|e| cpu::HypervisorCpuError::SetMpState(e.into())) } @@ -1992,8 +1916,6 @@ impl cpu::Vcpu for KvmVcpu { fn translate_gva(&self, gva: u64, _flags: u64) -> cpu::Result<(u64, u32)> { let tr = self .fd - .lock() - .unwrap() .translate_gva(gva) .map_err(|e| cpu::HypervisorCpuError::TranslateVirtualAddress(e.into()))?; // tr.valid is set if the GVA is mapped to valid GPA. @@ -2008,8 +1930,8 @@ impl cpu::Vcpu for KvmVcpu { /// /// Triggers the running of the current virtual CPU returning an exit reason. /// - fn run(&self) -> std::result::Result { - match self.fd.lock().unwrap().run() { + fn run(&mut self) -> std::result::Result { + match self.fd.run() { Ok(run) => match run { #[cfg(target_arch = "x86_64")] VcpuExit::IoIn(addr, data) => { @@ -2101,7 +2023,7 @@ impl cpu::Vcpu for KvmVcpu { /// potential soft lockups when being resumed. /// fn notify_guest_clock_paused(&self) -> cpu::Result<()> { - if let Err(e) = self.fd.lock().unwrap().kvmclock_ctrl() { + if let Err(e) = self.fd.kvmclock_ctrl() { // Linux kernel returns -EINVAL if the PV clock isn't yet initialised // which could be because we're still in firmware or the guest doesn't // use KVM clock. @@ -2164,8 +2086,6 @@ impl cpu::Vcpu for KvmVcpu { } } self.fd - .lock() - .unwrap() .set_guest_debug(&dbg) .map_err(|e| cpu::HypervisorCpuError::SetDebugRegs(e.into())) } @@ -2232,8 +2152,6 @@ impl cpu::Vcpu for KvmVcpu { fn vcpu_init(&self, kvi: &crate::VcpuInit) -> cpu::Result<()> { let kvm_kvi: kvm_bindings::kvm_vcpu_init = (*kvi).into(); self.fd - .lock() - .unwrap() .vcpu_init(&kvm_kvi) .map_err(|e| cpu::HypervisorCpuError::VcpuInit(e.into())) } @@ -2241,8 +2159,6 @@ impl cpu::Vcpu for KvmVcpu { #[cfg(target_arch = "aarch64")] fn vcpu_finalize(&self, feature: i32) -> cpu::Result<()> { self.fd - .lock() - .unwrap() .vcpu_finalize(&feature) .map_err(|e| cpu::HypervisorCpuError::VcpuFinalize(e.into())) } @@ -2255,8 +2171,6 @@ impl cpu::Vcpu for KvmVcpu { fn get_reg_list(&self, reg_list: &mut RegList) -> cpu::Result<()> { let mut kvm_reg_list: kvm_bindings::RegList = reg_list.clone().into(); self.fd - .lock() - .unwrap() .get_reg_list(&mut kvm_reg_list) .map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::GetRegList(e.into()))?; *reg_list = kvm_reg_list.into(); @@ -2291,8 +2205,6 @@ impl cpu::Vcpu for KvmVcpu { | KVM_REG_ARM64_SYSREG_OP2_MASK)) as u64); let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(id, &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetSysRegister(e.into()))?; Ok(u64::from_le_bytes(bytes)) @@ -2314,8 +2226,6 @@ impl cpu::Vcpu for KvmVcpu { // Get the register index of the PSTATE (Processor State) register. let pstate = offset_of!(kvm_regs, regs.pstate); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, pstate), ®s::PSTATE_FAULT_BITS_64.to_le_bytes(), @@ -2327,8 +2237,6 @@ impl cpu::Vcpu for KvmVcpu { // Setting the PC (Processor Counter) to the current program address (kernel address). let pc = offset_of!(kvm_regs, regs.pc); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, pc), &boot_ip.to_le_bytes(), @@ -2341,8 +2249,6 @@ impl cpu::Vcpu for KvmVcpu { // We are choosing to place it the end of DRAM. See `get_fdt_addr`. let regs0 = offset_of!(kvm_regs, regs.regs); self.fd - .lock() - .unwrap() .set_one_reg( arm64_core_reg_id!(KVM_REG_SIZE_U64, regs0), &fdt_start.to_le_bytes(), @@ -2360,8 +2266,6 @@ impl cpu::Vcpu for KvmVcpu { // Setting the A0 () to the hartid of this CPU. let a0 = offset_of!(kvm_riscv_core, regs.a0); self.fd - .lock() - .unwrap() .set_one_reg( riscv64_reg_id!(KVM_REG_RISCV_CORE, a0), &u64::from(cpu_id).to_le_bytes(), @@ -2371,8 +2275,6 @@ impl cpu::Vcpu for KvmVcpu { // Setting the PC (Processor Counter) to the current program address (kernel address). let pc = offset_of!(kvm_riscv_core, regs.pc); self.fd - .lock() - .unwrap() .set_one_reg( riscv64_reg_id!(KVM_REG_RISCV_CORE, pc), &boot_ip.to_le_bytes(), @@ -2386,8 +2288,6 @@ impl cpu::Vcpu for KvmVcpu { // not exceed 64 kilobytes in size." let a1 = offset_of!(kvm_riscv_core, regs.a1); self.fd - .lock() - .unwrap() .set_one_reg( riscv64_reg_id!(KVM_REG_RISCV_CORE, a1), &fdt_start.to_le_bytes(), @@ -2542,8 +2442,6 @@ impl cpu::Vcpu for KvmVcpu { let mut sys_regs: Vec = Vec::new(); let mut reg_list = kvm_bindings::RegList::new(500).unwrap(); self.fd - .lock() - .unwrap() .get_reg_list(&mut reg_list) .map_err(|e| cpu::HypervisorCpuError::GetRegList(e.into()))?; @@ -2562,8 +2460,6 @@ impl cpu::Vcpu for KvmVcpu { for index in indices.iter() { let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(*index, &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetSysRegister(e.into()))?; sys_regs.push(kvm_bindings::kvm_one_reg { @@ -2595,8 +2491,6 @@ impl cpu::Vcpu for KvmVcpu { let mut sys_regs: Vec = Vec::new(); let mut reg_list = kvm_bindings::RegList::new(200).unwrap(); self.fd - .lock() - .unwrap() .get_reg_list(&mut reg_list) .map_err(|e| cpu::HypervisorCpuError::GetRegList(e.into()))?; @@ -2622,8 +2516,6 @@ impl cpu::Vcpu for KvmVcpu { for index in indices.iter() { let mut bytes = [0_u8; 8]; self.fd - .lock() - .unwrap() .get_one_reg(*index, &mut bytes) .map_err(|e| cpu::HypervisorCpuError::GetSysRegister(e.into()))?; sys_regs.push(kvm_bindings::kvm_one_reg { @@ -2742,8 +2634,6 @@ impl cpu::Vcpu for KvmVcpu { // Set system registers for reg in &state.sys_regs { self.fd - .lock() - .unwrap() .set_one_reg(reg.id, ®.addr.to_le_bytes()) .map_err(|e| cpu::HypervisorCpuError::SetSysRegister(e.into()))?; } @@ -2764,8 +2654,6 @@ impl cpu::Vcpu for KvmVcpu { // Set system registers for reg in &state.non_core_regs { self.fd - .lock() - .unwrap() .set_one_reg(reg.id, ®.addr.to_le_bytes()) .map_err(|e| cpu::HypervisorCpuError::SetSysRegister(e.into()))?; } @@ -2780,20 +2668,15 @@ impl cpu::Vcpu for KvmVcpu { /// #[cfg(feature = "tdx")] fn tdx_init(&self, hob_address: u64) -> cpu::Result<()> { - tdx_command( - &self.fd.lock().unwrap().as_raw_fd(), - TdxCommand::InitVcpu, - 0, - hob_address, - ) - .map_err(cpu::HypervisorCpuError::InitializeTdx) + tdx_command(&self.fd.as_raw_fd(), TdxCommand::InitVcpu, 0, hob_address) + .map_err(cpu::HypervisorCpuError::InitializeTdx) } /// /// Set the "immediate_exit" state /// - fn set_immediate_exit(&self, exit: bool) { - self.fd.lock().unwrap().set_kvm_immediate_exit(exit.into()); + fn set_immediate_exit(&mut self, exit: bool) { + self.fd.set_kvm_immediate_exit(exit.into()); } /// @@ -2801,8 +2684,7 @@ impl cpu::Vcpu for KvmVcpu { /// #[cfg(feature = "tdx")] fn get_tdx_exit_details(&mut self) -> cpu::Result { - let mut fd = self.fd.as_ref().lock().unwrap(); - let kvm_run = fd.get_kvm_run(); + let kvm_run = self.fd.get_kvm_run(); // SAFETY: accessing a union field in a valid structure let tdx_vmcall = unsafe { &mut (*((&mut kvm_run.__bindgen_anon_1) as *mut kvm_run__bindgen_ty_1 @@ -2831,8 +2713,7 @@ impl cpu::Vcpu for KvmVcpu { /// #[cfg(feature = "tdx")] fn set_tdx_status(&mut self, status: TdxExitStatus) { - let mut fd = self.fd.as_ref().lock().unwrap(); - let kvm_run = fd.get_kvm_run(); + let kvm_run = self.fd.get_kvm_run(); // SAFETY: accessing a union field in a valid structure let tdx_vmcall = unsafe { &mut (*((&mut kvm_run.__bindgen_anon_1) as *mut kvm_run__bindgen_ty_1 @@ -2881,7 +2762,7 @@ impl cpu::Vcpu for KvmVcpu { addr: 0x0, flags: 0, }; - self.fd.lock().unwrap().has_device_attr(&cpu_attr).is_ok() + self.fd.has_device_attr(&cpu_attr).is_ok() } #[cfg(target_arch = "aarch64")] @@ -2899,13 +2780,9 @@ impl cpu::Vcpu for KvmVcpu { flags: 0, }; self.fd - .lock() - .unwrap() .set_device_attr(&cpu_attr_irq) .map_err(|_| cpu::HypervisorCpuError::InitializePmu)?; self.fd - .lock() - .unwrap() .set_device_attr(&cpu_attr) .map_err(|_| cpu::HypervisorCpuError::InitializePmu) } @@ -2915,7 +2792,7 @@ impl cpu::Vcpu for KvmVcpu { /// Get the frequency of the TSC if available /// fn tsc_khz(&self) -> cpu::Result> { - match self.fd.lock().unwrap().get_tsc_khz() { + match self.fd.get_tsc_khz() { Err(e) => { if e.errno() == libc::EIO { Ok(None) @@ -2932,7 +2809,7 @@ impl cpu::Vcpu for KvmVcpu { /// Set the frequency of the TSC if available /// fn set_tsc_khz(&self, freq: u32) -> cpu::Result<()> { - match self.fd.lock().unwrap().set_tsc_khz(freq) { + match self.fd.set_tsc_khz(freq) { Err(e) => { if e.errno() == libc::EIO { Ok(()) @@ -2949,7 +2826,7 @@ impl cpu::Vcpu for KvmVcpu { /// Trigger NMI interrupt /// fn nmi(&self) -> cpu::Result<()> { - match self.fd.lock().unwrap().nmi() { + match self.fd.nmi() { Err(e) => { if e.errno() == libc::EIO { Ok(()) @@ -2970,8 +2847,6 @@ impl KvmVcpu { fn get_xsave(&self) -> cpu::Result { Ok(self .fd - .lock() - .unwrap() .get_xsave() .map_err(|e| cpu::HypervisorCpuError::GetXsaveState(e.into()))? .into()) @@ -2987,8 +2862,6 @@ impl KvmVcpu { // when calling the kvm-ioctl library function. unsafe { self.fd - .lock() - .unwrap() .set_xsave(&xsave) .map_err(|e| cpu::HypervisorCpuError::SetXsaveState(e.into())) } @@ -3000,8 +2873,6 @@ impl KvmVcpu { /// fn get_xcrs(&self) -> cpu::Result { self.fd - .lock() - .unwrap() .get_xcrs() .map_err(|e| cpu::HypervisorCpuError::GetXcsr(e.into())) } @@ -3012,8 +2883,6 @@ impl KvmVcpu { /// fn set_xcrs(&self, xcrs: &ExtendedControlRegisters) -> cpu::Result<()> { self.fd - .lock() - .unwrap() .set_xcrs(xcrs) .map_err(|e| cpu::HypervisorCpuError::SetXcsr(e.into())) } @@ -3025,8 +2894,6 @@ impl KvmVcpu { /// fn get_vcpu_events(&self) -> cpu::Result { self.fd - .lock() - .unwrap() .get_vcpu_events() .map_err(|e| cpu::HypervisorCpuError::GetVcpuEvents(e.into())) } @@ -3038,8 +2905,6 @@ impl KvmVcpu { /// fn set_vcpu_events(&self, events: &VcpuEvents) -> cpu::Result<()> { self.fd - .lock() - .unwrap() .set_vcpu_events(events) .map_err(|e| cpu::HypervisorCpuError::SetVcpuEvents(e.into())) } @@ -3052,8 +2917,6 @@ impl KvmVcpu { let maybe_size = self .fd - .lock() - .unwrap() .get_nested_state(&mut buffer) .map_err(|e| cpu::HypervisorCpuError::GetNestedState(e.into()))?; @@ -3068,8 +2931,6 @@ impl KvmVcpu { #[cfg(target_arch = "x86_64")] fn set_nested_state(&self, state: &KvmNestedStateBuffer) -> cpu::Result<()> { self.fd - .lock() - .unwrap() .set_nested_state(state) .map_err(|e| cpu::HypervisorCpuError::GetNestedState(e.into())) } diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 385a444623..950dd55f12 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -604,7 +604,7 @@ impl cpu::Vcpu for MshvVcpu { } #[allow(non_upper_case_globals)] - fn run(&self) -> std::result::Result { + fn run(&mut self) -> std::result::Result { match self.fd.run() { Ok(x) => match x.header.message_type { hv_message_type_HVMSG_X64_HALT => { @@ -1821,7 +1821,7 @@ impl vm::Vm for MshvVm { &self, id: u32, vm_ops: Option>, - ) -> vm::Result> { + ) -> vm::Result> { let id: u8 = id.try_into().unwrap(); let vcpu_fd = self .fd @@ -1869,7 +1869,7 @@ impl vm::Vm for MshvVm { #[cfg(feature = "sev_snp")] host_access_pages: ArcSwap::new(self.host_access_pages.load().clone()), }; - Ok(Arc::new(vcpu)) + Ok(Box::new(vcpu)) } #[cfg(target_arch = "x86_64")] diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index a2f7921314..7a0d1b6af6 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -316,7 +316,7 @@ pub trait Vm: Send + Sync + Any { /// Unregister an event that will, when signaled, trigger the `gsi` IRQ. fn unregister_irqfd(&self, fd: &EventFd, gsi: u32) -> Result<()>; /// Creates a new KVM vCPU file descriptor and maps the memory corresponding - fn create_vcpu(&self, id: u32, vm_ops: Option>) -> Result>; + fn create_vcpu(&self, id: u32, vm_ops: Option>) -> Result>; #[cfg(target_arch = "aarch64")] fn create_vgic(&self, config: VgicConfig) -> Result>>; #[cfg(target_arch = "riscv64")] diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index d1eb4ddaf6..626ce2d83e 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -392,7 +392,7 @@ macro_rules! round_up { /// A wrapper around creating and using a kvm-based VCPU. pub struct Vcpu { // The hypervisor abstracted CPU. - vcpu: Arc, + vcpu: Box, id: u32, #[cfg(target_arch = "aarch64")] mpidr: u64, @@ -450,15 +450,16 @@ impl Vcpu { #[cfg(target_arch = "aarch64")] { self.init(vm)?; - self.mpidr = arch::configure_vcpu(&self.vcpu, self.id, boot_setup) + self.mpidr = arch::configure_vcpu(self.vcpu.as_ref(), self.id, boot_setup) .map_err(Error::VcpuConfiguration)?; } #[cfg(target_arch = "riscv64")] - arch::configure_vcpu(&self.vcpu, self.id, boot_setup).map_err(Error::VcpuConfiguration)?; + arch::configure_vcpu(self.vcpu.as_ref(), self.id, boot_setup) + .map_err(Error::VcpuConfiguration)?; info!("Configuring vCPU: cpu_id = {}", self.id); #[cfg(target_arch = "x86_64")] arch::configure_vcpu( - &self.vcpu, + self.vcpu.as_ref(), self.id, boot_setup, cpuid, @@ -515,7 +516,7 @@ impl Vcpu { /// /// Note that the state of the VCPU and associated VM must be setup first for this to do /// anything useful. - pub fn run(&self) -> std::result::Result { + pub fn run(&mut self) -> std::result::Result { self.vcpu.run() } @@ -1166,12 +1167,12 @@ impl CpuManager { #[cfg(feature = "kvm")] if matches!(hypervisor_type, HypervisorType::Kvm) { - vcpu.lock().as_ref().unwrap().vcpu.set_immediate_exit(true); + vcpu.lock().unwrap().vcpu.set_immediate_exit(true); if !matches!(vcpu.lock().unwrap().run(), Ok(VmExit::Ignore)) { error!("Unexpected VM exit on \"immediate_exit\" run"); break; } - vcpu.lock().as_ref().unwrap().vcpu.set_immediate_exit(false); + vcpu.lock().unwrap().vcpu.set_immediate_exit(false); } vcpu_run_interrupted.store(true, Ordering::SeqCst); @@ -1204,10 +1205,7 @@ impl CpuManager { break; } - #[cfg(feature = "tdx")] let mut vcpu = vcpu.lock().unwrap(); - #[cfg(not(feature = "tdx"))] - let vcpu = vcpu.lock().unwrap(); // vcpu.run() returns false on a triple-fault so trigger a reset match vcpu.run() { Ok(run) => match run { @@ -1248,8 +1246,7 @@ impl CpuManager { } #[cfg(feature = "tdx")] VmExit::Tdx => { - if let Some(vcpu) = Arc::get_mut(&mut vcpu.vcpu) { - match vcpu.get_tdx_exit_details() { + match vcpu.vcpu.get_tdx_exit_details() { Ok(details) => match details { TdxExitDetails::GetQuote => warn!("TDG_VP_VMCALL_GET_QUOTE not supported"), TdxExitDetails::SetupEventNotifyInterrupt => { @@ -1258,13 +1255,7 @@ impl CpuManager { }, Err(e) => error!("Unexpected TDX VMCALL: {}", e), } - vcpu.set_tdx_status(TdxExitStatus::InvalidOperand); - } else { - // We should never reach this code as - // this means the design from the code - // is wrong. - unreachable!("Couldn't get a mutable reference from Arc as there are multiple instances"); - } + vcpu.vcpu.set_tdx_status(TdxExitStatus::InvalidOperand); } }, @@ -3011,7 +3002,7 @@ mod tests { let lint0_mode_expected = set_apic_delivery_mode(lint0, APIC_MODE_EXTINT); let lint1_mode_expected = set_apic_delivery_mode(lint1, APIC_MODE_NMI); - set_lint(&vcpu).unwrap(); + set_lint(vcpu.as_ref()).unwrap(); // Compute the value that represents LVT0 and LVT1 after set_lint. let klapic_actual: LapicState = vcpu.get_lapic().unwrap(); @@ -3028,7 +3019,7 @@ mod tests { .create_vm(HypervisorVmConfig::default()) .expect("new VM fd creation failed"); let vcpu = vm.create_vcpu(0, None).unwrap(); - setup_fpu(&vcpu).unwrap(); + setup_fpu(vcpu.as_ref()).unwrap(); let expected_fpu: FpuState = FpuState { fcw: 0x37f, @@ -3054,7 +3045,7 @@ mod tests { .create_vm(HypervisorVmConfig::default()) .expect("new VM fd creation failed"); let vcpu = vm.create_vcpu(0, None).unwrap(); - setup_msrs(&vcpu).unwrap(); + setup_msrs(vcpu.as_ref()).unwrap(); // This test will check against the last MSR entry configured (the tenth one). // See create_msr_entries for details. @@ -3089,7 +3080,7 @@ mod tests { expected_regs.set_rip(1); setup_regs( - &vcpu, + vcpu.as_ref(), arch::EntryPoint { entry_addr: vm_memory::GuestAddress(expected_regs.get_rip()), setup_header: None, @@ -3116,7 +3107,7 @@ mod tests { expected_regs.set_rsi(ZERO_PAGE_START.0); setup_regs( - &vcpu, + vcpu.as_ref(), arch::EntryPoint { entry_addr: vm_memory::GuestAddress(expected_regs.get_rip()), setup_header: Some(setup_header { diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index aaf58945e6..9ea83cb052 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -3548,7 +3548,7 @@ pub fn test_vm() { mem.write_slice(&code, load_addr) .expect("Writing code to memory failed"); - let vcpu = vm.create_vcpu(0, None).expect("new Vcpu failed"); + let mut vcpu = vm.create_vcpu(0, None).expect("new Vcpu failed"); let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed"); vcpu_sregs.cs.base = 0; From 3e339439a496d72618d4db199726f93c7f616e18 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Thu, 14 Aug 2025 09:44:59 +0000 Subject: [PATCH 0245/1893] arch: move GICv2M SPI base to leave room for legacy IRQs Move the GICv2M MSI frame SPI base from 32 to 128 to avoid conflicts with platform device interrupts. While at it, rename the constants to make it explicit that they are associated with the GICv2m MSI Frame. Signed-off-by: Anirudh Rayabharam --- arch/src/aarch64/fdt.rs | 9 +++++---- arch/src/aarch64/layout.rs | 8 ++++---- vmm/src/cpu.rs | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index 2755012440..90d6877316 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -25,8 +25,9 @@ use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig}; use super::layout::{ - GIC_V2M_COMPATIBLE, IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, - MEM_PCI_IO_START, PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT, SPI_BASE, SPI_NUM, + 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, + PCI_MMIO_CONFIG_SIZE_PER_SEGMENT, }; use crate::{NumaNodes, PciSpaceInfo}; @@ -666,8 +667,8 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc>) -> Fd fdt.property_array_u64("reg", &msi_reg_prop)?; if msi_compatibility == GIC_V2M_COMPATIBLE { - fdt.property_u32("arm,msi-base-spi", SPI_BASE)?; - fdt.property_u32("arm,msi-num-spis", SPI_NUM)?; + fdt.property_u32("arm,msi-base-spi", GICV2M_SPI_BASE)?; + fdt.property_u32("arm,msi-num-spis", GICV2M_SPI_NUM)?; } fdt.end_node(msic_node)?; diff --git a/arch/src/aarch64/layout.rs b/arch/src/aarch64/layout.rs index dc2c74e398..66a12958a8 100644 --- a/arch/src/aarch64/layout.rs +++ b/arch/src/aarch64/layout.rs @@ -139,11 +139,11 @@ pub const IRQ_BASE: u32 = 32; /// Number of supported interrupts pub const IRQ_NUM: u32 = 256; -/// Base SPI interrupt number -pub const SPI_BASE: u32 = 32; +/// Base SPI interrupt number for the GICv2M MSI frame +pub const GICV2M_SPI_BASE: u32 = 128; -/// Total number of SPIs -pub const SPI_NUM: u32 = 64; +/// Total number of SPIs for the GICv2M MSI frame +pub const GICV2M_SPI_NUM: u32 = 64; /// GICv2M compatible string pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame"; diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 626ce2d83e..4f8f0dcc96 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -1569,7 +1569,7 @@ impl CpuManager { #[cfg(target_arch = "aarch64")] { - use arch::layout::{GIC_V2M_COMPATIBLE, SPI_BASE, SPI_NUM}; + use arch::layout::{GIC_V2M_COMPATIBLE, GICV2M_SPI_BASE, GICV2M_SPI_NUM}; /* Notes: * Ignore Local Interrupt Controller Address at byte offset 36 of MADT table. @@ -1645,8 +1645,8 @@ impl CpuManager { msi_frame_id: 0, base_address: vgic_config.msi_addr, flags: 1, - spi_count: SPI_NUM as u16, - spi_base: SPI_BASE as u16, + spi_count: GICV2M_SPI_NUM as u16, + spi_base: GICV2M_SPI_BASE as u16, }; madt.append(gic_msi_frame); } else { From fe32002141cb5a56aad80b3b34f8c947ffeb6686 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 23:15:40 +0000 Subject: [PATCH 0246/1893] build: Bump actions/upload-artifact from 4 to 5 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4876fd5d98..0ab1ea813f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -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@v4 + uses: actions/upload-artifact@v5 with: name: Artifacts for ${{ matrix.platform.target }} path: | @@ -80,7 +80,7 @@ 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@v4 + uses: actions/upload-artifact@v5 with: path: cloud-hypervisor-${{ github.event.ref }}.tar.xz name: cloud-hypervisor-${{ github.event.ref }}.tar.xz From 861b7ab64df682631da2f09517f8c515dc313d7c Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Fri, 24 Oct 2025 10:43:28 +0000 Subject: [PATCH 0247/1893] tests: exclude test_fw_cfg for mshv test_fw_cfg is frequently failing in the CI for MSHV. Exclude it for now. It needs further investigation. See issue #7434 for details. Signed-off-by: Anirudh Rayabharam --- scripts/run_integration_tests_x86_64.sh | 2 +- tests/integration.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 7580789ed7..2f628c9bbd 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -202,7 +202,7 @@ fi if [ $RES -eq 0 ]; then cargo build --features "mshv,fw_cfg" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo test "fw_cfg::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + time cargo test $test_features "fw_cfg::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} RES=$? fi diff --git a/tests/integration.rs b/tests/integration.rs index 1225d68a8a..04e47f6f1b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -11687,6 +11687,7 @@ mod fw_cfg { use crate::*; #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7434")] fn test_fw_cfg() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); From 5d9f75ca6db86296ca77a79163753628a9090fa9 Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Fri, 24 Oct 2025 21:57:01 +0530 Subject: [PATCH 0248/1893] ci: remove skipped tests from integration workflow Move the list of skipped tests from integration test workflow for mshv. Signed-off-by: AASTHA RAWAT --- .github/workflows/mshv-integration.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index b7e8323ca0..b14cc5603b 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -76,7 +76,7 @@ jobs: fi done - sudo ./scripts/dev_cli.sh tests --hypervisor mshv --integration -- -- --skip common_parallel::test_tpm --skip common_parallel::test_cpu_topology_421 --skip common_parallel::test_cpu_topology_142 --skip common_parallel::test_cpu_topology_262 --skip common_sequential::test_snapshot_restore_basic --skip common_sequential::test_snapshot_restore_with_fd --skip common_sequential::test_snapshot_restore_pvpanic --skip virtio_net_latency_us --skip common_parallel::test_cpu_hotplug + sudo ./scripts/dev_cli.sh tests --hypervisor mshv --integration EOF cleanup: From 03349e7523410e3a331e7782b10683386d2624b5 Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Fri, 24 Oct 2025 22:01:31 +0530 Subject: [PATCH 0249/1893] tests: disable skipped integration tests for mshv Disable the following common parallel and common sequential tests for mshv since these are failing consistently in the CI. Common parallel: - test_tpm - test_cpu_topology_421 - test_cpu_topology_142 - test_cpu_topology_262 - test_cpu_hotplug Common sequential: - test_snapshot_restore_basic - test_snapshot_restore_with_fd - test_snapshot_restore_pvpanic Signed-off-by: AASTHA RAWAT --- tests/integration.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 04e47f6f1b..061a853544 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2642,16 +2642,19 @@ mod common_parallel { } #[test] + #[cfg(not(feature = "mshv"))] // See issue #7433 fn test_cpu_topology_421() { test_cpu_topology(4, 2, 1, false); } #[test] + #[cfg(not(feature = "mshv"))] // See issue #7433 fn test_cpu_topology_142() { test_cpu_topology(1, 4, 2, false); } #[test] + #[cfg(not(feature = "mshv"))] // See issue #7433 fn test_cpu_topology_262() { test_cpu_topology(2, 6, 2, false); } @@ -4955,6 +4958,7 @@ mod common_parallel { } #[test] + #[cfg(not(feature = "mshv"))] // See issue #7435 #[cfg(target_arch = "x86_64")] fn test_cpu_hotplug() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); @@ -7250,6 +7254,7 @@ mod common_parallel { } #[test] + #[cfg(not(feature = "mshv"))] // See issue #7439 #[cfg(target_arch = "x86_64")] fn test_tpm() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); @@ -7983,6 +7988,7 @@ mod common_sequential { } #[test] + #[cfg(not(feature = "mshv"))] // See issue #7437 fn test_snapshot_restore_basic() { _test_snapshot_restore(false); } @@ -8240,6 +8246,7 @@ mod common_sequential { } #[test] + #[cfg(not(feature = "mshv"))] // See issue #7437 #[cfg_attr(target_arch = "aarch64", ignore = "See #6970")] fn test_snapshot_restore_with_fd() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); @@ -8463,6 +8470,7 @@ mod common_sequential { } #[test] + #[cfg(not(feature = "mshv"))] // See issue #7437 #[cfg(target_arch = "x86_64")] fn test_snapshot_restore_pvpanic() { _test_snapshot_restore_devices(true); From f033c5837b1c0836fd9861a2cb90e8c0249e492d Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Mon, 27 Oct 2025 14:09:54 +0530 Subject: [PATCH 0250/1893] ci: prevent cross-PR CI cancellation for mshv workflows The existing concurrency configuration causes workflow runs for one Pull Request to cancel in-progress runs for another Pull Request. When the workflow is triggered by the `pull_request_target event`, `github.ref` is always set to the base branch, regardless of which branch raised the PR. This causes every new pull request to share the same concurrency key and thus cancel the running CI of any other open pull request. This commit updates the `concurrency.group` to use `github.event.pull_request.number` for unique isolation when running on PR, falling back to `github.ref` for non-PR push (e.g., merge group). Signed-off-by: AASTHA RAWAT --- .github/workflows/mshv-infra.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index 887a3ca4e9..72a90548a7 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -38,7 +38,7 @@ on: description: 'Private IP of the VM' value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }} concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: infra-setup: From f74cde788271bac5c60329801879e905135c1287 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Tue, 28 Oct 2025 11:06:26 +0000 Subject: [PATCH 0251/1893] scripts: build mshv feature for ivshmem testing The ivshmem tests are all failing in the CI for MSHV because Cloud Hypervisor is built without the mshv feature. Error: Cloud Hypervisor exited with the following chain of errors: 0: Failed to open hypervisor interface (is hypervisor interface available?) 1: Failed to create the hypervisor 2: no supported hypervisor Modify the build command to include the mshv feature. Signed-off-by: Anirudh Rayabharam --- scripts/run_integration_tests_x86_64.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 2f628c9bbd..1d9c56be78 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -207,7 +207,7 @@ if [ $RES -eq 0 ]; then fi if [ $RES -eq 0 ]; then - cargo build --features ivshmem --all --release --target "$BUILD_TARGET" + cargo build --features "mshv,ivshmem" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 time cargo test $test_features "ivshmem::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} RES=$? From 6198fa7a79c8d13c9eb7c249f7dcaccdd8406563 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Tue, 28 Oct 2025 11:09:04 +0000 Subject: [PATCH 0252/1893] tests: disable broken ivshmem tests for MSHV The ivshmem tests involving snapshot/restore & live migration are failing in the MSHV CI with this error: Could not get vCPU state GetMsrEntries( Hypercall 80 failed with 0x5 : InvalidParameter ...) This needs more investigation. It is worth noting that the general live migration tests are also not run in the CI for MSHV. Disable these tests for MSHV for the time being. Signed-off-by: Anirudh Rayabharam --- tests/integration.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 061a853544..cd41589bec 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -7511,6 +7511,7 @@ mod dbus_api { } mod ivshmem { + #[cfg(not(feature = "mshv"))] use std::fs::remove_dir_all; use std::process::Command; @@ -7759,6 +7760,7 @@ mod ivshmem { } #[test] + #[cfg(not(feature = "mshv"))] fn test_snapshot_restore_ivshmem() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); @@ -7912,11 +7914,13 @@ mod ivshmem { } #[test] + #[cfg(not(feature = "mshv"))] fn test_live_migration_ivshmem() { _test_live_migration_ivshmem(false) } #[test] + #[cfg(not(feature = "mshv"))] fn test_live_migration_ivshmem_local() { _test_live_migration_ivshmem(true) } From e295719967ee1e56da472605d839803ac9af4e9e Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Tue, 28 Oct 2025 12:08:26 +0000 Subject: [PATCH 0253/1893] tests: disable test_snapshot_restore_hotplug_virtiomem for mshv Disable test_snapshot_restore_hotplug_virtiomem for mshv. It is failing frequently in the CI. It needs to be stabilized before enabling again. Signed-off-by: Anirudh Rayabharam --- tests/integration.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration.rs b/tests/integration.rs index cd41589bec..15919641b6 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -7987,6 +7987,7 @@ mod common_sequential { // through each ssh command. There's no need to perform a dedicated test to // verify the migration went well for virtio-net. #[test] + #[cfg(not(feature = "mshv"))] fn test_snapshot_restore_hotplug_virtiomem() { _test_snapshot_restore(true); } From 7536a95424b328a263347cd3a67b421de6e6cef4 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 28 Oct 2025 10:49:34 +0100 Subject: [PATCH 0254/1893] misc: cleanup `&Arc` -> `&dyn T` Consuming `&Arc` as argument is almost always an antipattern as it hides whether the callee is going to take over (shared) ownership (by .clone()) or not. Instead, it is better to consume `&dyn T` or `Arc` to be more explicit. This commit cleans up the code. The change is very mechanic and was very easy to implement across the code base. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- arch/src/aarch64/mod.rs | 2 +- arch/src/riscv64/mod.rs | 2 +- arch/src/x86_64/mod.rs | 5 ++-- block/src/lib.rs | 3 +- hypervisor/src/cpu.rs | 5 +--- hypervisor/src/kvm/mod.rs | 2 +- hypervisor/src/mshv/mod.rs | 2 +- net_util/src/ctrl_queue.rs | 4 +-- net_util/src/queue_pair.rs | 8 +++--- pci/src/vfio.rs | 4 +-- pci/src/vfio_user.rs | 4 +-- virtio-devices/src/balloon.rs | 2 +- virtio-devices/src/block.rs | 4 +-- virtio-devices/src/console.rs | 6 ++-- virtio-devices/src/device.rs | 4 +-- virtio-devices/src/iommu.rs | 2 +- virtio-devices/src/mem.rs | 2 +- virtio-devices/src/net.rs | 8 ++++-- virtio-devices/src/pmem.rs | 6 ++-- virtio-devices/src/rng.rs | 4 +-- virtio-devices/src/transport/pci_device.rs | 6 ++-- virtio-devices/src/vdpa.rs | 10 +++---- virtio-devices/src/vhost_user/blk.rs | 2 +- virtio-devices/src/vhost_user/fs.rs | 2 +- virtio-devices/src/vhost_user/mod.rs | 4 +-- virtio-devices/src/vhost_user/net.rs | 2 +- .../src/vhost_user/vu_common_ctrl.rs | 4 +-- virtio-devices/src/vsock/device.rs | 6 ++-- virtio-devices/src/vsock/packet.rs | 5 ++-- virtio-devices/src/watchdog.rs | 2 +- vm-device/src/bus.rs | 5 ++-- vm-virtio/src/lib.rs | 13 ++++----- vmm/src/cpu.rs | 18 ++++++------ vmm/src/device_manager.rs | 28 +++++++++---------- vmm/src/interrupt.rs | 12 ++++---- vmm/src/lib.rs | 21 ++++++++------ vmm/src/vm.rs | 19 +++++++------ 37 files changed, 121 insertions(+), 117 deletions(-) diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index cfb7e1ff5f..95c4cb3fd7 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -180,7 +180,7 @@ pub fn initramfs_load_addr( } } -pub fn get_host_cpu_phys_bits(hypervisor: &Arc) -> u8 { +pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 { let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap(); if host_cpu_phys_bits == 0 { // Host kernel does not support `get_host_ipa_limit`, diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index 53aa479e23..b4478aaa6d 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -156,7 +156,7 @@ pub fn initramfs_load_addr( } } -pub fn get_host_cpu_phys_bits(_hypervisor: &Arc) -> u8 { +pub fn get_host_cpu_phys_bits(_hypervisor: &dyn hypervisor::Hypervisor) -> u8 { 40 } diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index d720e69b6e..5d17065c81 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -6,7 +6,6 @@ // 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::sync::Arc; pub mod interrupts; pub mod layout; mod mpspec; @@ -547,7 +546,7 @@ impl CpuidFeatureEntry { } pub fn generate_common_cpuid( - hypervisor: &Arc, + hypervisor: &dyn hypervisor::Hypervisor, config: &CpuidConfig, ) -> super::Result> { // SAFETY: cpuid called with valid leaves @@ -1258,7 +1257,7 @@ pub fn initramfs_load_addr( Ok(aligned_addr) } -pub fn get_host_cpu_phys_bits(hypervisor: &Arc) -> u8 { +pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 { // SAFETY: call cpuid with valid leaves unsafe { let leaf = x86_64::__cpuid(0x8000_0000); diff --git a/block/src/lib.rs b/block/src/lib.rs index 4cfed22839..f7388e5055 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -39,7 +39,6 @@ use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; use std::os::linux::fs::MetadataExt; use std::os::unix::io::AsRawFd; use std::path::Path; -use std::sync::Arc; use std::time::Instant; use std::{cmp, result}; @@ -260,7 +259,7 @@ pub struct Request { impl Request { pub fn parse( desc_chain: &mut DescriptorChain>>, - access_platform: Option<&Arc>, + access_platform: Option<&dyn AccessPlatform>, ) -> result::Result { let hdr_desc = desc_chain .next() diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index 0d52d3281b..763eaa4558 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -10,9 +10,6 @@ // // -#[cfg(target_arch = "aarch64")] -use std::sync::Arc; - use thiserror::Error; #[cfg(not(target_arch = "riscv64"))] use vm_memory::GuestAddress; @@ -473,7 +470,7 @@ pub trait Vcpu: Send + Sync { #[cfg(target_arch = "aarch64")] fn vcpu_set_processor_features( &self, - vm: &Arc, + vm: &dyn crate::Vm, kvi: &mut VcpuInit, id: u32, ) -> Result<()>; diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index dfdf8fa197..5ce110d89a 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -2098,7 +2098,7 @@ impl cpu::Vcpu for KvmVcpu { #[cfg(target_arch = "aarch64")] fn vcpu_set_processor_features( &self, - vm: &Arc, + vm: &dyn crate::Vm, kvi: &mut crate::VcpuInit, id: u32, ) -> cpu::Result<()> { diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 950dd55f12..46a6103b52 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -1306,7 +1306,7 @@ impl cpu::Vcpu for MshvVcpu { #[cfg(target_arch = "aarch64")] fn vcpu_set_processor_features( &self, - _vm: &Arc, + _vm: &dyn crate::Vm, _kvi: &mut crate::VcpuInit, _id: u32, ) -> cpu::Result<()> { diff --git a/net_util/src/ctrl_queue.rs b/net_util/src/ctrl_queue.rs index 3fd242f668..6b9cfb23bf 100644 --- a/net_util/src/ctrl_queue.rs +++ b/net_util/src/ctrl_queue.rs @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use std::sync::Arc; - use thiserror::Error; use virtio_bindings::virtio_net::{ VIRTIO_NET_CTRL_GUEST_OFFLOADS, VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, VIRTIO_NET_CTRL_MQ, @@ -67,7 +65,7 @@ impl CtrlQueue { &mut self, mem: &GuestMemoryMmap, queue: &mut Queue, - access_platform: Option<&Arc>, + access_platform: Option<&dyn AccessPlatform>, ) -> Result<()> { while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) { let ctrl_desc = desc_chain.next().ok_or(Error::NoControlHeaderDescriptor)?; diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index c3c145646a..0ad328ff9f 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -45,7 +45,7 @@ impl TxVirtio { tap: &Tap, queue: &mut Queue, rate_limiter: &mut Option, - access_platform: Option<&Arc>, + access_platform: Option<&dyn AccessPlatform>, ) -> Result { let mut retry_write = false; let mut rate_limit_reached = false; @@ -173,7 +173,7 @@ impl RxVirtio { tap: &Tap, queue: &mut Queue, rate_limiter: &mut Option, - access_platform: Option<&Arc>, + access_platform: Option<&dyn AccessPlatform>, ) -> Result { let mut exhausted_descs = true; let mut rate_limit_reached = false; @@ -413,7 +413,7 @@ impl NetQueuePair { &self.tap, queue, &mut self.tx_rate_limiter, - self.access_platform.as_ref(), + self.access_platform.as_deref(), )?; // We got told to try again when writing to the tap. Wait for the TAP to be writable @@ -463,7 +463,7 @@ impl NetQueuePair { &self.tap, queue, &mut self.rx_rate_limiter, - self.access_platform.as_ref(), + self.access_platform.as_deref(), )?; let rate_limit_reached = self .rx_rate_limiter diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index abe80073b4..13a528eba3 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -1449,7 +1449,7 @@ impl VfioPciDevice { #[allow(clippy::too_many_arguments)] pub fn new( id: String, - vm: &Arc, + vm: Arc, device: VfioDevice, container: Arc, msi_interrupt_manager: Arc>, @@ -1478,7 +1478,7 @@ impl VfioPciDevice { let vfio_pci_device = VfioPciDevice { id, - vm: vm.clone(), + vm, device, container, common, diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index 79dfb659ac..be0c775efc 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -69,7 +69,7 @@ impl VfioUserPciDevice { #[allow(clippy::too_many_arguments)] pub fn new( id: String, - vm: &Arc, + vm: Arc, client: Arc>, msi_interrupt_manager: Arc>, legacy_interrupt_group: Option>, @@ -103,7 +103,7 @@ impl VfioUserPciDevice { Ok(Self { id, - vm: vm.clone(), + vm, client, common, memory_slot_allocator, diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index b721410e33..d28d1d117e 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -591,7 +591,7 @@ impl VirtioDevice for Balloon { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); let mut virtqueues = Vec::new(); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 89228c3826..ff8adf3b4e 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -171,7 +171,7 @@ impl BlockEpollHandler { let mut batch_inflight_requests = Vec::new(); while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) { - let mut request = Request::parse(&mut desc_chain, self.access_platform.as_ref()) + let mut request = Request::parse(&mut desc_chain, self.access_platform.as_deref()) .map_err(Error::RequestParsing)?; // For virtio spec compliance @@ -905,7 +905,7 @@ impl VirtioDevice for Block { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; self.update_writeback(); diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index 37b10b91b8..5756c92737 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -217,7 +217,7 @@ impl ConsoleEpollHandler { .write_slice( &source_slice[..], desc.addr() - .translate_gva(self.access_platform.as_ref(), desc.len() as usize), + .translate_gva(self.access_platform.as_deref(), desc.len() as usize), ) .map_err(Error::GuestMemoryWrite)?; @@ -253,7 +253,7 @@ impl ConsoleEpollHandler { .memory() .write_volatile_to( desc.addr() - .translate_gva(self.access_platform.as_ref(), desc.len() as usize), + .translate_gva(self.access_platform.as_deref(), desc.len() as usize), &mut buf, desc.len() as usize, ) @@ -707,7 +707,7 @@ impl VirtioDevice for Console { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; self.resizer .acked_features .store(self.common.acked_features, Ordering::Relaxed); diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 1a6a79b99f..a30ad60649 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -221,7 +221,7 @@ impl VirtioCommon { pub fn activate( &mut self, queues: &[(usize, Queue, EventFd)], - interrupt_cb: &Arc, + interrupt_cb: Arc, ) -> ActivateResult { if queues.len() < self.min_queues.into() { error!( @@ -246,7 +246,7 @@ impl VirtioCommon { // Save the interrupt EventFD as we need to return it on reset // but clone it to pass into the thread. - self.interrupt_cb = Some(interrupt_cb.clone()); + self.interrupt_cb = Some(interrupt_cb); Ok(()) } diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index ca6b1a1be0..f2acbcec97 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -1079,7 +1079,7 @@ impl VirtioDevice for Iommu { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); let (_, request_queue, request_queue_evt) = queues.remove(0); diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 6205c822ac..6620a5edf4 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -912,7 +912,7 @@ impl VirtioDevice for Mem { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); let (_, queue, queue_evt) = queues.remove(0); diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 598bce71d9..fd6a4a3b07 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -100,7 +100,11 @@ impl EpollHelperHandler for NetCtrlEpollHandler { )) })?; self.ctrl_q - .process(mem.deref(), &mut self.queue, self.access_platform.as_ref()) + .process( + mem.deref(), + &mut self.queue, + self.access_platform.as_deref(), + ) .map_err(|e| { EpollHelperError::HandleEvent(anyhow!( "Failed to process control queue: {e:?}" @@ -693,7 +697,7 @@ impl VirtioDevice for Net { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; let num_queues = queues.len(); let event_idx = self.common.feature_acked(VIRTIO_RING_F_EVENT_IDX.into()); diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index ff17fa6901..2916b6efc0 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -105,7 +105,7 @@ struct Request { impl Request { fn parse( desc_chain: &mut DescriptorChain>, - access_platform: Option<&Arc>, + access_platform: Option<&dyn AccessPlatform>, ) -> result::Result { let desc = desc_chain.next().ok_or(Error::DescriptorChainTooShort)?; // The descriptor contains the request type which MUST be readable. @@ -165,7 +165,7 @@ impl PmemEpollHandler { fn process_queue(&mut self) -> result::Result { let mut used_descs = false; while let Some(mut desc_chain) = self.queue.pop_descriptor_chain(self.mem.memory()) { - let len = match Request::parse(&mut desc_chain, self.access_platform.as_ref()) { + let len = match Request::parse(&mut desc_chain, self.access_platform.as_deref()) { Ok(ref req) if (req.type_ == RequestType::Flush) => { let status_code = match self.disk.sync_all() { Ok(()) => VIRTIO_PMEM_RESP_TYPE_OK, @@ -387,7 +387,7 @@ impl VirtioDevice for Pmem { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); if let Some(disk) = self.disk.as_ref() { let disk = disk.try_clone().map_err(|e| { diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 3ca58778bb..f87c523a34 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -76,7 +76,7 @@ impl RngEpollHandler { .memory() .read_volatile_from( desc.addr() - .translate_gva(self.access_platform.as_ref(), desc.len() as usize), + .translate_gva(self.access_platform.as_deref(), desc.len() as usize), &mut self.random_file, desc.len() as usize, ) @@ -248,7 +248,7 @@ impl VirtioDevice for Rng { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); if let Some(file) = self.random_file.as_ref() { diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 68adae664c..4f34ffea6f 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -382,7 +382,7 @@ impl VirtioPciDevice { device: Arc>, msix_num: u16, access_platform: Option>, - interrupt_manager: &Arc>, + interrupt_manager: &dyn InterruptManager, pci_device_bdf: u32, activate_evt: EventFd, use_64bit_bar: bool, @@ -815,8 +815,8 @@ impl VirtioPciDevice { !self.device_activated.load(Ordering::SeqCst) && self.is_driver_ready() } - pub fn dma_handler(&self) -> Option<&Arc> { - self.dma_handler.as_ref() + pub fn dma_handler(&self) -> Option<&dyn ExternalDmaMapping> { + self.dma_handler.as_deref() } } diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 46ef48781d..d4739e4999 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -216,7 +216,7 @@ impl Vdpa { fn activate_vdpa( &mut self, mem: &GuestMemoryMmap, - virtio_interrupt: &Arc, + virtio_interrupt: &dyn VirtioInterrupt, queues: Vec<(usize, Queue, EventFd)>, ) -> Result<()> { assert!(self.vhost.is_some()); @@ -245,15 +245,15 @@ impl Vdpa { queue_size, flags: 0u32, desc_table_addr: queue.desc_table().translate_gpa( - self.common.access_platform.as_ref(), + self.common.access_platform.as_deref(), queue_size as usize * std::mem::size_of::(), ), used_ring_addr: queue.used_ring().translate_gpa( - self.common.access_platform.as_ref(), + self.common.access_platform.as_deref(), 4 + queue_size as usize * 8, ), avail_ring_addr: queue.avail_ring().translate_gpa( - self.common.access_platform.as_ref(), + self.common.access_platform.as_deref(), 4 + queue_size as usize * 2, ), log_addr: None, @@ -421,7 +421,7 @@ impl VirtioDevice for Vdpa { virtio_interrupt: Arc, queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.activate_vdpa(&mem.memory(), &virtio_interrupt, queues) + self.activate_vdpa(&mem.memory(), virtio_interrupt.as_ref(), queues) .map_err(ActivateError::ActivateVdpa)?; // Store the virtio interrupt handler as we need to return it on reset diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 20309080c9..5dab8442af 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -284,7 +284,7 @@ impl VirtioDevice for Blk { interrupt_cb: Arc, queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); let backend_req_handler: Option> = None; diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index bf724316cb..ab07aea365 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -265,7 +265,7 @@ impl VirtioDevice for Fs { interrupt_cb: Arc, queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); let backend_req_handler: Option> = None; diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index f9dcb363f3..1db305421b 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -228,7 +228,7 @@ impl VhostUserEpollHandler { .iter() .map(|(i, q, e)| (*i, vm_virtio::clone_queue(q), e.try_clone().unwrap())) .collect(), - &self.virtio_interrupt, + self.virtio_interrupt.as_ref(), self.acked_features, self.acked_protocol_features, &self.backend_req_handler, @@ -332,7 +332,7 @@ impl VhostUserCommon { .iter() .map(|(i, q, e)| (*i, vm_virtio::clone_queue(q), e.try_clone().unwrap())) .collect(), - &interrupt_cb, + interrupt_cb.as_ref(), acked_features, &backend_req_handler, inflight.as_mut(), diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 4f9d366c10..2a5d05d7e4 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -295,7 +295,7 @@ impl VirtioDevice for Net { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); let num_queues = queues.len(); diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index eba0292175..f1255c7f7a 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -156,7 +156,7 @@ impl VhostUserHandle { &mut self, mem: &GuestMemoryMmap, queues: Vec<(usize, Queue, EventFd)>, - virtio_interrupt: &Arc, + virtio_interrupt: &dyn VirtioInterrupt, acked_features: u64, backend_req_handler: &Option>, inflight: Option<&mut Inflight>, @@ -340,7 +340,7 @@ impl VhostUserHandle { &mut self, mem: &GuestMemoryMmap, queues: Vec<(usize, Queue, EventFd)>, - virtio_interrupt: &Arc, + virtio_interrupt: &dyn VirtioInterrupt, acked_features: u64, acked_protocol_features: u64, backend_req_handler: &Option>, diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 385b5a2425..657e80879e 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -124,7 +124,7 @@ where while let Some(mut desc_chain) = self.queues[0].pop_descriptor_chain(self.mem.memory()) { let used_len = match VsockPacket::from_rx_virtq_head( &mut desc_chain, - self.access_platform.as_ref(), + self.access_platform.as_deref(), ) { Ok(mut pkt) => { if self.backend.write().unwrap().recv_pkt(&mut pkt).is_ok() { @@ -166,7 +166,7 @@ where while let Some(mut desc_chain) = self.queues[1].pop_descriptor_chain(self.mem.memory()) { let pkt = match VsockPacket::from_tx_virtq_head( &mut desc_chain, - self.access_platform.as_ref(), + self.access_platform.as_deref(), ) { Ok(pkt) => pkt, Err(e) => { @@ -430,7 +430,7 @@ where interrupt_cb: Arc, queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); let mut virtqueues = Vec::new(); diff --git a/virtio-devices/src/vsock/packet.rs b/virtio-devices/src/vsock/packet.rs index a6749381df..4606c93b11 100644 --- a/virtio-devices/src/vsock/packet.rs +++ b/virtio-devices/src/vsock/packet.rs @@ -17,7 +17,6 @@ //! to temporary buffers, before passing it on to the vsock backend. use std::ops::Deref; -use std::sync::Arc; use byteorder::{ByteOrder, LittleEndian}; use virtio_queue::DescriptorChain; @@ -111,7 +110,7 @@ impl VsockPacket { /// pub fn from_tx_virtq_head( desc_chain: &mut DescriptorChain, - access_platform: Option<&Arc>, + access_platform: Option<&dyn AccessPlatform>, ) -> Result where M: Clone + Deref, @@ -203,7 +202,7 @@ impl VsockPacket { /// pub fn from_rx_virtq_head( desc_chain: &mut DescriptorChain, - access_platform: Option<&Arc>, + access_platform: Option<&dyn AccessPlatform>, ) -> Result where M: Clone + Deref, diff --git a/virtio-devices/src/watchdog.rs b/virtio-devices/src/watchdog.rs index cb4b5845c5..25978f252d 100644 --- a/virtio-devices/src/watchdog.rs +++ b/virtio-devices/src/watchdog.rs @@ -330,7 +330,7 @@ impl VirtioDevice for Watchdog { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.common.activate(&queues, &interrupt_cb)?; + self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); let reset_evt = self.reset_evt.try_clone().map_err(|e| { diff --git a/vm-device/src/bus.rs b/vm-device/src/bus.rs index 70bbc049a4..fdfb82c9f1 100644 --- a/vm-device/src/bus.rs +++ b/vm-device/src/bus.rs @@ -192,12 +192,13 @@ impl Bus { } /// Removes all entries referencing the given device. - pub fn remove_by_device(&self, device: &Arc) -> Result<()> { + pub fn remove_by_device(&self, device: &dyn BusDeviceSync) -> Result<()> { let mut device_list = self.devices.write().unwrap(); let mut remove_key_list = Vec::new(); for (key, value) in device_list.iter() { - if Arc::ptr_eq(&value.upgrade().unwrap(), device) { + let value = value.upgrade().unwrap(); + if core::ptr::eq(Arc::as_ptr(&value), device) { remove_key_list.push(*key); } } diff --git a/vm-virtio/src/lib.rs b/vm-virtio/src/lib.rs index 87dcd0a8d0..b7f5370702 100644 --- a/vm-virtio/src/lib.rs +++ b/vm-virtio/src/lib.rs @@ -11,7 +11,6 @@ //! Implements virtio queues use std::fmt::{self, Debug}; -use std::sync::Arc; use virtio_queue::{Queue, QueueT}; use vm_memory::GuestAddress; @@ -102,28 +101,28 @@ pub trait AccessPlatform: Send + Sync + Debug { } pub trait Translatable { - fn translate_gva(&self, access_platform: Option<&Arc>, len: usize) -> Self; - fn translate_gpa(&self, access_platform: Option<&Arc>, len: usize) -> Self; + fn translate_gva(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self; + fn translate_gpa(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self; } impl Translatable for GuestAddress { - fn translate_gva(&self, access_platform: Option<&Arc>, len: usize) -> Self { + fn translate_gva(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self { GuestAddress(self.0.translate_gva(access_platform, len)) } - fn translate_gpa(&self, access_platform: Option<&Arc>, len: usize) -> Self { + fn translate_gpa(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self { GuestAddress(self.0.translate_gpa(access_platform, len)) } } impl Translatable for u64 { - fn translate_gva(&self, access_platform: Option<&Arc>, len: usize) -> Self { + fn translate_gva(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self { if let Some(access_platform) = access_platform { access_platform.translate_gva(*self, len as u64).unwrap() } else { *self } } - fn translate_gpa(&self, access_platform: Option<&Arc>, len: usize) -> Self { + fn translate_gpa(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self { if let Some(access_platform) = access_platform { access_platform.translate_gpa(*self, len as u64).unwrap() } else { diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 4f8f0dcc96..01e12e807f 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -413,7 +413,7 @@ impl Vcpu { pub fn new( id: u32, apic_id: u32, - vm: &Arc, + vm: &dyn hypervisor::Vm, vm_ops: Option>, #[cfg(target_arch = "x86_64")] cpu_vendor: CpuVendor, ) -> Result { @@ -441,7 +441,7 @@ impl Vcpu { /// * `cpuid` - (x86_64) CpuId, wrapper over the `kvm_cpuid2` structure. pub fn configure( &mut self, - #[cfg(target_arch = "aarch64")] vm: &Arc, + #[cfg(target_arch = "aarch64")] vm: &dyn hypervisor::Vm, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, #[cfg(target_arch = "x86_64")] cpuid: Vec, #[cfg(target_arch = "x86_64")] kvm_hyperv: bool, @@ -486,7 +486,7 @@ impl Vcpu { /// Initializes an aarch64 specific vcpu for booting Linux. #[cfg(target_arch = "aarch64")] - pub fn init(&self, vm: &Arc) -> Result<()> { + pub fn init(&self, vm: &dyn hypervisor::Vm) -> Result<()> { use std::arch::is_aarch64_feature_detected; #[allow(clippy::nonminimal_bool)] let sve_supported = @@ -748,7 +748,7 @@ impl CpuManager { exit_evt: EventFd, reset_evt: EventFd, #[cfg(feature = "guest_debug")] vm_debug_evt: EventFd, - hypervisor: &Arc, + hypervisor: Arc, seccomp_action: SeccompAction, vm_ops: Arc, #[cfg(feature = "tdx")] tdx_enabled: bool, @@ -854,7 +854,7 @@ impl CpuManager { proximity_domain_per_cpu, affinity, dynamic, - hypervisor: hypervisor.clone(), + hypervisor, #[cfg(feature = "sev_snp")] sev_snp_enabled, }))) @@ -863,7 +863,7 @@ impl CpuManager { #[cfg(target_arch = "x86_64")] pub fn populate_cpuid( &mut self, - hypervisor: &Arc, + hypervisor: &dyn hypervisor::Hypervisor, #[cfg(feature = "tdx")] tdx: bool, ) -> Result<()> { self.cpuid = { @@ -897,7 +897,7 @@ impl CpuManager { let mut vcpu = Vcpu::new( cpu_id, x2apic_id, - &self.vm, + self.vm.as_ref(), Some(self.vm_ops.clone()), #[cfg(target_arch = "x86_64")] self.hypervisor.get_cpu_vendor(), @@ -906,7 +906,7 @@ impl CpuManager { if let Some(snapshot) = snapshot { // AArch64 vCPUs should be initialized after created. #[cfg(target_arch = "aarch64")] - vcpu.init(&self.vm)?; + vcpu.init(self.vm.as_ref())?; let state: CpuState = snapshot.to_state().map_err(|e| { Error::VcpuCreate(anyhow!("Could not get vCPU state from snapshot {e:?}")) @@ -977,7 +977,7 @@ impl CpuManager { )?; #[cfg(target_arch = "aarch64")] - vcpu.configure(&self.vm, boot_setup)?; + vcpu.configure(self.vm.as_ref(), boot_setup)?; #[cfg(target_arch = "riscv64")] vcpu.configure(boot_setup)?; diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 4b04ee8ebe..e52c5900ba 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1433,11 +1433,11 @@ impl DeviceManager { )?; #[cfg(target_arch = "aarch64")] - self.add_legacy_devices(&legacy_interrupt_manager)?; + self.add_legacy_devices(legacy_interrupt_manager.as_ref())?; { self.ged_notification_device = self.add_acpi_devices( - &legacy_interrupt_manager, + legacy_interrupt_manager.as_ref(), self.reset_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, @@ -1450,7 +1450,7 @@ impl DeviceManager { self.original_termios_opt = original_termios_opt; self.console = self.add_console_devices( - &legacy_interrupt_manager, + legacy_interrupt_manager.as_ref(), &mut virtio_devices, console_info, console_resize_pipe, @@ -1813,7 +1813,7 @@ impl DeviceManager { fn add_acpi_devices( &mut self, - interrupt_manager: &Arc>, + interrupt_manager: &dyn InterruptManager, reset_evt: EventFd, exit_evt: EventFd, ) -> DeviceManagerResult>>> { @@ -1999,7 +1999,7 @@ impl DeviceManager { #[cfg(target_arch = "aarch64")] fn add_legacy_devices( &mut self, - interrupt_manager: &Arc>, + interrupt_manager: &dyn InterruptManager, ) -> DeviceManagerResult<()> { // Add a RTC device let rtc_irq = self @@ -2133,7 +2133,7 @@ impl DeviceManager { #[cfg(target_arch = "x86_64")] fn add_serial_device( &mut self, - interrupt_manager: &Arc>, + interrupt_manager: &dyn InterruptManager, serial_writer: Option>, ) -> DeviceManagerResult>> { // Serial is tied to IRQ #4 @@ -2184,7 +2184,7 @@ impl DeviceManager { #[cfg(target_arch = "aarch64")] fn add_serial_device( &mut self, - interrupt_manager: &Arc>, + interrupt_manager: &dyn InterruptManager, serial_writer: Option>, ) -> DeviceManagerResult>> { let id = String::from(SERIAL_DEVICE_NAME); @@ -2248,7 +2248,7 @@ impl DeviceManager { #[cfg(target_arch = "riscv64")] fn add_serial_device( &mut self, - interrupt_manager: &Arc>, + interrupt_manager: &dyn InterruptManager, serial_writer: Option>, ) -> DeviceManagerResult>> { let id = String::from(SERIAL_DEVICE_NAME); @@ -2398,7 +2398,7 @@ impl DeviceManager { /// - virtio-console fn add_console_devices( &mut self, - interrupt_manager: &Arc>, + interrupt_manager: &dyn InterruptManager, virtio_devices: &mut Vec, console_info: Option, console_resize_pipe: Option>, @@ -3790,7 +3790,7 @@ impl DeviceManager { let vfio_pci_device = VfioPciDevice::new( vfio_name.clone(), - &self.address_manager.vm, + self.address_manager.vm.clone(), vfio_device, vfio_container, self.msi_interrupt_manager.clone(), @@ -3956,7 +3956,7 @@ impl DeviceManager { let mut vfio_user_pci_device = VfioUserPciDevice::new( vfio_user_name.clone(), - &self.address_manager.vm, + self.address_manager.vm.clone(), client.clone(), self.msi_interrupt_manager.clone(), legacy_interrupt_group, @@ -4134,7 +4134,7 @@ impl DeviceManager { virtio_device, msix_num, access_platform, - &self.msi_interrupt_manager, + self.msi_interrupt_manager.as_ref(), pci_device_bdf.into(), self.activate_evt .try_clone() @@ -4706,12 +4706,12 @@ impl DeviceManager { #[cfg(target_arch = "x86_64")] // Remove the device from the IO bus self.io_bus() - .remove_by_device(&bus_device) + .remove_by_device(bus_device.as_ref()) .map_err(DeviceManagerError::RemoveDeviceFromIoBus)?; // Remove the device from the MMIO bus self.mmio_bus() - .remove_by_device(&bus_device) + .remove_by_device(bus_device.as_ref()) .map_err(DeviceManagerError::RemoveDeviceFromMmioBus)?; // Remove the device from the list of BusDevice held by the diff --git a/vmm/src/interrupt.rs b/vmm/src/interrupt.rs index 8bafd800af..8c4c94b0e0 100644 --- a/vmm/src/interrupt.rs +++ b/vmm/src/interrupt.rs @@ -40,7 +40,7 @@ impl InterruptRoute { }) } - pub fn enable(&self, vm: &Arc) -> Result<()> { + pub fn enable(&self, vm: &dyn hypervisor::Vm) -> Result<()> { if !self.registered.load(Ordering::Acquire) { vm.register_irqfd(&self.irq_fd, self.gsi) .map_err(|e| io::Error::other(format!("Failed registering irq_fd: {e}")))?; @@ -52,7 +52,7 @@ impl InterruptRoute { Ok(()) } - pub fn disable(&self, vm: &Arc) -> Result<()> { + pub fn disable(&self, vm: &dyn hypervisor::Vm) -> Result<()> { if self.registered.load(Ordering::Acquire) { vm.unregister_irqfd(&self.irq_fd, self.gsi) .map_err(|e| io::Error::other(format!("Failed unregistering irq_fd: {e}")))?; @@ -122,7 +122,7 @@ impl MsiInterruptGroup { impl InterruptSourceGroup for MsiInterruptGroup { fn enable(&self) -> Result<()> { for (_, route) in self.irq_routes.iter() { - route.enable(&self.vm)?; + route.enable(self.vm.as_ref())?; } Ok(()) @@ -130,7 +130,7 @@ impl InterruptSourceGroup for MsiInterruptGroup { fn disable(&self) -> Result<()> { for (_, route) in self.irq_routes.iter() { - route.disable(&self.vm)?; + route.disable(self.vm.as_ref())?; } Ok(()) @@ -172,7 +172,7 @@ impl InterruptSourceGroup for MsiInterruptGroup { // So it's required to call disable() (which deassign KVM_IRQFD) before // set_gsi_routes() to avoid kernel panic (see #3827) if masked { - route.disable(&self.vm)?; + route.disable(self.vm.as_ref())?; } let mut routes = self.gsi_msi_routes.lock().unwrap(); @@ -185,7 +185,7 @@ impl InterruptSourceGroup for MsiInterruptGroup { // panic on kernel which not have commit a80ced6ea514 // (KVM: SVM: fix panic on out-of-bounds guest IRQ). if !masked { - route.enable(&self.vm)?; + route.enable(self.vm.as_ref())?; } return Ok(()); diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index bac3b3e134..9137ba1805 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -873,7 +873,7 @@ impl Vmm { } let vm = Vm::create_hypervisor_vm( - &self.hypervisor, + self.hypervisor.as_ref(), #[cfg(feature = "tdx")] false, #[cfg(feature = "sev_snp")] @@ -892,8 +892,10 @@ impl Vmm { vm.enable_x2apic_api().unwrap(); } - let phys_bits = - vm::physical_bits(&self.hypervisor, config.lock().unwrap().cpus.max_phys_bits); + let phys_bits = vm::physical_bits( + self.hypervisor.as_ref(), + config.lock().unwrap().cpus.max_phys_bits, + ); let memory_manager = MemoryManager::new( vm, @@ -1129,10 +1131,12 @@ impl Vmm { }; let amx = vm_config.lock().unwrap().cpus.features.amx; - let phys_bits = - vm::physical_bits(&hypervisor, vm_config.lock().unwrap().cpus.max_phys_bits); + let phys_bits = vm::physical_bits( + hypervisor.as_ref(), + vm_config.lock().unwrap().cpus.max_phys_bits, + ); arch::generate_common_cpuid( - &hypervisor, + hypervisor.as_ref(), &arch::CpuidConfig { phys_bits, kvm_hyperv: vm_config.lock().unwrap().cpus.kvm_hyperv, @@ -1268,9 +1272,10 @@ impl Vmm { let dest_cpuid = &{ let vm_config = &src_vm_config.lock().unwrap(); - let phys_bits = vm::physical_bits(&self.hypervisor, vm_config.cpus.max_phys_bits); + let phys_bits = + vm::physical_bits(self.hypervisor.as_ref(), vm_config.cpus.max_phys_bits); arch::generate_common_cpuid( - &self.hypervisor.clone(), + self.hypervisor.as_ref(), &arch::CpuidConfig { phys_bits, kvm_hyperv: vm_config.cpus.kvm_hyperv, diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 9ea83cb052..2271c65fd9 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -493,7 +493,7 @@ impl VmOps for VmOpsHandler { } } -pub fn physical_bits(hypervisor: &Arc, max_phys_bits: u8) -> u8 { +pub fn physical_bits(hypervisor: &dyn hypervisor::Hypervisor, max_phys_bits: u8) -> u8 { let host_phys_bits = get_host_cpu_phys_bits(hypervisor); cmp::min(host_phys_bits, max_phys_bits) @@ -592,7 +592,7 @@ impl Vm { reset_evt.try_clone().map_err(Error::EventFdClone)?, #[cfg(feature = "guest_debug")] vm_debug_evt, - &hypervisor, + hypervisor.clone(), seccomp_action.clone(), vm_ops, #[cfg(feature = "tdx")] @@ -608,7 +608,7 @@ impl Vm { .lock() .unwrap() .populate_cpuid( - &hypervisor, + hypervisor.as_ref(), #[cfg(feature = "tdx")] tdx_enabled, ) @@ -1015,7 +1015,7 @@ impl Vm { }; let vm = Self::create_hypervisor_vm( - &hypervisor, + hypervisor.as_ref(), #[cfg(feature = "tdx")] tdx_enabled, #[cfg(feature = "sev_snp")] @@ -1029,7 +1029,10 @@ impl Vm { vm.enable_x2apic_api().unwrap(); } - let phys_bits = physical_bits(&hypervisor, vm_config.lock().unwrap().cpus.max_phys_bits); + let phys_bits = physical_bits( + hypervisor.as_ref(), + vm_config.lock().unwrap().cpus.max_phys_bits, + ); let memory_manager = if let Some(snapshot) = snapshot_from_id(snapshot.as_ref(), MEMORY_MANAGER_SNAPSHOT_ID) @@ -1078,7 +1081,7 @@ impl Vm { } pub fn create_hypervisor_vm( - hypervisor: &Arc, + hypervisor: &dyn hypervisor::Hypervisor, #[cfg(feature = "tdx")] tdx_enabled: bool, #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, #[cfg(feature = "sev_snp")] mem_size: u64, @@ -2876,11 +2879,11 @@ impl Snapshottable for Vm { let common_cpuid = { let amx = self.config.lock().unwrap().cpus.features.amx; let phys_bits = physical_bits( - &self.hypervisor, + self.hypervisor.as_ref(), self.config.lock().unwrap().cpus.max_phys_bits, ); arch::generate_common_cpuid( - &self.hypervisor, + self.hypervisor.as_ref(), &arch::CpuidConfig { phys_bits, kvm_hyperv: self.config.lock().unwrap().cpus.kvm_hyperv, From 1356b26c0f08e9624e5d4c9b3be377d2547793bd Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 29 Oct 2025 20:15:33 +0100 Subject: [PATCH 0255/1893] build: bump bitfield-struct from 0.11.0 to 0.12.1 Should fix beta clippy. Signed-off-by: Alyssa Ross --- Cargo.lock | 8 ++++---- devices/Cargo.toml | 2 +- hypervisor/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1b2507b4c..0057c99c46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,9 +294,9 @@ dependencies = [ [[package]] name = "bitfield-struct" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8" +checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" dependencies = [ "proc-macro2", "quote", @@ -539,7 +539,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitfield-struct 0.11.0", + "bitfield-struct 0.12.1", "bitflags 2.9.4", "byteorder", "event_monitor", @@ -927,7 +927,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arc-swap", - "bitfield-struct 0.11.0", + "bitfield-struct 0.12.1", "byteorder", "cfg-if", "concat-idents", diff --git a/devices/Cargo.toml b/devices/Cargo.toml index a15030870b..201d5284fc 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -8,7 +8,7 @@ version = "0.1.0" acpi_tables = { workspace = true } anyhow = { workspace = true } arch = { path = "../arch" } -bitfield-struct = { version = "0.11.0", optional = true } +bitfield-struct = { version = "0.12.0", optional = true } bitflags = { workspace = true } byteorder = { workspace = true } event_monitor = { path = "../event_monitor" } diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index ec65d85ccf..93343c931f 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -15,7 +15,7 @@ tdx = [] [dependencies] anyhow = { workspace = true } arc-swap = "1.7.1" -bitfield-struct = "0.11.0" +bitfield-struct = "0.12.0" byteorder = { workspace = true } cfg-if = { workspace = true } concat-idents = "1.1.5" From 0395b10b29a579adf4f4d2ac238242b1def43386 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 29 Oct 2025 20:04:03 +0100 Subject: [PATCH 0256/1893] openapi: add missing NetConfig offload parameters Signed-off-by: Alyssa Ross --- vmm/src/api/openapi/cloud-hypervisor.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index e4a76f6b74..d87573c558 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -966,6 +966,15 @@ components: format: int16 rate_limiter_config: $ref: "#/components/schemas/RateLimiterConfig" + offload_tso: + type: boolean + default: true + offload_ufo: + type: boolean + default: true + offload_csum: + type: boolean + default: true RngConfig: required: From 2a39b4a4e5071c28c3e784e659433fa3cb470ab0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 23:19:23 +0000 Subject: [PATCH 0257/1893] build: Bump crate-ci/typos from 1.38.1 to 1.39.0 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.1 to 1.39.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.38.1...v1.39.0) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.39.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 57584d936b..f8d5ca41fa 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.38.1 + - uses: crate-ci/typos@v1.39.0 From 9158c36dbf21365a5a2155d316810096a1064e41 Mon Sep 17 00:00:00 2001 From: Ariel Chenet Date: Sat, 1 Nov 2025 17:06:01 +0100 Subject: [PATCH 0258/1893] docs: Update documentation to reflect initial VMM output The API documentation tells users to expect a message when Cloud Hypervisor is launched, this message was removed in commit 13724db This change updates the documentation to reflect how the program actually functions, which is to say no message. Signed-off-by: Ariel Chenet --- docs/api.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/docs/api.md b/docs/api.md index 94c465c0e3..8fc951c727 100644 --- a/docs/api.md +++ b/docs/api.md @@ -56,13 +56,6 @@ or a fd with `--api-socket fd=...`. ``` $ ./target/debug/cloud-hypervisor --api-socket path=/tmp/cloud-hypervisor.sock -Cloud Hypervisor Guest - API server: /tmp/cloud-hypervisor.sock - vCPUs: 1 - Memory: 512 MB - Kernel: None - Kernel cmdline: - Disk(s): None ``` #### REST API Endpoints @@ -121,13 +114,6 @@ the REST API available at `/tmp/cloud-hypervisor.sock`: ``` $ ./target/debug/cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock -Cloud Hypervisor Guest - API server: /tmp/cloud-hypervisor.sock - vCPUs: 1 - Memory: 512 MB - Kernel: None - Kernel cmdline: - Disk(s): None ``` ##### Create a Virtual Machine From d59dfdf8b6c477a05bbbe3bd5fbc22232bc8eb1b Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Sun, 2 Nov 2025 11:49:45 +0100 Subject: [PATCH 0259/1893] vmm: seccomp: allow http-server to use sendto Fixes: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7449 Signed-off-by: Alyssa Ross --- vmm/src/seccomp_filters.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 8fff3925bc..afb304c066 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -859,6 +859,7 @@ fn http_api_thread_rules() -> Result)>, BackendError> (libc::SYS_recvfrom, vec![]), (libc::SYS_recvmsg, vec![]), (libc::SYS_sched_yield, vec![]), + (libc::SYS_sendto, vec![]), (libc::SYS_sigaltstack, vec![]), (libc::SYS_write, vec![]), (libc::SYS_rt_sigprocmask, vec![]), From 932e1a636a0029f673dc05ce3a54f3d2adef6d42 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 4 Nov 2025 17:40:45 +0000 Subject: [PATCH 0260/1893] tests: Disable integration tests that use virtio-mem on MSHV These tests are now failing See: #7456 Signed-off-by: Rob Bradford --- tests/integration.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 15919641b6..53a464e8e7 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2867,6 +2867,7 @@ mod common_parallel { } #[test] + #[cfg(not(feature = "mshv"))] // See #7456 fn test_user_defined_memory_regions() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); @@ -2934,6 +2935,7 @@ mod common_parallel { } #[test] + #[cfg(not(feature = "mshv"))] // See #7456 fn test_guest_numa_nodes() { _test_guest_numa_nodes(false); } @@ -5136,6 +5138,7 @@ mod common_parallel { } #[test] + #[cfg(not(feature = "mshv"))] // See #7456 fn test_virtio_mem() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); @@ -5840,6 +5843,7 @@ mod common_parallel { } #[test] + #[cfg(not(feature = "mshv"))] // See #7456 fn test_virtio_balloon_free_page_reporting() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); From e3fa27e251243c10dd68db7c5ad020fa7ac05797 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 31 Oct 2025 19:38:36 -0700 Subject: [PATCH 0261/1893] scripts: Fix issue when extra docker volumes provided With the current syntax docker gives 'docker: invalid reference format' error. Also during parsing /xxx:/yyy in process_volumes_args with \" inside variable i.e arr_vols=("${arg_vols//#/ }") gives wrong output. Example: scripts/dev_cli.sh tests --integration --volumes /mshv:/mshv Error: The volume /mshv /mshv does not exist. Signed-off-by: Muminul Islam --- scripts/dev_cli.sh | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 519517dcdb..92ded5d017 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -144,7 +144,7 @@ fix_dir_perms() { --rm \ --volume /dev:/dev \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ "$CTR_IMAGE" \ chown -R "$(id -u):$(id -g)" "$CTR_CLH_ROOT_DIR" @@ -161,9 +161,9 @@ process_volumes_args() { exported_volumes="" arr_vols=("${arg_vols//#/ }") for var in "${arr_vols[@]}"; do - parts=("${var//:/ }") - if [[ ! -e "${parts[0]}" ]]; then - echo "The volume ${parts[0]} does not exist." + dev=$(echo "$var" | cut -d ':' -f 1) + if [[ ! -e "$dev" ]]; then + echo "The volume $dev does not exist." exit 1 fi exported_volumes="$exported_volumes --volume $var" @@ -296,7 +296,7 @@ cmd_build() { --rm \ --volume $exported_device \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --env RUSTFLAGS="$rustflags" \ --env TARGET_CC="$target_cc" \ "$CTR_IMAGE" \ @@ -316,7 +316,7 @@ cmd_clean() { --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ "$CTR_IMAGE" \ cargo clean \ --target-dir "$CTR_CLH_CARGO_TARGET" \ @@ -413,7 +413,7 @@ cmd_tests() { --device /dev/net/tun \ --cap-add net_admin \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ --env TARGET_CC="$target_cc" \ @@ -434,7 +434,7 @@ cmd_tests() { --mount type=tmpfs,destination=/tmp \ --volume /dev:/dev \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --env BUILD_TARGET="$target" \ @@ -458,7 +458,7 @@ cmd_tests() { --mount type=tmpfs,destination=/tmp \ --volume /dev:/dev \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --env BUILD_TARGET="$target" \ @@ -481,7 +481,7 @@ cmd_tests() { --mount type=tmpfs,destination=/tmp \ --volume /dev:/dev \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --env BUILD_TARGET="$target" \ @@ -504,7 +504,7 @@ cmd_tests() { --mount type=tmpfs,destination=/tmp \ --volume /dev:/dev \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --env BUILD_TARGET="$target" \ @@ -529,7 +529,7 @@ cmd_tests() { --mount type=tmpfs,destination=/tmp \ --volume /dev:/dev \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --env BUILD_TARGET="$target" \ @@ -552,7 +552,7 @@ cmd_tests() { --mount type=tmpfs,destination=/tmp \ --volume /dev:/dev \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --env BUILD_TARGET="$target" \ @@ -576,7 +576,7 @@ cmd_tests() { --mount type=tmpfs,destination=/tmp \ --volume /dev:/dev \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --env BUILD_TARGET="$target" \ @@ -666,7 +666,7 @@ cmd_shell() { --tmpfs /tmp:exec \ --volume /dev:/dev \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+"$exported_volumes"} \ + ${exported_volumes:+$exported_volumes} \ --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --entrypoint bash \ From 3791062b2332e229f55ef01a11a5bd597376c0eb Mon Sep 17 00:00:00 2001 From: Eugene Korenevsky Date: Sun, 2 Nov 2025 01:51:24 +0300 Subject: [PATCH 0262/1893] block: qcow: refactor: extract method cache_l2_cluster() There are several copy-pasted code fragments in impl QcowFile. All of them add L2 entry to the cache and one of them (in file_offset_write()) does also allocating new L2 entry if necessary. Fold all these code fragments (except of one in l2_table() which does error handling in special way) into cache_l2_cluster() method without changing the logic. This will make the code more compact and clean. Signed-off-by: Eugene Korenevsky --- block/src/qcow/mod.rs | 106 ++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 65 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 14deafc856..d9684a4ad2 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1070,21 +1070,7 @@ impl QcowFile { let l2_index = self.l2_table_index(address) as usize; - if !self.l2_cache.contains_key(l1_index) { - // Not in the cache. - let table = - VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?); - - let l1_table = &self.l1_table; - let raw_file = &mut self.raw_file; - self.l2_cache.insert(l1_index, table, |index, evicted| { - raw_file.write_pointer_table( - l1_table[index], - evicted.get_values(), - CLUSTER_USED_FLAG, - ) - })?; - }; + self.cache_l2_cluster(l1_index, l2_addr_disk, false)?; let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index]; if cluster_addr == 0 { @@ -1109,28 +1095,9 @@ impl QcowFile { let mut set_refcounts = Vec::new(); - if !self.l2_cache.contains_key(l1_index) { - // Not in the cache. - let l2_table = if l2_addr_disk == 0 { - // Allocate a new cluster to store the L2 table and update the L1 table to point - // to the new table. - let new_addr: u64 = self.get_new_cluster(None)?; - // The cluster refcount starts at one meaning it is used but doesn't need COW. - set_refcounts.push((new_addr, 1)); - self.l1_table[l1_index] = new_addr; - VecCache::new(self.l2_entries as usize) - } else { - VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?) - }; - let l1_table = &self.l1_table; - let raw_file = &mut self.raw_file; - self.l2_cache.insert(l1_index, l2_table, |index, evicted| { - raw_file.write_pointer_table( - l1_table[index], - evicted.get_values(), - CLUSTER_USED_FLAG, - ) - })?; + if let Some(new_addr) = self.cache_l2_cluster(l1_index, l2_addr_disk, true)? { + // The cluster refcount starts at one meaning it is used but doesn't need COW. + set_refcounts.push((new_addr, 1)); } let cluster_addr = match self.l2_cache.get(l1_index).unwrap()[l2_index] { @@ -1247,20 +1214,7 @@ impl QcowFile { return Ok(false); } - if !self.l2_cache.contains_key(l1_index) { - // Not in the cache. - let table = - VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?); - let l1_table = &self.l1_table; - let raw_file = &mut self.raw_file; - self.l2_cache.insert(l1_index, table, |index, evicted| { - raw_file.write_pointer_table( - l1_table[index], - evicted.get_values(), - CLUSTER_USED_FLAG, - ) - })?; - } + self.cache_l2_cluster(l1_index, l2_addr_disk, false)?; let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index]; // If cluster_addr != 0, the cluster is allocated. @@ -1319,20 +1273,7 @@ impl QcowFile { return Ok(()); } - if !self.l2_cache.contains_key(l1_index) { - // Not in the cache. - let table = - VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?); - let l1_table = &self.l1_table; - let raw_file = &mut self.raw_file; - self.l2_cache.insert(l1_index, table, |index, evicted| { - raw_file.write_pointer_table( - l1_table[index], - evicted.get_values(), - CLUSTER_USED_FLAG, - ) - })?; - } + self.cache_l2_cluster(l1_index, l2_addr_disk, false)?; let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index]; if cluster_addr == 0 { @@ -1417,6 +1358,41 @@ impl QcowFile { .collect()) } + // Put an L2 cluster to the cache with evicting less-used cluster + // The new cluster may be allocated if necessary + // (may_alloc argument is true and l2_addr_disk == 0) + fn cache_l2_cluster( + &mut self, + l1_index: usize, + l2_addr_disk: u64, + may_alloc: bool, + ) -> std::io::Result> { + let mut new_cluster: Option = None; + if !self.l2_cache.contains_key(l1_index) { + // Not in the cache. + let l2_table = if may_alloc && l2_addr_disk == 0 { + // Allocate a new cluster to store the L2 table and update the L1 table to point + // to the new table. + let new_addr: u64 = self.get_new_cluster(None)?; + new_cluster = Some(new_addr); + self.l1_table[l1_index] = new_addr; + VecCache::new(self.l2_entries as usize) + } else { + VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?) + }; + let l1_table = &self.l1_table; + let raw_file = &mut self.raw_file; + self.l2_cache.insert(l1_index, l2_table, |index, evicted| { + raw_file.write_pointer_table( + l1_table[index], + evicted.get_values(), + CLUSTER_USED_FLAG, + ) + })?; + } + Ok(new_cluster) + } + // Set the refcount for a cluster with the given address. // Returns a list of any refblocks that can be reused, this happens when a refblock is moved, // the old location can be reused. From e302d50b091d27c2290d2d8e04e8bf58b818d2f5 Mon Sep 17 00:00:00 2001 From: Aastha Rawat Date: Thu, 6 Nov 2025 15:02:15 +0000 Subject: [PATCH 0263/1893] scripts: aarch64: enable MSHV support Refactor MSHV hypervisor selection logic to enable aarch64 support instead of exiting. Use `--features mshv` to conditionally build & test when hypervisor is mshv replacing hard exit for aarch64 on mshv. Signed-off-by: Aastha Rawat --- scripts/run_integration_tests_aarch64.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 758c69c6b2..6262eb8852 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -170,10 +170,10 @@ update_workloads() { process_common_args "$@" -# aarch64 not supported for MSHV -if [[ "$hypervisor" = "mshv" ]]; then - echo "AArch64 is not supported in Microsoft Hypervisor" - exit 1 +test_features="" + +if [ "$hypervisor" = "mshv" ]; then + test_features="--features mshv" fi # lock the workloads folder to avoid parallel updating by different containers @@ -192,7 +192,7 @@ fi export RUST_BACKTRACE=1 -cargo build --all --release --target "$BUILD_TARGET" +cargo build --features mshv --all --release --target "$BUILD_TARGET" # Enable KSM with some reasonable parameters so that it won't take too long # for the memory to be merged between two processes. @@ -207,13 +207,13 @@ echo "$PAGE_NUM" | sudo tee /proc/sys/vm/nr_hugepages sudo chmod a+rwX /dev/hugepages # Run all direct kernel boot (Device Tree) test cases in mod `parallel` -time cargo test "common_parallel::$test_filter" --target "$BUILD_TARGET" -- --test-threads=$(($(nproc) / 8)) ${test_binary_args[*]} +time cargo test "common_parallel::$test_filter" --target "$BUILD_TARGET" $test_features -- --test-threads=$(($(nproc) / 8)) ${test_binary_args[*]} RES=$? # Run some tests in sequence since the result could be affected by other tests # running in parallel. if [ $RES -eq 0 ]; then - time cargo test "common_sequential::$test_filter" --target "$BUILD_TARGET" -- --test-threads=1 ${test_binary_args[*]} + time cargo test "common_sequential::$test_filter" --target "$BUILD_TARGET" $test_features -- --test-threads=1 ${test_binary_args[*]} RES=$? else exit $RES @@ -221,7 +221,7 @@ fi # Run all ACPI test cases if [ $RES -eq 0 ]; then - time cargo test "aarch64_acpi::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + time cargo test "aarch64_acpi::$test_filter" --target "$BUILD_TARGET" $test_features -- ${test_binary_args[*]} RES=$? else exit $RES @@ -229,14 +229,14 @@ fi # Run all test cases related to live migration if [ $RES -eq 0 ]; then - time cargo test "live_migration_parallel::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + time cargo test "live_migration_parallel::$test_filter" --target "$BUILD_TARGET" $test_features -- ${test_binary_args[*]} RES=$? else exit $RES fi if [ $RES -eq 0 ]; then - time cargo test "live_migration_sequential::$test_filter" --target "$BUILD_TARGET" -- --test-threads=1 ${test_binary_args[*]} + time cargo test "live_migration_sequential::$test_filter" --target "$BUILD_TARGET" $test_features -- --test-threads=1 ${test_binary_args[*]} RES=$? else exit $RES From c4cce386319448f9bc572e4e689113f079f2b2c7 Mon Sep 17 00:00:00 2001 From: Aastha Rawat Date: Thu, 6 Nov 2025 15:04:12 +0000 Subject: [PATCH 0264/1893] scripts: aarch64: replace guestmount for image modification Replace the use of `guestmount and `guestunmount` with standard tools (losetup, mount) to modify cloud disk image. This eliminates dependency on guestmount which requires /dev/kvm and is not available in MSHV root partition. Signed-off-by: Aastha Rawat --- scripts/run_integration_tests_aarch64.sh | 9 +- scripts/run_metrics.sh | 14 +++- scripts/test-util.sh | 100 +++++++++++++++++++++++ 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 6262eb8852..483c9e8d37 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -134,11 +134,12 @@ update_workloads() { FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_NAME="focal-server-cloudimg-arm64-custom-20210929-0-update-kernel.raw" cp "$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME" "$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_NAME" FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_ROOT_DIR="$WORKLOADS_DIR/focal-server-cloudimg-root" + NEW_KERNEL="$WORKLOADS_DIR/Image-arm64.gz" + DST_KERNEL_PATH="boot/vmlinuz" mkdir -p "$FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_ROOT_DIR" - # Mount the 'raw' image, replace the compressed kernel file and umount the working folder - guestmount -a "$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_NAME" -m /dev/sda1 "$FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_ROOT_DIR" || exit 1 - cp "$WORKLOADS_DIR"/Image-arm64.gz "$FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_ROOT_DIR"/boot/vmlinuz - guestunmount "$FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_ROOT_DIR" + IMG="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_NAME" + # Mount image partition, copy kernel, and unmount + copy_to_image "$IMG" "$FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_ROOT_DIR" "$NEW_KERNEL" "$DST_KERNEL_PATH" || exit 1 # Build virtiofsd build_virtiofsd diff --git a/scripts/run_metrics.sh b/scripts/run_metrics.sh index 3f82abda32..988d90aafe 100755 --- a/scripts/run_metrics.sh +++ b/scripts/run_metrics.sh @@ -74,10 +74,16 @@ if [ "${TEST_ARCH}" == "aarch64" ]; then if [ ! -d "$FOCAL_OS_RAW_IMAGE_UPDATE_TOOL_ROOT_DIR" ]; then mkdir -p "$FOCAL_OS_RAW_IMAGE_UPDATE_TOOL_ROOT_DIR" fi - # Mount the 'raw' image, replace the fio and umount the working folder - guestmount -a "$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_UPDATE_TOOL_NAME" -m /dev/sda1 "$FOCAL_OS_RAW_IMAGE_UPDATE_TOOL_ROOT_DIR" || exit 1 - cp "$WORKLOADS_DIR"/fio "$FOCAL_OS_RAW_IMAGE_UPDATE_TOOL_ROOT_DIR"/usr/bin/fio - guestunmount "$FOCAL_OS_RAW_IMAGE_UPDATE_TOOL_ROOT_DIR" + # Mount image partition + IMG="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_UPDATE_TOOL_NAME" + + # Update fio binary in the image + SRC_FIO_PATH="$WORKLOADS_DIR/fio" + DST_FIO_PATH="usr/bin/fio" + + # Mount image partition, copy fio, and unmount + chmod +x "$SRC_FIO_PATH" + copy_to_image "$IMG" "$FOCAL_OS_RAW_IMAGE_UPDATE_TOOL_ROOT_DIR" "$SRC_FIO_PATH" "$DST_FIO_PATH" || exit fi # Prepare linux image (build from source or download pre-built) diff --git a/scripts/test-util.sh b/scripts/test-util.sh index 78f7386552..f0cf93608e 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -179,3 +179,103 @@ download_ovmf() { time wget --quiet $OVMF_FW_URL || exit 1 popd || exit } + +# Function to mount image partition, execute commands, and cleanup. +# Arguments: $1: Image file path, $2: Mount directory, $3+: Commands to execute. +mount_and_exec() { + local IMG="$1" + local MOUNT_DIR="$2" + local LOOP_DEV="" + local PARTITION_DEV="" + local COMMAND_STATUS=0 + + # Cleanup function to unmount and detach loop device + # shellcheck disable=SC2317 + cleanup() { + if [ -n "$MOUNT_DIR" ]; then + echo "Cleanup: Unmounting $MOUNT_DIR..." >&2 + sudo umount -l "$MOUNT_DIR" 2>/dev/null || true + fi + if [ -n "$LOOP_DEV" ]; then + echo "Cleanup: Detaching loop device $LOOP_DEV..." >&2 + sudo losetup -d "$LOOP_DEV" 2>/dev/null || true + fi + } + + if [ ! -f "$IMG" ] || [ -z "$MOUNT_DIR" ]; then + echo "ERROR: Image path ($IMG) or mount directory ($MOUNT_DIR) is invalid." >&2 + return 1 + fi + mkdir -p "$MOUNT_DIR" + + # Create loop device for the entire disk image + LOOP_DEV=$(sudo losetup -f --show "$IMG") + if [ -z "$LOOP_DEV" ]; then + echo "ERROR: Failed to create loop device for $IMG." >&2 + return 1 + fi + + # Set the trap now that LOOP_DEV is active. + trap cleanup EXIT INT TERM + + # Scan for partitions and define partition device node (p1) + sudo partprobe "$LOOP_DEV" 2>/dev/null + PARTITION_DEV="${LOOP_DEV}p1" + + local MAX_RETRIES=5 + local RETRY_DELAY=1 + + # Wait for partition node with retries + for i in $(seq 1 "$MAX_RETRIES"); do + if sudo test -b "$PARTITION_DEV"; then + break + fi + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "ERROR: Partition device node $PARTITION_DEV not found after $MAX_RETRIES attempts." >&2 + return 1 + fi + echo "Partition node $PARTITION_DEV not found, waiting $RETRY_DELAY second(s)... (Attempt $i/$MAX_RETRIES)" >&2 + sleep "$RETRY_DELAY" + done + + # Mount the partition + if ! sudo mount "$PARTITION_DEV" "$MOUNT_DIR"; then + echo "ERROR: Failed to mount $PARTITION_DEV." >&2 + return 1 + fi + + # Execute the commands + shift 2 + "$@" + COMMAND_STATUS=$? + + trap - EXIT INT TERM + return $COMMAND_STATUS +} + +# Function to copy a file from the host into the mounted disk image. +# Arguments: +# $1: Image file path +# $2: Mount directory +# $3: Source file path +# $4: Destination file path +copy_to_image() { + local IMG="$1" + local MOUNT_DIR="$2" + local SRC_FILE="$3" + local DST_PATH="$4" + + if [ ! -f "$SRC_FILE" ]; then + echo "ERROR: Source file not found at $SRC_FILE." >&2 + return 1 + elif [ -z "$DST_PATH" ]; then + echo "ERROR: Destination path cannot be empty." >&2 + return 1 + fi + + # Define the command to copy the file + local COPY_COMMAND="sudo cp \"$SRC_FILE\" \"$MOUNT_DIR/$DST_PATH\"" + + mount_and_exec "$IMG" "$MOUNT_DIR" /bin/bash -c "$COPY_COMMAND" + return $? +} From 9acf610a7b612aea92f3a7fd9f32767c04338280 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 6 Nov 2025 23:19:17 +0000 Subject: [PATCH 0265/1893] build: Release v49.0 Signed-off-by: Bo Chen --- Cargo.lock | 2 +- Cargo.toml | 2 +- release-notes.md | 224 ++++++++++++++++++++++++++++++----------------- 3 files changed, 144 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0057c99c46..118b60cea6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,7 +413,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cloud-hypervisor" -version = "48.0.0" +version = "49.0.0" dependencies = [ "anyhow", "api_client", diff --git a/Cargo.toml b/Cargo.toml index 2426b3c0bb..1d03f39688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" license = "Apache-2.0 AND BSD-3-Clause" name = "cloud-hypervisor" -version = "48.0.0" +version = "49.0.0" # Minimum buildable version: # Keep in sync with version in .github/workflows/build.yaml # Policy on MSRV (see #4318): diff --git a/release-notes.md b/release-notes.md index 4dc982e838..d839d79c4f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,3 +1,9 @@ +- [v49.0](#v490) + - [MSHV Support Improvements](#mshv-support-improvements) + - [Logging Improvements](#logging-improvements) + - [Removed Default IP and Mask for `virtio-net` Devices](#removed-default-ip-and-mask-for-virtio-net-devices) + - [Notable Bug Fixes](#notable-bug-fixes) + - [Contributors](#contributors) - [v48.0](#v480) - [Experimental `fw_cfg` Device Support](#experimental-fw_cfg-device-support) - [Experimental `ivshmem` Device Support](#experimental-ivshmem-device-support) @@ -8,62 +14,62 @@ - [Updated Documentation on Windows Guest Support](#updated-documentation-on-windows-guest-support) - [Policy on AI Generated Code](#policy-on-ai-generated-code) - [Removed SGX Support](#removed-sgx-support) - - [Notable Bug Fixes](#notable-bug-fixes) - - [Contributors](#contributors) + - [Notable Bug Fixes](#notable-bug-fixes-1) + - [Contributors](#contributors-1) - [v47.0](#v470) - [Block Device Error Reporting to the Guest](#block-device-error-reporting-to-the-guest) - [Nice Error Messages on Exit](#nice-error-messages-on-exit) - [Alphabetically Sorted CLI Options for ch-remote](#alphabetically-sorted-cli-options-for-ch-remote) - - [Notable Bug Fixes](#notable-bug-fixes-1) + - [Notable Bug Fixes](#notable-bug-fixes-2) - [Deprecations](#deprecations) - - [Contributors](#contributors-1) + - [Contributors](#contributors-2) - [v46.0](#v460) - [File-level Locking Support with `--disk`](#file-level-locking-support-with---disk) - [Improved Error Reporting with VM Resizing](#improved-error-reporting-with-vm-resizing) - [IPv6 Address Support with `--net`](#ipv6-address-support-with---net) - [Experimental AArch64 Support with the MSHV Hypervisor](#experimental-aarch64-support-with-the-mshv-hypervisor) - [Deprecated SGX Support](#deprecated-sgx-support) - - [Notable Bug Fixes](#notable-bug-fixes-2) - - [Contributors](#contributors-2) + - [Notable Bug Fixes](#notable-bug-fixes-3) + - [Contributors](#contributors-3) - [v45.0](#v450) - [Experimental `riscv64` Architecture Support](#experimental-riscv64-architecture-support) - [Alphabetically Sorted CLI Options](#alphabetically-sorted-cli-options) - [Improved Downtime of VM Live Migration](#improved-downtime-of-vm-live-migration) - - [Notable Bug Fixes](#notable-bug-fixes-3) - - [Contributors](#contributors-3) + - [Notable Bug Fixes](#notable-bug-fixes-4) + - [Contributors](#contributors-4) - [v44.0](#v440) - [Configurable `virtio-iommu` Address Width](#configurable-virtio-iommu-address-width) - [Notable Performance Improvements](#notable-performance-improvements) - [New Fuzzers](#new-fuzzers) - - [Notable Bug Fixes](#notable-bug-fixes-4) - - [Contributors](#contributors-4) + - [Notable Bug Fixes](#notable-bug-fixes-5) + - [Contributors](#contributors-5) - [v43.0](#v430) - [Live Migration over TCP Connections](#live-migration-over-tcp-connections) - [Notable Performance Improvements](#notable-performance-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-5) - - [Contributors](#contributors-5) + - [Notable Bug Fixes](#notable-bug-fixes-6) + - [Contributors](#contributors-6) - [v42.0](#v420) - [SVE/SVE2 Support on AArch64](#svesve2-support-on-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-6) + - [Notable Bug Fixes](#notable-bug-fixes-7) - [Sponsorships](#sponsorships) - - [Contributors](#contributors-6) + - [Contributors](#contributors-7) - [v41.0](#v410) - [Experimental "Pvmemcontrol" Support](#experimental-pvmemcontrol-support) - [Sandboxing With Landlock Support](#sandboxing-with-landlock-support) - [Notable Performance Improvements](#notable-performance-improvements-2) - - [Notable Bug Fixes](#notable-bug-fixes-7) - - [Contributors](#contributors-7) -- [v40.0](#v400) - - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) - [Notable Bug Fixes](#notable-bug-fixes-8) - [Contributors](#contributors-8) +- [v40.0](#v400) + - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) + - [Notable Bug Fixes](#notable-bug-fixes-9) + - [Contributors](#contributors-9) - [v39.0](#v390) - [Variable Sizing of PCI Apertures for Segments](#variable-sizing-of-pci-apertures-for-segments) - [Direct Booting with bzImages](#direct-booting-with-bzimages) - [Support for NVIDIA GPUDirect P2P Support](#support-for-nvidia-gpudirect-p2p-support) - [Guest NMI Injection Support](#guest-nmi-injection-support) - - [Notable Bug Fixes](#notable-bug-fixes-9) - - [Contributors](#contributors-9) + - [Notable Bug Fixes](#notable-bug-fixes-10) + - [Contributors](#contributors-10) - [v38.0](#v380) - [Group Rate Limiter on Block Devices](#group-rate-limiter-on-block-devices) - [CPU Pinning Support for Block Device Worker Thread](#cpu-pinning-support-for-block-device-worker-thread) @@ -71,16 +77,16 @@ - [New 'debug-console' Device](#new-debug-console-device) - [Improved VFIO Device Support](#improved-vfio-device-support) - [Extended CPU Affinity Support](#extended-cpu-affinity-support) - - [Notable Bug Fixes](#notable-bug-fixes-10) - - [Contributors](#contributors-10) + - [Notable Bug Fixes](#notable-bug-fixes-11) + - [Contributors](#contributors-11) - [v37.0](#v370) - [Long Term Support (LTS) Release](#long-term-support-lts-release) - [Multiple PCI segments Support for 32-bit VFIO devices](#multiple-pci-segments-support-for-32-bit-vfio-devices) - [Configurable Named TAP Devices](#configurable-named-tap-devices) - [TTY Output from Both Serial Device and Virtio Console](#tty-output-from-both-serial-device-and-virtio-console) - [Faster VM Restoration from Snapshots](#faster-vm-restoration-from-snapshots) - - [Notable Bug Fixes](#notable-bug-fixes-11) - - [Contributors](#contributors-11) + - [Notable Bug Fixes](#notable-bug-fixes-12) + - [Contributors](#contributors-12) - [v36.0](#v360) - [Command Line Changes](#command-line-changes) - [Enabled Features Reported via API Endpoint and CLI](#enabled-features-reported-via-api-endpoint-and-cli) @@ -89,31 +95,31 @@ - [Unix Socket Backend for Serial Port](#unix-socket-backend-for-serial-port) - [AIO Backend for Block Devices](#aio-backend-for-block-devices) - [Documentation Improvements](#documentation-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-12) - - [Contributors](#contributors-12) + - [Notable Bug Fixes](#notable-bug-fixes-13) + - [Contributors](#contributors-13) - [v35.0](#v350) - [`virtio-vsock` Support for Linux Guest Kernel v6.3+](#virtio-vsock-support-for-linux-guest-kernel-v63) - [User Specified Serial Number for `virtio-block`](#user-specified-serial-number-for-virtio-block) - [vCPU TSC Frequency Included in Migration State](#vcpu-tsc-frequency-included-in-migration-state) - - [Notable Bug Fixes](#notable-bug-fixes-13) - - [Contributors](#contributors-13) + - [Notable Bug Fixes](#notable-bug-fixes-14) + - [Contributors](#contributors-14) - [v34.0](#v340) - [Paravirtualised Panic Device Support](#paravirtualised-panic-device-support) - [Improvements to VM Core Dump](#improvements-to-vm-core-dump) - [QCOW2 Support for Backing Files](#qcow2-support-for-backing-files) - [Minimum Host Kernel Bump](#minimum-host-kernel-bump) - - [Notable Bug Fixes](#notable-bug-fixes-14) - - [Contributors](#contributors-14) + - [Notable Bug Fixes](#notable-bug-fixes-15) + - [Contributors](#contributors-15) - [v33.0](#v330) - [D-Bus based API](#d-bus-based-api) - [Expose Host CPU Cache Details for AArch64](#expose-host-cpu-cache-details-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-15) - - [Contributors](#contributors-15) + - [Notable Bug Fixes](#notable-bug-fixes-16) + - [Contributors](#contributors-16) - [v32.0](#v320) - [Increased PCI Segment Limit](#increased-pci-segment-limit) - [API Changes](#api-changes) - - [Notable Bug Fixes](#notable-bug-fixes-16) - - [Contributors](#contributors-16) + - [Notable Bug Fixes](#notable-bug-fixes-17) + - [Contributors](#contributors-17) - [v31.1](#v311) - [v31.0](#v310) - [Update to Latest `acpi_tables`](#update-to-latest-acpi_tables) @@ -121,15 +127,15 @@ - [Improvements on Console `SIGWINCH` Handler](#improvements-on-console-sigwinch-handler) - [Remove Directory Support from `MemoryZoneConfig::file`](#remove-directory-support-from-memoryzoneconfigfile) - [Documentation Improvements](#documentation-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-17) - - [Contributors](#contributors-17) + - [Notable Bug Fixes](#notable-bug-fixes-18) + - [Contributors](#contributors-18) - [v30.0](#v300) - [Command Line Changes for Reduced Binary Size](#command-line-changes-for-reduced-binary-size) - [Basic vfio-user Server Support](#basic-vfio-user-server-support) - [Heap Profiling Support](#heap-profiling-support) - [Documentation Improvements](#documentation-improvements-2) - - [Notable Bug Fixes](#notable-bug-fixes-18) - - [Contributors](#contributors-18) + - [Notable Bug Fixes](#notable-bug-fixes-19) + - [Contributors](#contributors-19) - [v28.2](#v282) - [v29.0](#v290) - [Release Binary Supports Both MSHV and KVM](#release-binary-supports-both-mshv-and-kvm) @@ -139,10 +145,10 @@ - [`AArch64` Documentation Integration](#aarch64-documentation-integration) - [`virtio-block` Counters Enhancement](#virtio-block-counters-enhancement) - [TCP Offload Control](#tcp-offload-control) - - [Notable Bug Fixes](#notable-bug-fixes-19) + - [Notable Bug Fixes](#notable-bug-fixes-20) - [Removals](#removals) - [Deprecations](#deprecations-1) - - [Contributors](#contributors-19) + - [Contributors](#contributors-20) - [v28.1](#v281) - [v28.0](#v280) - [Community Engagement (Reminder)](#community-engagement-reminder) @@ -150,9 +156,9 @@ - [Virtualised TPM Support](#virtualised-tpm-support) - [Transparent Huge Page Support](#transparent-huge-page-support) - [README Quick Start Improved](#readme-quick-start-improved) - - [Notable Bug Fixes](#notable-bug-fixes-20) + - [Notable Bug Fixes](#notable-bug-fixes-21) - [Removals](#removals-1) - - [Contributors](#contributors-20) + - [Contributors](#contributors-21) - [v27.0](#v270) - [Community Engagement](#community-engagement) - [Prebuilt Packages](#prebuilt-packages) @@ -161,41 +167,41 @@ - [Simplified Build Feature Flags](#simplified-build-feature-flags) - [Asynchronous Kernel Loading](#asynchronous-kernel-loading) - [GDB Support for AArch64](#gdb-support-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-21) + - [Notable Bug Fixes](#notable-bug-fixes-22) - [Deprecations](#deprecations-2) - - [Contributors](#contributors-21) + - [Contributors](#contributors-22) - [v26.0](#v260) - [SMBIOS Improvements via `--platform`](#smbios-improvements-via---platform) - [Unified Binary MSHV and KVM Support](#unified-binary-mshv-and-kvm-support) - - [Notable Bug Fixes](#notable-bug-fixes-22) + - [Notable Bug Fixes](#notable-bug-fixes-23) - [Deprecations](#deprecations-3) - [Removals](#removals-2) - - [Contributors](#contributors-22) + - [Contributors](#contributors-23) - [v25.0](#v250) - [`ch-remote` Improvements](#ch-remote-improvements-1) - [VM "Coredump" Support](#vm-coredump-support) - - [Notable Bug Fixes](#notable-bug-fixes-23) + - [Notable Bug Fixes](#notable-bug-fixes-24) - [Removals](#removals-3) - - [Contributors](#contributors-23) + - [Contributors](#contributors-24) - [v24.0](#v240) - [Bypass Mode for `virtio-iommu`](#bypass-mode-for-virtio-iommu) - [Ensure Identifiers Uniqueness](#ensure-identifiers-uniqueness) - [Sparse Mmap support](#sparse-mmap-support) - [Expose Platform Serial Number](#expose-platform-serial-number) - - [Notable Bug Fixes](#notable-bug-fixes-24) + - [Notable Bug Fixes](#notable-bug-fixes-25) - [Notable Improvements](#notable-improvements) - [Deprecations](#deprecations-4) - [New on the Website](#new-on-the-website) - - [Contributors](#contributors-24) + - [Contributors](#contributors-25) - [v23.1](#v231) - [v23.0](#v230) - [vDPA Support](#vdpa-support) - [Updated OS Support list](#updated-os-support-list) - [`AArch64` Memory Map Improvements](#aarch64-memory-map-improvements) - [`AMX` Support](#amx-support) - - [Notable Bug Fixes](#notable-bug-fixes-25) + - [Notable Bug Fixes](#notable-bug-fixes-26) - [Deprecations](#deprecations-5) - - [Contributors](#contributors-25) + - [Contributors](#contributors-26) - [v22.1](#v221) - [v22.0](#v220) - [GDB Debug Stub Support](#gdb-debug-stub-support) @@ -206,13 +212,13 @@ - [PMU Support for AArch64](#pmu-support-for-aarch64) - [Documentation Under CC-BY-4.0 License](#documentation-under-cc-by-40-license) - [Deprecation of "Classic" `virtiofsd`](#deprecation-of-classic-virtiofsd) - - [Notable Bug Fixes](#notable-bug-fixes-26) - - [Contributors](#contributors-26) + - [Notable Bug Fixes](#notable-bug-fixes-27) + - [Contributors](#contributors-27) - [v21.0](#v210) - [Efficient Local Live Migration (for Live Upgrade)](#efficient-local-live-migration-for-live-upgrade) - [Recommended Kernel is Now 5.15](#recommended-kernel-is-now-515) - - [Notable Bug fixes](#notable-bug-fixes-27) - - [Contributors](#contributors-27) + - [Notable Bug fixes](#notable-bug-fixes-28) + - [Contributors](#contributors-28) - [v20.2](#v202) - [v20.1](#v201) - [v20.0](#v200) @@ -221,8 +227,8 @@ - [Improved VFIO support](#improved-vfio-support) - [Safer code](#safer-code) - [Extended documentation](#extended-documentation) - - [Notable bug fixes](#notable-bug-fixes-28) - - [Contributors](#contributors-28) + - [Notable bug fixes](#notable-bug-fixes-29) + - [Contributors](#contributors-29) - [v19.0](#v190) - [Improved PTY handling for serial and `virtio-console`](#improved-pty-handling-for-serial-and-virtio-console) - [PCI boot time optimisations](#pci-boot-time-optimisations) @@ -230,8 +236,8 @@ - [Live migration enhancements](#live-migration-enhancements) - [`virtio-mem` support with `vfio-user`](#virtio-mem-support-with-vfio-user) - [AArch64 for `virtio-iommu`](#aarch64-for-virtio-iommu) - - [Notable bug fixes](#notable-bug-fixes-29) - - [Contributors](#contributors-29) + - [Notable bug fixes](#notable-bug-fixes-30) + - [Contributors](#contributors-30) - [v18.0](#v180) - [Experimental User Device (`vfio-user`) support](#experimental-user-device-vfio-user-support) - [Migration support for `vhost-user` devices](#migration-support-for-vhost-user-devices) @@ -241,23 +247,23 @@ - [Live migration on MSHV hypervisor](#live-migration-on-mshv-hypervisor) - [AArch64 CPU topology support](#aarch64-cpu-topology-support) - [Power button support on AArch64](#power-button-support-on-aarch64) - - [Notable bug fixes](#notable-bug-fixes-30) - - [Contributors](#contributors-30) + - [Notable bug fixes](#notable-bug-fixes-31) + - [Contributors](#contributors-31) - [v17.0](#v170) - [ARM64 NUMA support using ACPI](#arm64-numa-support-using-acpi) - [`Seccomp` support for MSHV backend](#seccomp-support-for-mshv-backend) - [Hotplug of `macvtap` devices](#hotplug-of-macvtap-devices) - [Improved SGX support](#improved-sgx-support) - [Inflight tracking for `vhost-user` devices](#inflight-tracking-for-vhost-user-devices) - - [Notable bug fixes](#notable-bug-fixes-31) - - [Contributors](#contributors-31) + - [Notable bug fixes](#notable-bug-fixes-32) + - [Contributors](#contributors-32) - [v16.0](#v160) - [Improved live migration support](#improved-live-migration-support) - [Improved `vhost-user` support](#improved-vhost-user-support) - [ARM64 ACPI and UEFI support](#arm64-acpi-and-uefi-support) - - [Notable bug fixes](#notable-bug-fixes-32) + - [Notable bug fixes](#notable-bug-fixes-33) - [Removed functionality](#removed-functionality) - - [Contributors](#contributors-32) + - [Contributors](#contributors-33) - [v15.0](#v150) - [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees) - [Network device rate limiting](#network-device-rate-limiting) @@ -265,7 +271,7 @@ - [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter) - [Bug fixes](#bug-fixes) - [Deprecations](#deprecations-6) - - [Contributors](#contributors-33) + - [Contributors](#contributors-34) - [v0.14.1](#v0141) - [v0.14.0](#v0140) - [Structured event monitoring](#structured-event-monitoring) @@ -275,7 +281,7 @@ - [PTY control for serial and `virtio-console`](#pty-control-for-serial-and-virtio-console) - [Block device rate limiting](#block-device-rate-limiting) - [Deprecations](#deprecations-7) - - [Contributors](#contributors-34) + - [Contributors](#contributors-35) - [v0.13.0](#v0130) - [Wider VFIO device support](#wider-vfio-device-support) - [Improved huge page support](#improved-huge-page-support) @@ -283,13 +289,13 @@ - [VHD disk image support](#vhd-disk-image-support) - [Improved Virtio device threading](#improved-virtio-device-threading) - [Clean shutdown support via synthetic power button](#clean-shutdown-support-via-synthetic-power-button) - - [Contributors](#contributors-35) + - [Contributors](#contributors-36) - [v0.12.0](#v0120) - [ARM64 enhancements](#arm64-enhancements) - [Removal of `vhost-user-net` and `vhost-user-block` self spawning](#removal-of-vhost-user-net-and-vhost-user-block-self-spawning) - [Migration of `vhost-user-fs` backend](#migration-of-vhost-user-fs-backend) - [Enhanced "info" API](#enhanced-info-api) - - [Contributors](#contributors-36) + - [Contributors](#contributors-37) - [v0.11.0](#v0110) - [`io_uring` support by default for `virtio-block`](#io_uring-support-by-default-for-virtio-block) - [Windows Guest Support](#windows-guest-support) @@ -301,15 +307,15 @@ - [Default Log Level Changed](#default-log-level-changed) - [New `--balloon` Parameter Added](#new---balloon-parameter-added) - [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support) - - [Notable Bug Fixes](#notable-bug-fixes-33) - - [Contributors](#contributors-37) + - [Notable Bug Fixes](#notable-bug-fixes-34) + - [Contributors](#contributors-38) - [v0.10.0](#v0100) - [`virtio-block` Support for Multiple Descriptors](#virtio-block-support-for-multiple-descriptors) - [Memory Zones](#memory-zones) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements) - [Preliminary KVM HyperV Emulation Control](#preliminary-kvm-hyperv-emulation-control) - - [Notable Bug Fixes](#notable-bug-fixes-34) - - [Contributors](#contributors-38) + - [Notable Bug Fixes](#notable-bug-fixes-35) + - [Contributors](#contributors-39) - [v0.9.0](#v090) - [`io_uring` Based Block Device Support](#io_uring-based-block-device-support) - [Block and Network Device Statistics](#block-and-network-device-statistics) @@ -322,17 +328,17 @@ - [Enhancements to ARM64 Support](#enhancements-to-arm64-support) - [Intel SGX Support](#intel-sgx-support) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-35) - - [Contributors](#contributors-39) + - [Notable Bug Fixes](#notable-bug-fixes-36) + - [Contributors](#contributors-40) - [v0.8.0](#v080) - [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support) - [Experimental ARM64 Support](#experimental-arm64-support) - [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests) - [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices) - [`vhost_user_fs` Improvements](#vhost_user_fs-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-36) + - [Notable Bug Fixes](#notable-bug-fixes-37) - [Command Line and API Changes](#command-line-and-api-changes) - - [Contributors](#contributors-40) + - [Contributors](#contributors-41) - [v0.7.0](#v070) - [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug) - [Alternative `libc` Support](#alternative-libc-support) @@ -342,14 +348,14 @@ - [`Seccomp` Sandboxing](#seccomp-sandboxing) - [Updated Distribution Support](#updated-distribution-support) - [Command Line and API Changes](#command-line-and-api-changes-1) - - [Contributors](#contributors-41) + - [Contributors](#contributors-42) - [v0.6.0](#v060) - [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug) - [Shared Filesystem Improvements](#shared-filesystem-improvements) - [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading) - [Command Line Interface](#command-line-interface) - [PVH Boot](#pvh-boot) - - [Contributors](#contributors-42) + - [Contributors](#contributors-43) - [v0.5.1](#v051) - [v0.5.0](#v050) - [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing) @@ -357,7 +363,7 @@ - [New Interrupt Management Framework](#new-interrupt-management-framework) - [Development Tools](#development-tools) - [Kata Containers Integration](#kata-containers-integration) - - [Contributors](#contributors-43) + - [Contributors](#contributors-44) - [v0.4.0](#v040) - [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition) - [Programmatic firmware tables generation](#programmatic-firmware-tables-generation) @@ -366,7 +372,7 @@ - [Userspace IOAPIC by default](#userspace-ioapic-by-default) - [PCI BAR reprogramming](#pci-bar-reprogramming) - [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization) - - [Contributors](#contributors-44) + - [Contributors](#contributors-45) - [v0.3.0](#v030) - [Block device offloading](#block-device-offloading) - [Network device backend](#network-device-backend) @@ -393,6 +399,60 @@ - [Unit testing](#unit-testing) - [Integration tests parallelization](#integration-tests-parallelization) +# v49.0 + +This release has been tracked in [v49.0 +group](https://github.com/orgs/cloud-hypervisor/projects/6/views/4?filterQuery=release%3A%22Release+49%22) +of our [roadmap project](https://github.com/orgs/cloud-hypervisor/projects/6/). + +### MSHV Support Improvements + +On AArch64 with MSHV, firmware boot (#7391) and graceful guest shutdown +(#7354) are now supported, and CI coverage has been added (#7381). + +### Logging Improvements + +Logs now use an improved timestamp format (#7355), emit an explicit +message on graceful shutdown (#7346), and reduce noisy warnings with +newer guest kernels (#7423). + +### Removed Default IP and Mask for `virtio-net` Devices + +The implicit default IP (192.168.249.1) and netmask (255.255.255.0) for +`virtio-net` devices have been removed, as previously announced in the +deprecation notice two releases ago (#7365). Users now can create +`virtio-net` devices with no IP and netmask assigned. + +### Notable Bug Fixes + +* Prevent crash on malformed vsock connect requests (#7310) +* Fix vCPU hotplug when the vCPU count exceeds 255 (#7349) +* Drop the VFIO container when no VFIO devices are active (#7364) +* Gracefully close preserved file descriptors on hot device removal (#7371) +* Fix race condition in pause–resume cycles (#7397) +* Add NetConfig offload parameters to the OpenAPI spec file (#7445) +* Fix seccomp filter violation from the http-server thread (#7454) + +### Contributors + +Many thanks to everyone who has contributed to our release: + +* Aastha Rawat +* Alyssa Ross +* Anirudh Rayabharam +* Ariel Chenet +* Bo Chen +* Can Zhang +* Eugene Korenevsky +* Maximilian Güntner +* Muminul Islam +* Oliver Anderson +* Philipp Schuster +* Rob Bradford +* Ruoqing He +* Wei Liu +* Yi Wang + # v48.0 This release has been tracked in [v48.0 From dd66eb834c80b3f15717ab7c209ad0644c870d26 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Fri, 31 Oct 2025 13:19:48 +0000 Subject: [PATCH 0266/1893] ci: dump kernel logs in MSHV workflow Dump kernel logs after running the tests in the MSHV workflow to help debug failures. In addition to getting the kernel logs using `dmesg` also use AzCli to retrieve the serial console logs. If the VM is hung or panicked, the workflow would be unable to SSH into it and execute `dmesg`. In this case the serial console logs would be helpful. Signed-off-by: Anirudh Rayabharam --- .github/workflows/mshv-infra.yaml | 14 +++++++++++++- .github/workflows/mshv-integration.yaml | 24 +++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index 72a90548a7..ffd72713e1 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -34,6 +34,12 @@ on: 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 }} @@ -46,6 +52,8 @@ jobs: 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 @@ -112,6 +120,7 @@ jobs: # 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 @@ -173,6 +182,9 @@ jobs: --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 @@ -234,4 +246,4 @@ jobs: sudo groupadd -f docker sudo usermod -a -G docker ${USERNAME} sudo systemctl restart docker - EOF \ No newline at end of file + EOF diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index b14cc5603b..2083c54361 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -79,6 +79,28 @@ jobs: 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 @@ -106,4 +128,4 @@ jobs: else echo "SSH key does not exist. Skipping deletion." fi - echo "Cleanup process completed." \ No newline at end of file + echo "Cleanup process completed." From bb7730e00f69d28eb65c36522ec29a6fcdd8d389 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 10 Nov 2025 16:01:03 +0000 Subject: [PATCH 0267/1893] vmm: seccomp: Use rseq syscall constant Formerly these syscall's had to be specified by number as the constants were missing in musl. Signed-off-by: Rob Bradford --- vmm/src/seccomp_filters.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index afb304c066..2b0016fa58 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -638,12 +638,7 @@ fn vmm_thread_rules( (libc::SYS_recvfrom, vec![]), (libc::SYS_recvmsg, vec![]), (libc::SYS_restart_syscall, vec![]), - // musl is missing this constant - // (libc::SYS_rseq, vec![]), - #[cfg(target_arch = "x86_64")] - (334, vec![]), - #[cfg(target_arch = "aarch64")] - (293, vec![]), + (libc::SYS_rseq, vec![]), (libc::SYS_rt_sigaction, vec![]), (libc::SYS_rt_sigprocmask, vec![]), (libc::SYS_rt_sigreturn, vec![]), @@ -888,12 +883,7 @@ fn dbus_api_thread_rules() -> Result)>, BackendError> (libc::SYS_munmap, vec![]), (libc::SYS_prctl, vec![]), (libc::SYS_recvmsg, vec![]), - // musl is missing this constant - // (libc::SYS_rseq, vec![]), - #[cfg(target_arch = "x86_64")] - (334, vec![]), - #[cfg(target_arch = "aarch64")] - (293, vec![]), + (libc::SYS_rseq, vec![]), (libc::SYS_rt_sigprocmask, vec![]), (libc::SYS_sched_getaffinity, vec![]), (libc::SYS_sched_yield, vec![]), From e023efce3d5df405628758ab7c26244841932656 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 4 Nov 2025 19:41:04 +0000 Subject: [PATCH 0268/1893] vmm: cpu: Retry signalling the vCPU thread if it doesn't acknowledge Resignal every 10ms the thread if it has not acknowledged the signal via setting the atomic when the vCPU thread was acknowledged. Further, avoid an infinite loop by generating an error if it takes more than 1000ms to interrupt the thread. The retry helps mitigate a race condition where the signal is received between checking the pause atomic and entering KVM_RUN ioctl when pausing. Hitting this race condition would leave the wait_untial_signal_acknowledged() method spinning indefinitely. The timeout error avoids the VMM process being blocked indefinitely. See: #7427 Signed-off-by: Rob Bradford --- vmm/src/cpu.rs | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 01e12e807f..02e1cc315f 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -153,6 +153,9 @@ pub enum Error { #[error("Failed to join on vCPU threads: {0:?}")] ThreadCleanup(std::boxed::Box), + #[error("Timeout when waiting for signal to be acknowledged")] + SignalAcknowledgeTimeout, + #[error("Error adding CpuManager to MMIO bus")] BusError(#[source] vm_device::BusError), @@ -707,21 +710,31 @@ impl VcpuState { } } - /// Blocks until the vCPU thread has acknowledged the signal. + /// Blocks until the vCPU thread has acknowledged the signal. It retries to send + /// the signal every 10ms. Times out after 1000ms. /// /// This is the counterpart of [`Self::signal_thread`]. - fn wait_until_signal_acknowledged(&self) { + fn wait_until_signal_acknowledged(&self) -> Result<()> { if let Some(_handle) = self.handle.as_ref() { + let mut count = 0; loop { if self.vcpu_run_interrupted.load(Ordering::SeqCst) { - break; + return Ok(()); } else { // This is more effective than thread::yield_now() at // avoiding a priority inversion with the vCPU thread thread::sleep(std::time::Duration::from_millis(1)); + count += 1; + if count >= 1000 { + return Err(Error::SignalAcknowledgeTimeout); + } else if count % 10 == 0 { + warn!("vCPU thread did not respond in {count}ms to signal - retrying"); + self.signal_thread(); + } } } } + Ok(()) } fn join_thread(&mut self) -> Result<()> { @@ -1356,7 +1369,7 @@ impl CpuManager { let state = &mut self.vcpu_states[usize::try_from(cpu_id).unwrap()]; state.kill.store(true, Ordering::SeqCst); state.signal_thread(); - state.wait_until_signal_acknowledged(); + state.wait_until_signal_acknowledged()?; state.join_thread()?; state.handle = None; @@ -1428,15 +1441,17 @@ impl CpuManager { /// /// For the vCPU threads this will interrupt the KVM_RUN ioctl() allowing /// the loop to check the shared state booleans. - fn signal_vcpus(&self) { + fn signal_vcpus(&mut self) -> Result<()> { // Splitting this into two loops reduced the time to pause many vCPUs // massively. Example: 254 vCPUs. >254ms -> ~4ms. for state in self.vcpu_states.iter() { state.signal_thread(); } for state in self.vcpu_states.iter() { - state.wait_until_signal_acknowledged(); + state.wait_until_signal_acknowledged()?; } + + Ok(()) } pub fn shutdown(&mut self) -> Result<()> { @@ -1451,7 +1466,7 @@ impl CpuManager { state.unpark_thread(); } - self.signal_vcpus(); + self.signal_vcpus()?; // Wait for all the threads to finish. This removes the state from the vector. for mut state in self.vcpu_states.drain(..) { @@ -2065,9 +2080,9 @@ impl CpuManager { self.sev_snp_enabled } - pub(crate) fn nmi(&self) -> Result<()> { + pub(crate) fn nmi(&mut self) -> Result<()> { self.vcpus_kick_signalled.store(true, Ordering::SeqCst); - self.signal_vcpus(); + self.signal_vcpus()?; self.vcpus_kick_signalled.store(false, Ordering::SeqCst); Ok(()) @@ -2429,7 +2444,8 @@ impl Pausable for CpuManager { // Tell the vCPUs to pause themselves next time they exit self.vcpus_pause_signalled.store(true, Ordering::SeqCst); - self.signal_vcpus(); + self.signal_vcpus() + .map_err(|e| MigratableError::Pause(anyhow!("Error signalling vCPUs: {e}")))?; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] for vcpu in self.vcpus.iter() { From 8bf284e71318606c56bbd1d3be40fe23e1591410 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 12 Nov 2025 17:21:33 +0000 Subject: [PATCH 0269/1893] scripts: Bump Linux version to 6.16.9 Signed-off-by: Rob Bradford --- scripts/test-util.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/test-util.sh b/scripts/test-util.sh index f0cf93608e..ede00b7255 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -49,7 +49,7 @@ checkout_repo() { build_custom_linux() { ARCH=$(uname -m) LINUX_CUSTOM_DIR="$WORKLOADS_DIR/linux-custom" - LINUX_CUSTOM_BRANCH="ch-6.12.8" + LINUX_CUSTOM_BRANCH="ch-6.16.9" LINUX_CUSTOM_URL="https://github.com/cloud-hypervisor/linux.git" checkout_repo "$LINUX_CUSTOM_DIR" "$LINUX_CUSTOM_URL" "$LINUX_CUSTOM_BRANCH" @@ -140,7 +140,7 @@ download_hypervisor_fw() { } download_linux() { - KERNEL_TAG="ch-release-v6.12.8-20250613" + KERNEL_TAG="ch-release-v6.16.9-20251112" if [ -n "$AUTH_DOWNLOAD_TOKEN" ]; then echo "Using authenticated download from GitHub" KERNEL_URLS=$(curl --silent https://api.github.com/repos/cloud-hypervisor/linux/releases/tags/${KERNEL_TAG} \ From b3b51bd3a2d45c871ec1534f7d5a4a50197543bd Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 12 Nov 2025 18:31:08 +0000 Subject: [PATCH 0270/1893] tests: aarch64: Fix test_virtio_iommu for kernel IOMMU groups change The numbers for the IOMMU groups have shifted after the update to Linux kernel 6.16.9. Signed-off-by: Rob Bradford --- tests/integration.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 53a464e8e7..87ffc08a9b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2283,7 +2283,7 @@ fn _test_virtio_iommu(acpi: bool) { // contains "0000:00:02.0" which is the first disk. // // Verify the iommu group of the first disk. - let iommu_group = !acpi as i32; + let iommu_group = if acpi { 0 } else { 2 }; assert_eq!( guest .ssh_command(format!("ls /sys/kernel/iommu_groups/{iommu_group}/devices").as_str()) @@ -2293,7 +2293,7 @@ fn _test_virtio_iommu(acpi: bool) { ); // Verify the iommu group of the second disk. - let iommu_group = if acpi { 1 } else { 2 }; + let iommu_group = if acpi { 1 } else { 3 }; assert_eq!( guest .ssh_command(format!("ls /sys/kernel/iommu_groups/{iommu_group}/devices").as_str()) @@ -2303,7 +2303,7 @@ fn _test_virtio_iommu(acpi: bool) { ); // Verify the iommu group of the network card. - let iommu_group = if acpi { 2 } else { 3 }; + let iommu_group = if acpi { 2 } else { 4 }; assert_eq!( guest .ssh_command(format!("ls /sys/kernel/iommu_groups/{iommu_group}/devices").as_str()) From d586c844de43d69f3d9f27e53aeeb48d033b28ad Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 12 Nov 2025 17:05:34 +0000 Subject: [PATCH 0271/1893] tests: Update test_iommu_segments check After updating the Linux kernel to 6.19.6 the second segment (segment=1) is now under the 2nd IOMMU group (which it a more logical setup) and as such the added device which is on that segment is in that second IOMMU group. The same check is made in test_vdpa_block so also test there. Signed-off-by: Rob Bradford --- tests/integration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 87ffc08a9b..a419f9d278 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3007,7 +3007,7 @@ mod common_parallel { ); assert_eq!( guest - .ssh_command("ls /sys/kernel/iommu_groups/0/devices") + .ssh_command("ls /sys/kernel/iommu_groups/1/devices") .unwrap() .trim(), "0001:00:01.0" @@ -7126,7 +7126,7 @@ mod common_parallel { ); assert_eq!( guest - .ssh_command("ls /sys/kernel/iommu_groups/0/devices") + .ssh_command("ls /sys/kernel/iommu_groups/1/devices") .unwrap() .trim(), "0001:00:01.0" From 063aa4b7d53acedfaae8d8b54f84cab60595a4c7 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 11 Nov 2025 15:48:42 +0000 Subject: [PATCH 0272/1893] arch: riscv64: Expose host extension set to guest via FDT The set of extensions supported by a RISC-V system needs to be exposed to the guest - currently that is a fixed, minimal set of extensions. These extensions are not sufficient to boot Ubuntu 25.10 which now has a mininimum requirement of RVA23S64 (which is a minimum set of extensions that make sense for server use cases.) The easiest way to convey the extensions that the guest should use is to copy those that the host kernel understands (and thus includes in the /proc/cpuinfo) data. However since nested virtualisation is not currently possible - exclude the "H" (Hypervisor) extension from the list of short (single letter) extensions. Signed-off-by: Rob Bradford --- arch/src/riscv64/fdt.rs | 7 ++--- arch/src/riscv64/mod.rs | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/arch/src/riscv64/fdt.rs b/arch/src/riscv64/fdt.rs index ee453eb2fc..e781388616 100644 --- a/arch/src/riscv64/fdt.rs +++ b/arch/src/riscv64/fdt.rs @@ -65,6 +65,7 @@ pub fn create_fdt, aia_device: &Arc>, initrd: &Option, @@ -84,7 +85,7 @@ pub fn create_fdt, guest_mem: &GuestMemoryMmap) -> R } // 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) -> FdtWriterResult<()> { +fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32, isa_string: &str) -> 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. @@ -123,7 +124,7 @@ fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> { fdt.property_string("device_type", "cpu")?; fdt.property_string("compatible", "riscv")?; fdt.property_string("mmu-type", "sv48")?; - fdt.property_string("riscv,isa", "rv64imafdc_smaia_ssaia")?; + fdt.property_string("riscv,isa", isa_string)?; fdt.property_string("status", "okay")?; fdt.property_u32("reg", cpu_index)?; fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?; diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index b4478aaa6d..b4feb99d57 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -12,6 +12,8 @@ pub mod uefi; use std::collections::HashMap; use std::fmt::Debug; +use std::fs::File; +use std::io::{BufRead, BufReader}; use std::sync::{Arc, Mutex}; use hypervisor::arch::riscv64::aia::Vaia; @@ -51,6 +53,22 @@ pub enum Error { /// Error configuring the general purpose registers #[error("Error configuring the general purpose registers")] RegsConfiguration(#[source] hypervisor::HypervisorCpuError), + + /// Error opening /proc/cpuinfo + #[error("Error opening /proc/cpuinfo")] + OpenCpuInfo(#[source] std::io::Error), + + /// Error reading /proc/cpuinfo + #[error("Error reading /proc/cpuinfo")] + ReadCpuInfo(#[source] std::io::Error), + + /// Invalid ISA string + #[error("Invalid ISA string: {0}")] + InvalidIsaString(String), + + /// Error parsing /proc/cpuinfo + #[error("Error parsing /proc/cpuinfo")] + CpuInfoParsing, } #[derive(Debug, Copy, Clone)] @@ -104,6 +122,43 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> { ] } +// Read the first "isa" string from /proc/cpuinfo and filter out the H extension, +// while correctly preserving multi-letter extensions. +fn isa_string_from_host() -> Result { + let file = File::open("/proc/cpuinfo").map_err(Error::OpenCpuInfo)?; + let reader = BufReader::new(file); + + for line in reader.lines() { + let line = line.map_err(Error::ReadCpuInfo)?; + let trimmed_line = line.trim(); + + if trimmed_line.starts_with("isa") { + let parts: Vec<&str> = trimmed_line.split(':').collect(); + if parts.len() == 2 { + let isa_string = parts[1].trim(); + + // Split the string by underscores to separate single letter vs long-form + // extensions + let mut components: Vec = + isa_string.split('_').map(|s| s.to_string()).collect(); + + if components.is_empty() { + return Err(Error::InvalidIsaString(isa_string.to_string())); + } + + // Remove H extension if present in single letter extensions + let first_component = components[0].chars().filter(|&c| c != 'h').collect(); + + components[0] = first_component; + + return Ok(components.join("_")); + } + } + } + + Err(Error::CpuInfoParsing) +} + /// Configures the system and should be called once per vm before starting vcpu threads. #[allow(clippy::too_many_arguments)] pub fn configure_system( @@ -115,10 +170,12 @@ pub fn configure_system>, ) -> super::Result<()> { + let isa_string = isa_string_from_host()?; let fdt_final = fdt::create_fdt( guest_mem, cmdline, num_vcpu, + &isa_string, device_info, aia_device, initrd, From f826d926014f7a95adddce4630182cd2335a7f97 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 13 Nov 2025 13:24:19 +0100 Subject: [PATCH 0273/1893] misc: gitlint: allow well-known commit tags to exceed line limit To get that list, I've used ``` git log | grep --fixed-strings -- "-by:" | head -n 100000 | sort | less ``` on the Linux kernel's git repository. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- scripts/gitlint/rules/BodyMaxLineLengthEx.py | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/scripts/gitlint/rules/BodyMaxLineLengthEx.py b/scripts/gitlint/rules/BodyMaxLineLengthEx.py index 88314fc529..5541fb30e2 100644 --- a/scripts/gitlint/rules/BodyMaxLineLengthEx.py +++ b/scripts/gitlint/rules/BodyMaxLineLengthEx.py @@ -4,6 +4,30 @@ import re +IGNORE_PREFIXES = [ + # Please sort alphabetically + "Acked-by: ", + "Co-authored-by: ", + "Co-developed-by: ", + "Debugged-by: ", + "Diagnosed-by: ", + "Explained-by: ", + "Fixed-by: ", + "Fixes: ", + "Helped-by: ", + "Inspired-by: ", + "On-behalf-of: ", + "Originally-by: ", + "Reported-by: ", + "Reviewed-and-tested-by: ", + "Reviewed-by: ", + "Signed-off-by: ", + "Suggested-by: ", + "Tested-by: ", + "Triggered-by: ", +] + + class BodyMaxLineLengthEx(LineRule): """A rule to enforce a line limit of 72 characters, except for valid cases.""" @@ -44,6 +68,11 @@ def validate(self, line, commit): if is_link: return + # Don't check lines with allowed prefixes + for prefix in IGNORE_PREFIXES: + if line.startswith(prefix): + return None + return [ RuleViolation(self.id, f"Line '{line}' exceeds limit of {self.max_len}") ] From 62345cd6fdbd7a98191c14ef18f45812356e8927 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 13 Nov 2025 07:22:27 +0100 Subject: [PATCH 0274/1893] misc: gitlint: allow more prefixes disabling 72 width limit Suggested-by Alyssa Ross [0]. [0]: https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7471#discussion_r2519894440 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- .gitlint | 2 +- scripts/gitlint/rules/BodyMaxLineLengthEx.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlint b/.gitlint index 455dd0281a..d65c4ab73e 100644 --- a/.gitlint +++ b/.gitlint @@ -1,7 +1,7 @@ [general] extra-path=scripts/gitlint/rules regex-style-search=true -ignore=body-max-line-length +ignore=body-max-line-length,body-hard-tab [ignore-by-author-name] regex=dependabot diff --git a/scripts/gitlint/rules/BodyMaxLineLengthEx.py b/scripts/gitlint/rules/BodyMaxLineLengthEx.py index 5541fb30e2..34b213e5b9 100644 --- a/scripts/gitlint/rules/BodyMaxLineLengthEx.py +++ b/scripts/gitlint/rules/BodyMaxLineLengthEx.py @@ -6,6 +6,7 @@ IGNORE_PREFIXES = [ # Please sort alphabetically + " ", "Acked-by: ", "Co-authored-by: ", "Co-developed-by: ", @@ -25,6 +26,7 @@ "Suggested-by: ", "Tested-by: ", "Triggered-by: ", + "\t", ] From 02da2f2d369bcfdcf543902d00e29e7bf5bc599e Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 13 Nov 2025 13:24:03 +0100 Subject: [PATCH 0275/1893] misc: gitlint: python code improvements Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- scripts/gitlint/rules/BodyMaxLineLengthEx.py | 59 ++++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/scripts/gitlint/rules/BodyMaxLineLengthEx.py b/scripts/gitlint/rules/BodyMaxLineLengthEx.py index 34b213e5b9..60b555d83a 100644 --- a/scripts/gitlint/rules/BodyMaxLineLengthEx.py +++ b/scripts/gitlint/rules/BodyMaxLineLengthEx.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 from gitlint.rules import LineRule, RuleViolation, CommitMessageBody +from typing import List, Optional import re @@ -29,9 +30,23 @@ "\t", ] +# Pattern allowing: +# - [0]: https://example.com +# - [0] https://example.com +# - https://example.com +LINK_REGEX = re.compile(r"^(([\[0-9]+]:?\s?)?https?://).*$") + +MAX_LEN = 72 + class BodyMaxLineLengthEx(LineRule): - """A rule to enforce a line limit of 72 characters, except for valid cases.""" + """ + A rule to enforce a line limit of 72 characters, except for valid cases: + + - Markdown-style code blocks + - Commit tags, such as Signed-off-by + - Links + """ # A rule MUST have a human friendly name name = "body-max-line-length-ex" @@ -43,38 +58,34 @@ class BodyMaxLineLengthEx(LineRule): # A line-rule MUST have a target (not required for CommitRules). target = CommitMessageBody - max_len = 72 - # Updated property as the commit messages is validated line by line. inside_open_codeblock = False - def validate(self, line, commit): - # Pattern allowing: - # - [0]: https://foobar - # - [0] https://foobar - # - https://foobar - link_regex = re.compile(r"^((\[[0-9]+\]:?\s?)?https?:\/\/).*$") - + def validate(self, line, commit) -> Optional[List[RuleViolation]]: + # We keep track of whether we are in an open code block. is_codeblock_marker = line.startswith("```") - inside_open_codeblock_ = self.inside_open_codeblock if is_codeblock_marker: self.inside_open_codeblock = not self.inside_open_codeblock - if len(line) > self.max_len: - is_link = link_regex.match(line) + # Begin checks + if len(line) <= MAX_LEN: + return None - if inside_open_codeblock_: - return + if inside_open_codeblock_: + return None - if is_link: - return + if None is not LINK_REGEX.match(line): + return None - # Don't check lines with allowed prefixes - for prefix in IGNORE_PREFIXES: - if line.startswith(prefix): - return None + # Don't check lines with allowed prefixes + for prefix in IGNORE_PREFIXES: + if line.startswith(prefix): + return None - return [ - RuleViolation(self.id, f"Line '{line}' exceeds limit of {self.max_len}") - ] + return [ + RuleViolation( + self.id, + f"Line '{line}' exceeds limit of {MAX_LEN}: {len(line)}", + ) + ] From ec57aade1563075e37b8e9ccc0b85fe2c04a54b8 Mon Sep 17 00:00:00 2001 From: Matt Moriarity Date: Thu, 13 Nov 2025 08:52:43 -0700 Subject: [PATCH 0276/1893] seccomp: allow sendto for vsock thread as of rust 1.90, writes to unix socket streams use send_with_flags instead of write, so it uses a sendto syscall instead of write. Signed-off-by: Matt Moriarity --- virtio-devices/src/seccomp_filters.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs index 5986e72ea3..26d1445f6e 100644 --- a/virtio-devices/src/seccomp_filters.rs +++ b/virtio-devices/src/seccomp_filters.rs @@ -239,6 +239,7 @@ fn virtio_vsock_thread_rules() -> Vec<(i64, Vec)> { (libc::SYS_connect, vec![]), (libc::SYS_ioctl, create_vsock_ioctl_seccomp_rule()), (libc::SYS_recvfrom, vec![]), + (libc::SYS_sendto, vec![]), (libc::SYS_socket, vec![]), // If debug_assertions is enabled, closing a file first checks // whether the FD is valid with fcntl. From 5051feb0bd98512051d989126b08a0243bb7c222 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 14 Nov 2025 14:32:56 +0000 Subject: [PATCH 0277/1893] build: Bump MSRV to 1.89.0 This is required to support exclusive locking on files which is needed for safe test ID generation when using nextest (since it runs each test as a separate process.) Signed-off-by: Rob Bradford --- .github/workflows/build.yaml | 2 +- .github/workflows/docker-image.yaml | 2 +- .github/workflows/preview-riscv64-build.yaml | 2 +- .github/workflows/preview-riscv64-modules.yaml | 2 +- .github/workflows/release.yaml | 2 +- Cargo.toml | 2 +- resources/Dockerfile | 2 +- scripts/dev_cli.sh | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 286c2af548..0a5cef9abb 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,7 +15,7 @@ jobs: - stable - beta - nightly - - "1.88.0" + - "1.89.0" target: - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index a026eac2c6..3696ae97d6 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -41,7 +41,7 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # generate Docker tags based on the following events/attributes tags: | - type=raw,value=20250815-0 + type=raw,value=20251114-0 type=sha - name: Build and push diff --git a/.github/workflows/preview-riscv64-build.yaml b/.github/workflows/preview-riscv64-build.yaml index 929a60147a..cdcfd9638e 100644 --- a/.github/workflows/preview-riscv64-build.yaml +++ b/.github/workflows/preview-riscv64-build.yaml @@ -18,7 +18,7 @@ jobs: fetch-depth: 0 - name: Install Rust toolchain - run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0 + run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0 - name: Build test (kvm) run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked --no-default-features --features "kvm" diff --git a/.github/workflows/preview-riscv64-modules.yaml b/.github/workflows/preview-riscv64-modules.yaml index 767d9779a8..f06b96286c 100644 --- a/.github/workflows/preview-riscv64-modules.yaml +++ b/.github/workflows/preview-riscv64-modules.yaml @@ -24,7 +24,7 @@ jobs: fetch-depth: 0 - name: Install Rust toolchain - run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0 + 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 rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0ab1ea813f..2c26d8bb94 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -45,7 +45,7 @@ jobs: target: ${{ matrix.platform.target }} args: ${{ matrix.platform.args }} strip: true - toolchain: "1.88.0" + toolchain: "1.89.0" - name: Copy Release Binaries if: github.event_name == 'create' && github.event.ref_type == 'tag' shell: bash diff --git a/Cargo.toml b/Cargo.toml index 1d03f39688..4b74245de5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ version = "49.0.0" # 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. -rust-version = "1.88.0" +rust-version = "1.89.0" [profile.release] codegen-units = 1 diff --git a/resources/Dockerfile b/resources/Dockerfile index 704e8602f3..1b08f6acb3 100644 --- a/resources/Dockerfile +++ b/resources/Dockerfile @@ -8,7 +8,7 @@ FROM ubuntu:24.04 AS dev ARG TARGETARCH -ARG RUST_TOOLCHAIN="1.88.0" +ARG RUST_TOOLCHAIN="1.89.0" ARG CLH_SRC_DIR="/cloud-hypervisor" ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build" ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry" diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 92ded5d017..ee95b99b37 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -9,7 +9,7 @@ CLI_NAME="Cloud Hypervisor" CTR_IMAGE_TAG="ghcr.io/cloud-hypervisor/cloud-hypervisor" # Needs to match explicit version in docker-image.yaml workflow -CTR_IMAGE_VERSION="20250815-0" +CTR_IMAGE_VERSION="20251114-0" : "${CTR_IMAGE:=${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}}" DOCKER_RUNTIME="docker" From 176023156eb8885b62b5fd0c53971080e61db498 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 14 Nov 2025 14:42:36 +0000 Subject: [PATCH 0278/1893] test_infra: Make guest ID generation multiprocess safe When using nextest for running tests each test is run in its own process so the old solution of using a static variable for the guest ID (used to determine the network segment) no longer works. Instead use a text file on the filesystem protected with an exclusive lock. The test process will read from it and then write back the next ID that can be used. It wraps around at the limit of u8 and skips ID 0. This function intentionally panics rather than propagate errors as it should only be called for testing purposes and there the panic handler will give a useful backtrace and cleanup. Signed-off-by: Rob Bradford --- test_infra/src/lib.rs | 45 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 24a818c194..12d9c5940a 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -7,14 +7,14 @@ use std::ffi::OsStr; use std::fmt::Display; -use std::io::{Read, Write}; +use std::fs::OpenOptions; +use std::io::{Read, Seek, SeekFrom, Write}; use std::net::{TcpListener, TcpStream}; use std::os::unix::fs::PermissionsExt; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::path::Path; use std::process::{Child, Command, ExitStatus, Output, Stdio}; use std::str::FromStr; -use std::sync::{LazyLock, Mutex}; use std::time::Duration; use std::{env, fmt, fs, io, thread}; @@ -849,14 +849,45 @@ pub fn kill_child(child: &mut Child) { pub const PIPE_SIZE: i32 = 32 << 20; -static NEXT_VM_ID: LazyLock> = LazyLock::new(|| Mutex::new(1)); - pub struct Guest { pub tmp_dir: TempDir, pub disk_config: Box, pub network: GuestNetworkConfig, } +// Return the next id that can be used for this guest. This is stored in a +// file in the filesystem and is protected by a filesystem lock allowing +// multiple test processes to safely access it with the process blocking +// until the lock is released. +fn next_guest_id() -> u8 { + let mut id_file_path = dirs::home_dir().unwrap(); + id_file_path.push("workloads"); + id_file_path.push("id.counter"); + + let mut id_file = OpenOptions::new() + .create(true) + .truncate(false) + .write(true) + .read(true) + .open(id_file_path) + .unwrap(); + + id_file.lock().unwrap(); + + // Use a string in the file for (human) readability + let mut buf = String::default(); + id_file.read_to_string(&mut buf).unwrap(); + let id = buf.trim().parse::().unwrap_or(1); + let next_id = u8::max(1, id.overflowing_add(1).0); + id_file.set_len(0).unwrap(); + id_file.seek(SeekFrom::Start(0)).unwrap(); + id_file.write_all(next_id.to_string().as_bytes()).unwrap(); + + id_file.unlock().unwrap(); + + id +} + // Safe to implement as we know we have no interior mutability impl std::panic::RefUnwindSafe for Guest {} @@ -890,11 +921,7 @@ impl Guest { } pub fn new(disk_config: Box) -> Self { - let mut guard = NEXT_VM_ID.lock().unwrap(); - let id = *guard; - *guard = id + 1; - - Self::new_from_ip_range(disk_config, "192.168", id) + Self::new_from_ip_range(disk_config, "192.168", next_guest_id()) } pub fn default_net_string(&self) -> String { From 2707b0f72ab3acef15fdda9c6a97b8d1d9bfafa8 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 14 Nov 2025 14:59:28 +0000 Subject: [PATCH 0279/1893] build: Add cargo nextest to container cargo nextest is an improved test runner that allows retries as well a reporting the times for the test runs. Signed-off-by: Rob Bradford --- resources/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/Dockerfile b/resources/Dockerfile index 1b08f6acb3..1eead0d26b 100644 --- a/resources/Dockerfile +++ b/resources/Dockerfile @@ -112,6 +112,7 @@ RUN export ARCH="$(uname -m)" \ $RUST_TOOLCHAIN-x86_64-unknown-linux-musl; fi \ && if [ "$TARGETARCH" = "amd64" ]; then rustup component add rustfmt; fi \ && if [ "$TARGETARCH" = "amd64" ]; then rustup component add clippy; fi \ + && cargo install cargo-nextest --locked \ && rm -rf "$CARGO_HOME/registry" \ && ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \ && rm -rf "$CARGO_HOME/git" \ From 3734a13cbf306ed098f94bbe3641c9a155cef6fe Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 14 Nov 2025 15:04:59 +0000 Subject: [PATCH 0280/1893] tests: Use cargo nextest for integration tests This alternative test runner supports retries and also reports how long each test takes to run. Signed-off-by: Rob Bradford --- scripts/run_integration_tests_aarch64.sh | 20 ++++++++++--------- .../run_integration_tests_live_migration.sh | 5 +++-- scripts/run_integration_tests_rate_limiter.sh | 2 +- scripts/run_integration_tests_vfio.sh | 2 +- .../run_integration_tests_windows_aarch64.sh | 2 +- .../run_integration_tests_windows_x86_64.sh | 2 +- scripts/run_integration_tests_x86_64.sh | 10 +++++----- 7 files changed, 23 insertions(+), 20 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 483c9e8d37..af6b4773c1 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -208,13 +208,13 @@ echo "$PAGE_NUM" | sudo tee /proc/sys/vm/nr_hugepages sudo chmod a+rwX /dev/hugepages # Run all direct kernel boot (Device Tree) test cases in mod `parallel` -time cargo test "common_parallel::$test_filter" --target "$BUILD_TARGET" $test_features -- --test-threads=$(($(nproc) / 8)) ${test_binary_args[*]} +time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "common_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? # Run some tests in sequence since the result could be affected by other tests # running in parallel. if [ $RES -eq 0 ]; then - time cargo test "common_sequential::$test_filter" --target "$BUILD_TARGET" $test_features -- --test-threads=1 ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=1 "common_sequential::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES @@ -222,7 +222,7 @@ fi # Run all ACPI test cases if [ $RES -eq 0 ]; then - time cargo test "aarch64_acpi::$test_filter" --target "$BUILD_TARGET" $test_features -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "aarch64_acpi::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES @@ -230,14 +230,14 @@ fi # Run all test cases related to live migration if [ $RES -eq 0 ]; then - time cargo test "live_migration_parallel::$test_filter" --target "$BUILD_TARGET" $test_features -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES fi if [ $RES -eq 0 ]; then - time cargo test "live_migration_sequential::$test_filter" --target "$BUILD_TARGET" $test_features -- --test-threads=1 ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES @@ -247,7 +247,8 @@ fi if [ $RES -eq 0 ]; then cargo build --features "dbus_api" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo test "dbus_api::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + # integration tests now do not reply on build feature "dbus_api" + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} RES=$? fi @@ -255,14 +256,15 @@ fi if [ $RES -eq 0 ]; then cargo build --features "fw_cfg" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo test "fw_cfg::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} RES=$? fi if [ $RES -eq 0 ]; then - cargo build --features ivshmem --all --release --target "$BUILD_TARGET" + cargo build --features "ivshmem" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo test "ivshmem::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} + RES=$? fi diff --git a/scripts/run_integration_tests_live_migration.sh b/scripts/run_integration_tests_live_migration.sh index fa0b3dcf45..38ddd9e380 100755 --- a/scripts/run_integration_tests_live_migration.sh +++ b/scripts/run_integration_tests_live_migration.sh @@ -84,14 +84,15 @@ echo "$PAGE_NUM" | sudo tee /proc/sys/vm/nr_hugepages sudo chmod a+rwX /dev/hugepages export RUST_BACKTRACE=1 -time cargo test $test_features "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} +time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} + RES=$? # Run some tests in sequence since the result could be affected by other tests # running in parallel. if [ $RES -eq 0 ]; then export RUST_BACKTRACE=1 - time cargo test $test_features "live_migration_sequential::$test_filter" -- --test-threads=1 ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} RES=$? fi diff --git a/scripts/run_integration_tests_rate_limiter.sh b/scripts/run_integration_tests_rate_limiter.sh index fd9f689a3f..1ae8975943 100755 --- a/scripts/run_integration_tests_rate_limiter.sh +++ b/scripts/run_integration_tests_rate_limiter.sh @@ -56,7 +56,7 @@ fi cargo build --features mshv --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 -time cargo test $test_features "rate_limiter::$test_filter" -- --test-threads=1 ${test_binary_args[*]} +time cargo nextest run $test_features --test-threads=1 "rate_limiter::$test_filter" -- ${test_binary_args[*]} RES=$? exit $RES diff --git a/scripts/run_integration_tests_vfio.sh b/scripts/run_integration_tests_vfio.sh index 4d7bac60a4..1f36f9580e 100755 --- a/scripts/run_integration_tests_vfio.sh +++ b/scripts/run_integration_tests_vfio.sh @@ -27,7 +27,7 @@ fi cargo build --features mshv --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 -time cargo test "vfio::test_nvidia" -- --test-threads=1 ${test_binary_args[*]} +time cargo nextest run --test-threads=1 "vfio::test_nvidia" -- ${test_binary_args[*]} RES=$? exit $RES diff --git a/scripts/run_integration_tests_windows_aarch64.sh b/scripts/run_integration_tests_windows_aarch64.sh index 92d66f805d..a4dcda7a6e 100755 --- a/scripts/run_integration_tests_windows_aarch64.sh +++ b/scripts/run_integration_tests_windows_aarch64.sh @@ -42,7 +42,7 @@ cargo build --all --release --target "$BUILD_TARGET" # Only run with 1 thread to avoid tests interfering with one another because # Windows has a static IP configured -time cargo test "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} +time cargo nextest run "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} RES=$? dmsetup remove_all -f diff --git a/scripts/run_integration_tests_windows_x86_64.sh b/scripts/run_integration_tests_windows_x86_64.sh index 2b11a6e687..9708038295 100755 --- a/scripts/run_integration_tests_windows_x86_64.sh +++ b/scripts/run_integration_tests_windows_x86_64.sh @@ -45,7 +45,7 @@ export RUST_BACKTRACE=1 # Only run with 1 thread to avoid tests interfering with one another because # Windows has a static IP configured -time cargo test $test_features "windows::$test_filter" -- ${test_binary_args[*]} +time cargo nextest run $test_features "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} RES=$? dmsetup remove_all -f diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 1d9c56be78..1b2990a669 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -178,14 +178,14 @@ ulimit -l unlimited ulimit -n 4096 export RUST_BACKTRACE=1 -time cargo test --release --target "$BUILD_TARGET" $test_features "common_parallel::$test_filter" -- ${test_binary_args[*]} --test-threads=$((($(nproc) * 3) / 4)) +time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "common_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? # Run some tests in sequence since the result could be affected by other tests # running in parallel. if [ $RES -eq 0 ]; then export RUST_BACKTRACE=1 - time cargo test --release --target "$BUILD_TARGET" $test_features "common_sequential::$test_filter" -- --test-threads=1 ${test_binary_args[*]} + cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=1 "common_sequential::$test_filter" -- ${test_binary_args[*]} RES=$? fi @@ -194,7 +194,7 @@ if [ $RES -eq 0 ]; then cargo build --features "mshv,dbus_api" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 # integration tests now do not reply on build feature "dbus_api" - time cargo test $test_features "dbus_api::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} RES=$? fi @@ -202,14 +202,14 @@ fi if [ $RES -eq 0 ]; then cargo build --features "mshv,fw_cfg" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo test $test_features "fw_cfg::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} RES=$? fi if [ $RES -eq 0 ]; then cargo build --features "mshv,ivshmem" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo test $test_features "ivshmem::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} RES=$? fi From 9f046f02a214f1b4a187ac3a3dcdeb8376c407c4 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 14 Nov 2025 16:18:16 +0000 Subject: [PATCH 0281/1893] tests: Disable test_snapshot_restore_with_fd() This is now failing on x86-64 as well after the update of the Rust version. Signed-off-by: Rob Bradford --- tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration.rs b/tests/integration.rs index a419f9d278..14ace3850d 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -8256,7 +8256,7 @@ mod common_sequential { #[test] #[cfg(not(feature = "mshv"))] // See issue #7437 - #[cfg_attr(target_arch = "aarch64", ignore = "See #6970")] + #[ignore = "See #6970"] fn test_snapshot_restore_with_fd() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); From f9076cccfa00168cd451c386d2ba253ad9423400 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 15 Nov 2025 09:01:55 +0000 Subject: [PATCH 0282/1893] tests: Add test timeout (10 minutes) to nextest configuration Signed-off-by: Rob Bradford --- .config/nextest.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .config/nextest.toml diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 0000000000..a88e599093 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,3 @@ +[profile.default] +# Don't let one individual test run for more than 10 minutes +slow-timeout = { period = "60s", terminate-after = 10 } From 6147c4c8b74f3108a3fe16e046876db0beba2cbd Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 15 Nov 2025 12:13:56 +0000 Subject: [PATCH 0283/1893] tests: Disable nextest failure on fw_cfg tests on MSHV Due to the fw_cfg test being disabled on MSHV this results in no tests being runnable which results in an error in nextest. Reduce that error to a warning use --no-tests=warn Signed-off-by: Rob Bradford --- scripts/run_integration_tests_x86_64.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 1b2990a669..952f7e8cd2 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -202,7 +202,7 @@ fi if [ $RES -eq 0 ]; then cargo build --features "mshv,fw_cfg" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --no-tests=warn --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} RES=$? fi From 96f4e33897421baa42d84e1184dbffdcaa0aee47 Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Fri, 7 Nov 2025 14:51:23 +0100 Subject: [PATCH 0284/1893] vm-migration: add helper to iterate over bitmaps Instead of using ad-hoc code, just write an extension to the Iterator trait that we can easily unit test. Co-authored-by: Philipp Schuster On-behalf-of: SAP julian.stecklina@sap.com On-behalf-of: SAP philipp.schuster@sap.com Signed-off-by: Julian Stecklina Signed-off-by: Philipp Schuster --- vm-migration/src/bitpos_iterator.rs | 95 +++++++++++++++++++++++++++++ vm-migration/src/lib.rs | 1 + 2 files changed, 96 insertions(+) create mode 100644 vm-migration/src/bitpos_iterator.rs diff --git a/vm-migration/src/bitpos_iterator.rs b/vm-migration/src/bitpos_iterator.rs new file mode 100644 index 0000000000..cce89ff0fe --- /dev/null +++ b/vm-migration/src/bitpos_iterator.rs @@ -0,0 +1,95 @@ +// Copyright © 2025 Cyberus Technology GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::Mul; + +/// An iterator that turns a sequence of u64s into a sequence of bit positions +/// that are set. +/// +/// This is useful to iterate over dirty memory bitmaps. +struct BitposIterator { + underlying_it: I, + + /// How many `u64`'s we've already consumed. + /// + /// `u32` is sufficient. + word_pos: u32, + + /// If we already started working on a u64, it's here. Together with the bit + /// position where we have to continue. + current_word: Option<(u64 /* cur word */, u32 /* cur pos */)>, +} + +impl Iterator for BitposIterator +where + I: Iterator, +{ + type Item = u64; + + fn next(&mut self) -> Option { + loop { + if self.current_word.is_none() { + self.current_word = self.underlying_it.next().map(|w| (w, 0)); + } + + let (word, word_bit) = self.current_word?; + + // Continue early if there is no chance to find something. + if word != 0 && word_bit < 64 { + let shifted_word = word >> word_bit; + if shifted_word != 0 { + let zeroes = shifted_word.trailing_zeros(); + + self.current_word = Some((word, zeroes + word_bit + 1)); + let next_bitpos = (self.word_pos as u64) + .mul(64) + // the inner value can not overflow + .checked_add(word_bit as u64 + zeroes as u64) + .unwrap(); + + return Some(next_bitpos); + } + } + + self.current_word = None; + self.word_pos += 1; + } + } +} + +pub trait BitposIteratorExt: Iterator + Sized { + /// Turn an iterator over `u64` into an iterator over the bit positions of + /// all 1s. We basically treat the incoming `u64` as one gigantic integer + /// and just spit out which bits are set. + fn bit_positions(self) -> impl Iterator { + BitposIterator { + underlying_it: self, + word_pos: 0, + current_word: None, + } + } +} + +impl + Sized> BitposIteratorExt for I {} + +#[cfg(test)] +mod tests { + use super::*; + + fn bitpos_check(inp: &[u64], out: &[u64]) { + assert_eq!(inp.iter().copied().bit_positions().collect::>(), out); + } + + #[test] + fn bitpos_iterator_works() { + bitpos_check(&[], &[]); + bitpos_check(&[0], &[]); + bitpos_check(&[1], &[0]); + bitpos_check(&[5], &[0, 2]); + bitpos_check(&[3 + 32], &[0, 1, 5]); + bitpos_check(&[1 << 63], &[63]); + + bitpos_check(&[1, 1 + 32], &[0, 64, 69]); + } +} diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 05bcc3131e..7532026743 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -9,6 +9,7 @@ use thiserror::Error; use crate::protocol::MemoryRangeTable; +mod bitpos_iterator; pub mod protocol; #[derive(Error, Debug)] From ad9034ed1d5852d7882756bb1cfb29b955911be6 Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Fri, 7 Nov 2025 14:51:49 +0100 Subject: [PATCH 0285/1893] vm-migration: optimize dirty bitmap scanning Adding itertools as dependency improves the iteration code in the following significantly. With this change, we don't need a copy of the vector. Just something that can be coerced into an iterator. We also use the bit position iterator to make the code somewhat clearer. The new code is much faster, because it will not iterate over every bit, just each 1 bit in the input. The next commit will complete this optimization and have some concrete numbers. On-behalf-of: SAP julian.stecklina@sap.com Signed-off-by: Julian Stecklina --- Cargo.lock | 16 ++++++++++ Cargo.toml | 1 + vm-migration/Cargo.toml | 1 + vm-migration/src/protocol.rs | 61 +++++++++++++++++++++--------------- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 118b60cea6..feb65da670 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -597,6 +597,12 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "endi" version = "1.1.0" @@ -1031,6 +1037,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -2418,6 +2433,7 @@ name = "vm-migration" version = "0.1.0" dependencies = [ "anyhow", + "itertools", "serde", "serde_json", "thiserror 2.0.17", diff --git a/Cargo.toml b/Cargo.toml index 4b74245de5..e1d2f158bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,6 +145,7 @@ dirs = "6.0.0" env_logger = "0.11.8" epoll = "4.4.0" flume = "0.11.1" +itertools = "0.14.0" libc = "0.2.177" log = "0.4.28" signal-hook = "0.3.18" diff --git a/vm-migration/Cargo.toml b/vm-migration/Cargo.toml index 7a8c9337b3..69d57076f5 100644 --- a/vm-migration/Cargo.toml +++ b/vm-migration/Cargo.toml @@ -6,6 +6,7 @@ version = "0.1.0" [dependencies] anyhow = { workspace = true } +itertools = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index 2ed782ae39..8c320c68e6 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -5,10 +5,12 @@ use std::io::{Read, Write}; +use itertools::Itertools; use serde::{Deserialize, Serialize}; use vm_memory::ByteValued; use crate::MigratableError; +use crate::bitpos_iterator::BitposIteratorExt; // Migration protocol // 1: Source establishes communication with destination (file socket or TCP connection.) @@ -211,38 +213,47 @@ pub struct MemoryRange { pub length: u64, } +impl MemoryRange { + /// Turn an iterator over the dirty bitmap into an iterator of dirty ranges. + pub fn dirty_ranges( + bitmap: impl IntoIterator, + start_addr: u64, + page_size: u64, + ) -> impl Iterator { + bitmap + .into_iter() + .bit_positions() + // Turn them into single-element ranges for coalesce. + .map(|b| b..(b + 1)) + // Merge adjacent ranges. + .coalesce(|prev, curr| { + if prev.end == curr.start { + Ok(prev.start..curr.end) + } else { + Err((prev, curr)) + } + }) + .map(move |r| Self { + gpa: start_addr + r.start * page_size, + length: (r.end - r.start) * page_size, + }) + } +} + #[derive(Clone, Default, Serialize, Deserialize)] pub struct MemoryRangeTable { data: Vec, } impl MemoryRangeTable { - pub fn from_bitmap(bitmap: Vec, start_addr: u64, page_size: u64) -> Self { - let mut table = MemoryRangeTable::default(); - let mut entry: Option = None; - for (i, block) in bitmap.iter().enumerate() { - for j in 0..64 { - let is_page_dirty = ((block >> j) & 1u64) != 0u64; - let page_offset = ((i * 64) + j) as u64 * page_size; - if is_page_dirty { - if let Some(entry) = &mut entry { - entry.length += page_size; - } else { - entry = Some(MemoryRange { - gpa: start_addr + page_offset, - length: page_size, - }); - } - } else if let Some(entry) = entry.take() { - table.push(entry); - } - } - } - if let Some(entry) = entry.take() { - table.push(entry); + pub fn from_bitmap( + bitmap: impl IntoIterator, + start_addr: u64, + page_size: u64, + ) -> Self { + Self { + data: MemoryRange::dirty_ranges(bitmap, start_addr, page_size).collect(), } - - table } pub fn regions(&self) -> &[MemoryRange] { From 3d5f9a3a984d617d94b94a73d6c36646c96ada05 Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Fri, 7 Nov 2025 15:04:11 +0100 Subject: [PATCH 0286/1893] virtio-devices: mark a possible improvement This would be a good opportunity to optimize another pointless vector away, but I don't have a good way to test this at the moment. But maybe someone else gives it a shot. On-behalf-of: SAP julian.stecklina@sap.com Signed-off-by: Julian Stecklina --- virtio-devices/src/mem.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 6620a5edf4..3378498b1a 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -391,6 +391,8 @@ impl BlocksState { } } + // TODO We can avoid creating a new bitmap here, if we switch the code + // to use Vec to keep dirty bits and just pass it as is. MemoryRangeTable::from_bitmap(bitmap, start_addr, VIRTIO_MEM_DEFAULT_BLOCK_SIZE) } } From fc99e299c3e93293c4f7d867d135c1ac2e60a48f Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Fri, 7 Nov 2025 15:05:24 +0100 Subject: [PATCH 0287/1893] virtio-devices: avoid creating a temporary vector ... by passing the slice along instead. On-behalf-of: SAP julian.stecklina@sap.com Signed-off-by: Julian Stecklina --- virtio-devices/src/vhost_user/vu_common_ctrl.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index f1255c7f7a..b61b2aad3c 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -570,12 +570,16 @@ impl VhostUserHandle { // divide it by 8. let len = region.size() / 8; // SAFETY: region is of size len - let bitmap = unsafe { + let bitmap: &[u64] = unsafe { // Cast the pointer to u64 let ptr = region.as_ptr() as *const u64; - std::slice::from_raw_parts(ptr, len).to_vec() + std::slice::from_raw_parts(ptr, len) }; - Ok(MemoryRangeTable::from_bitmap(bitmap, 0, 4096)) + Ok(MemoryRangeTable::from_bitmap( + bitmap.iter().copied(), + 0, + 4096, + )) } else { Err(Error::MissingShmLogRegion) } From b6c266c8809e86acd5480e9c29ddd38b7fe5a7ab Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Fri, 7 Nov 2025 15:06:05 +0100 Subject: [PATCH 0288/1893] vmm: avoid creating large temporary vector during migration ... by just passing the iterator along. For large VMs this bitmap is gigantic. A 12TB VM has 384MB of dirty bitmap. With all these optimizations from the previous commits in place, we see quite the improvement when it comes to scanning the dirty bitmap. For a bitmap with 1% bits (randomly) set, dirty_log() takes: Original code: 2166ms (100.0%) New code: 382ms ( 17.6%) on my system. The sparser the dirty bitmap the faster. Scanning an empty bitmap is 100x faster. For a 5% populated bitmap we are still 3x faster. If someone wants to play with this, there is a benchmark harness here: https://github.com/blitz/chv-bitmap-bench On-behalf-of: SAP julian.stecklina@sap.com Signed-off-by: Julian Stecklina --- vmm/src/memory_manager.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 9a9b7f23e6..e3b08adf73 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -2600,11 +2600,10 @@ impl Migratable for MemoryManager { } }; - let dirty_bitmap: Vec = vm_dirty_bitmap + let dirty_bitmap = vm_dirty_bitmap .iter() .zip(vmm_dirty_bitmap.iter()) - .map(|(x, y)| x | y) - .collect(); + .map(|(x, y)| x | y); let sub_table = MemoryRangeTable::from_bitmap(dirty_bitmap, r.gpa, 4096); From a7fa3a0c866fb48a06b7446125f31e6becb2198d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 12 Nov 2025 11:18:35 +0100 Subject: [PATCH 0289/1893] vm-migration: better naming + unittests Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- virtio-devices/src/mem.rs | 2 +- .../src/vhost_user/vu_common_ctrl.rs | 2 +- vm-migration/src/protocol.rs | 68 +++++++++++++++---- vmm/src/memory_manager.rs | 2 +- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 3378498b1a..51bb41ba00 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -393,7 +393,7 @@ impl BlocksState { // TODO We can avoid creating a new bitmap here, if we switch the code // to use Vec to keep dirty bits and just pass it as is. - MemoryRangeTable::from_bitmap(bitmap, start_addr, VIRTIO_MEM_DEFAULT_BLOCK_SIZE) + MemoryRangeTable::from_dirty_bitmap(bitmap, start_addr, VIRTIO_MEM_DEFAULT_BLOCK_SIZE) } } diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index b61b2aad3c..5f81bc2471 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -575,7 +575,7 @@ impl VhostUserHandle { let ptr = region.as_ptr() as *const u64; std::slice::from_raw_parts(ptr, len) }; - Ok(MemoryRangeTable::from_bitmap( + Ok(MemoryRangeTable::from_dirty_bitmap( bitmap.iter().copied(), 0, 4096, diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index 8c320c68e6..b66c9a7ada 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -207,19 +207,28 @@ impl Response { } #[repr(C)] -#[derive(Clone, Default, Serialize, Deserialize)] +#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MemoryRange { pub gpa: u64, pub length: u64, } -impl MemoryRange { - /// Turn an iterator over the dirty bitmap into an iterator of dirty ranges. - pub fn dirty_ranges( +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct MemoryRangeTable { + data: Vec, +} + +impl MemoryRangeTable { + /// Converts an iterator over a dirty bitmap into an iterator of dirty + /// [`MemoryRange`]s, merging consecutive dirty pages into contiguous ranges. + /// + /// A memory page (i.e., a range) is marked dirty when its corresponding bit + /// is set. + fn dirty_ranges_iter( bitmap: impl IntoIterator, start_addr: u64, page_size: u64, - ) -> impl Iterator { + ) -> impl Iterator { bitmap .into_iter() .bit_positions() @@ -233,26 +242,23 @@ impl MemoryRange { Err((prev, curr)) } }) - .map(move |r| Self { + .map(move |r| MemoryRange { gpa: start_addr + r.start * page_size, length: (r.end - r.start) * page_size, }) } -} -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct MemoryRangeTable { - data: Vec, -} - -impl MemoryRangeTable { - pub fn from_bitmap( + /// Creates a new [`MemoryRangeTable`] from a bitmap (represented as + /// multiple `u64`) where each bit corresponds to a dirty memory page. + /// + /// Only dirty ranges are represented in the resulting bitmap. + pub fn from_dirty_bitmap( bitmap: impl IntoIterator, start_addr: u64, page_size: u64, ) -> Self { Self { - data: MemoryRange::dirty_ranges(bitmap, start_addr, page_size).collect(), + data: Self::dirty_ranges_iter(bitmap, start_addr, page_size).collect(), } } @@ -312,3 +318,35 @@ impl MemoryRangeTable { Self { data } } } + +#[cfg(test)] +mod tests { + use crate::protocol::{MemoryRange, MemoryRangeTable}; + + #[test] + fn test_memory_range_table_from_dirty_ranges_iter() { + let input = [0b1111_1110_1110, 0b1_0000]; + + let start_gpa = 0x1000; + let page_size = 0x1000; + + let range = MemoryRangeTable::from_dirty_bitmap(input, start_gpa, page_size); + assert_eq!( + range.regions(), + &[ + MemoryRange { + gpa: start_gpa + page_size, + length: page_size * 3, + }, + MemoryRange { + gpa: start_gpa + 5 * page_size, + length: page_size * 7, + }, + MemoryRange { + gpa: start_gpa + (64 + 4) * page_size, + length: page_size, + } + ] + ); + } +} diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index e3b08adf73..db00f4d611 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -2605,7 +2605,7 @@ impl Migratable for MemoryManager { .zip(vmm_dirty_bitmap.iter()) .map(|(x, y)| x | y); - let sub_table = MemoryRangeTable::from_bitmap(dirty_bitmap, r.gpa, 4096); + let sub_table = MemoryRangeTable::from_dirty_bitmap(dirty_bitmap, r.gpa, 4096); if sub_table.regions().is_empty() { info!("Dirty Memory Range Table is empty"); From e4fd066d8276f7acd56f7888e80acfa9b924cce3 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 17 Nov 2025 15:42:20 +0100 Subject: [PATCH 0290/1893] misc: improve developer experience of cargo clippy A major improvement to the developer experience of clippy in Cloud Hypervisor. 1. Make `cargo clippy` just work with the same lints we use in CI 2. Simplify adding new lints Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- .github/workflows/build.yaml | 24 +++++++++---------- .../workflows/preview-riscv64-modules.yaml | 4 ++-- .github/workflows/quality.yaml | 24 +++++++++---------- Cargo.toml | 9 +++++++ api_client/Cargo.toml | 3 +++ arch/Cargo.toml | 3 +++ block/Cargo.toml | 3 +++ devices/Cargo.toml | 3 +++ event_monitor/Cargo.toml | 3 +++ hypervisor/Cargo.toml | 3 +++ net_gen/Cargo.toml | 3 +++ net_util/Cargo.toml | 3 +++ option_parser/Cargo.toml | 3 +++ pci/Cargo.toml | 3 +++ performance-metrics/Cargo.toml | 3 +++ rate_limiter/Cargo.toml | 3 +++ serial_buffer/Cargo.toml | 3 +++ test_infra/Cargo.toml | 3 +++ tpm/Cargo.toml | 3 +++ tracer/Cargo.toml | 3 +++ vhost_user_block/Cargo.toml | 3 +++ vhost_user_net/Cargo.toml | 3 +++ virtio-devices/Cargo.toml | 3 +++ vm-allocator/Cargo.toml | 3 +++ vm-device/Cargo.toml | 3 +++ vm-migration/Cargo.toml | 3 +++ vm-virtio/Cargo.toml | 3 +++ vmm/Cargo.toml | 3 +++ 28 files changed, 107 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0a5cef9abb..2149f2e881 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -35,40 +35,40 @@ jobs: target: ${{ matrix.target }} - name: Build (default features) - run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings - name: Build (kvm) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings - name: Build (default features + tdx) - run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings - name: Build (default features + dbus_api) - run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings - name: Build (default features + guest_debug) - run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings - name: Build (default features + pvmemcontrol) - run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings - name: Build (default features + fw_cfg) - run: cargo rustc --locked --bin cloud-hypervisor --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --features "fw_cfg" -- -D warnings - name: Build (default features + ivshmem) - run: cargo rustc --locked --bin cloud-hypervisor --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --features "ivshmem" -- -D warnings - name: Build (mshv) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings - name: Build (sev_snp) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings - name: Build (igvm) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings - name: Build (mshv + kvm) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings - name: Release Build (default features) run: cargo build --locked --all --release --target=${{ matrix.target }} diff --git a/.github/workflows/preview-riscv64-modules.yaml b/.github/workflows/preview-riscv64-modules.yaml index f06b96286c..8c2baa0492 100644 --- a/.github/workflows/preview-riscv64-modules.yaml +++ b/.github/workflows/preview-riscv64-modules.yaml @@ -27,10 +27,10 @@ jobs: 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 rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings - 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + 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" diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index f8d5ca41fa..8bc37938e6 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -56,7 +56,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings - name: Clippy (mshv) uses: houseabsolute/actions-rust-cross@v1 @@ -65,7 +65,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings - name: Clippy (mshv + kvm) uses: houseabsolute/actions-rust-cross@v1 @@ -74,7 +74,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + 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 @@ -83,7 +83,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + args: --locked --all --all-targets --tests --examples -- -D warnings - name: Clippy (default features + guest_debug) uses: houseabsolute/actions-rust-cross@v1 @@ -92,7 +92,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings - name: Clippy (default features + pvmemcontrol) uses: houseabsolute/actions-rust-cross@v1 @@ -101,7 +101,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings - name: Clippy (default features + tracing) uses: houseabsolute/actions-rust-cross@v1 @@ -110,13 +110,13 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + 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 @@ -125,7 +125,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings - name: Clippy (sev_snp) if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} @@ -135,7 +135,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + 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' }} @@ -145,7 +145,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + 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' }} @@ -155,7 +155,7 @@ jobs: cross-version: 3e0957637b49b1bbced23ad909170650c5b70635 toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states + 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)" diff --git a/Cargo.toml b/Cargo.toml index e1d2f158bd..842a39d88f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,9 @@ sev_snp = ["igvm", "mshv", "vmm/sev_snp"] tdx = ["vmm/tdx"] tracing = ["tracer/tracing", "vmm/tracing"] +[lints] +workspace = true + [workspace] members = [ "api_client", @@ -153,3 +156,9 @@ thiserror = "2.0.17" uuid = { version = "1.18.1" } wait-timeout = "0.2.1" zerocopy = { version = "0.8.27", default-features = false } + +[workspace.lints.clippy] +# Any clippy lint in alphabetical order, including lint groups: +# https://rust-lang.github.io/rust-clippy/master/index.html +assertions_on_result_states = "deny" +undocumented_unsafe_blocks = "deny" diff --git a/api_client/Cargo.toml b/api_client/Cargo.toml index 429ecbf927..b8791dfc3d 100644 --- a/api_client/Cargo.toml +++ b/api_client/Cargo.toml @@ -7,3 +7,6 @@ version = "0.1.0" [dependencies] thiserror = { workspace = true } vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/arch/Cargo.toml b/arch/Cargo.toml index 3bd32affb1..804be793d0 100644 --- a/arch/Cargo.toml +++ b/arch/Cargo.toml @@ -27,3 +27,6 @@ vmm-sys-util = { workspace = true, features = ["with-serde"] } [target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies] fdt_parser = { version = "0.1.5", package = "fdt" } vm-fdt = { workspace = true } + +[lints] +workspace = true diff --git a/block/Cargo.toml b/block/Cargo.toml index db4ac9a6b3..9823c1f818 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -28,3 +28,6 @@ vm-memory = { workspace = true, features = [ ] } vm-virtio = { path = "../vm-virtio" } vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 201d5284fc..f635e66a49 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -48,3 +48,6 @@ fw_cfg = ["arch/fw_cfg", "bitfield-struct", "linux-loader", "zerocopy"] ivshmem = [] kvm = ["arch/kvm"] pvmemcontrol = [] + +[lints] +workspace = true diff --git a/event_monitor/Cargo.toml b/event_monitor/Cargo.toml index b2b7a4e48d..41d3102807 100644 --- a/event_monitor/Cargo.toml +++ b/event_monitor/Cargo.toml @@ -9,3 +9,6 @@ flume = { workspace = true } libc = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } + +[lints] +workspace = true diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 93343c931f..993d18583d 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -63,3 +63,6 @@ version = "1.21.0" [dev-dependencies] env_logger = { workspace = true } + +[lints] +workspace = true diff --git a/net_gen/Cargo.toml b/net_gen/Cargo.toml index b1443c1f29..a99c7c995d 100644 --- a/net_gen/Cargo.toml +++ b/net_gen/Cargo.toml @@ -7,3 +7,6 @@ version = "0.1.0" [dependencies] vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml index 8f5df72225..a55db49f8a 100644 --- a/net_util/Cargo.toml +++ b/net_util/Cargo.toml @@ -27,3 +27,6 @@ vmm-sys-util = { workspace = true } pnet = "0.35.0" pnet_datalink = "0.35.0" serde_json = { workspace = true } + +[lints] +workspace = true diff --git a/option_parser/Cargo.toml b/option_parser/Cargo.toml index abacf51ddd..3d76690b41 100644 --- a/option_parser/Cargo.toml +++ b/option_parser/Cargo.toml @@ -6,3 +6,6 @@ version = "0.1.0" [dependencies] thiserror = { workspace = true } + +[lints] +workspace = true diff --git a/pci/Cargo.toml b/pci/Cargo.toml index e1d631c348..760baae03d 100644 --- a/pci/Cargo.toml +++ b/pci/Cargo.toml @@ -29,3 +29,6 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index 00e6e74682..531c6abe99 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -12,3 +12,6 @@ serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } test_infra = { path = "../test_infra" } thiserror = { workspace = true } + +[lints] +workspace = true diff --git a/rate_limiter/Cargo.toml b/rate_limiter/Cargo.toml index 3067c695bb..206ec7b7f8 100644 --- a/rate_limiter/Cargo.toml +++ b/rate_limiter/Cargo.toml @@ -9,3 +9,6 @@ libc = { workspace = true } log = { workspace = true } thiserror = { workspace = true } vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/serial_buffer/Cargo.toml b/serial_buffer/Cargo.toml index 0691b8a3b7..767c8a97ff 100644 --- a/serial_buffer/Cargo.toml +++ b/serial_buffer/Cargo.toml @@ -3,3 +3,6 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "serial_buffer" version = "0.1.0" + +[lints] +workspace = true diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index 8fdefed15a..e6ea592c39 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -13,3 +13,6 @@ ssh2 = { version = "0.9.5", features = ["vendored-openssl"] } thiserror = { workspace = true } vmm-sys-util = { workspace = true } wait-timeout = { workspace = true } + +[lints] +workspace = true diff --git a/tpm/Cargo.toml b/tpm/Cargo.toml index cf03968cde..82dc8f79be 100644 --- a/tpm/Cargo.toml +++ b/tpm/Cargo.toml @@ -12,3 +12,6 @@ log = { workspace = true } net_gen = { path = "../net_gen" } thiserror = { workspace = true } vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/tracer/Cargo.toml b/tracer/Cargo.toml index bdcf559695..1ac9f4e393 100644 --- a/tracer/Cargo.toml +++ b/tracer/Cargo.toml @@ -12,3 +12,6 @@ serde_json = { workspace = true } [features] tracing = [] + +[lints] +workspace = true diff --git a/vhost_user_block/Cargo.toml b/vhost_user_block/Cargo.toml index c2e7385668..e674c96eac 100644 --- a/vhost_user_block/Cargo.toml +++ b/vhost_user_block/Cargo.toml @@ -19,3 +19,6 @@ virtio-bindings = { workspace = true } virtio-queue = { workspace = true } vm-memory = { workspace = true } vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/vhost_user_net/Cargo.toml b/vhost_user_net/Cargo.toml index 6cd316e9fe..849ad5426e 100644 --- a/vhost_user_net/Cargo.toml +++ b/vhost_user_net/Cargo.toml @@ -19,3 +19,6 @@ vhost-user-backend = { workspace = true } virtio-bindings = { workspace = true } vm-memory = { workspace = true } vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index 64a60910ca..5cbfe145f4 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -47,3 +47,6 @@ vm-memory = { workspace = true, features = [ vm-migration = { path = "../vm-migration" } vm-virtio = { path = "../vm-virtio" } vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/vm-allocator/Cargo.toml b/vm-allocator/Cargo.toml index e77e877917..a4996d6dc3 100644 --- a/vm-allocator/Cargo.toml +++ b/vm-allocator/Cargo.toml @@ -14,3 +14,6 @@ vm-memory = { workspace = true } [target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies] arch = { path = "../arch" } + +[lints] +workspace = true diff --git a/vm-device/Cargo.toml b/vm-device/Cargo.toml index 80ed1489a3..a57ea57f5b 100644 --- a/vm-device/Cargo.toml +++ b/vm-device/Cargo.toml @@ -16,3 +16,6 @@ thiserror = { workspace = true } vfio-ioctls = { workspace = true, default-features = false } vm-memory = { workspace = true, features = ["backend-mmap"] } vmm-sys-util = { workspace = true } + +[lints] +workspace = true diff --git a/vm-migration/Cargo.toml b/vm-migration/Cargo.toml index 69d57076f5..b17475065c 100644 --- a/vm-migration/Cargo.toml +++ b/vm-migration/Cargo.toml @@ -11,3 +11,6 @@ serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } thiserror = { workspace = true } vm-memory = { workspace = true, features = ["backend-atomic", "backend-mmap"] } + +[lints] +workspace = true diff --git a/vm-virtio/Cargo.toml b/vm-virtio/Cargo.toml index 5f195af492..228f552416 100644 --- a/vm-virtio/Cargo.toml +++ b/vm-virtio/Cargo.toml @@ -14,3 +14,6 @@ vm-memory = { workspace = true, features = [ "backend-bitmap", "backend-mmap", ] } + +[lints] +workspace = true diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index b7035f030c..37434f215c 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -93,3 +93,6 @@ vm-virtio = { path = "../vm-virtio" } vmm-sys-util = { workspace = true, features = ["with-serde"] } zbus = { version = "5.11.0", optional = true } zerocopy = { workspace = true, features = ["alloc", "derive"] } + +[lints] +workspace = true From 935332bc425665196e09200a9f078a764ced676f Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 17 Nov 2025 15:46:40 +0100 Subject: [PATCH 0291/1893] misc: unrelated misc code improvements Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 9137ba1805..d0a538b6da 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1815,12 +1815,9 @@ impl RequestHandler for Vmm { self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; if let Some(ref mut vm) = self.vm { - if let Err(e) = vm.resize(desired_vcpus, desired_ram, desired_balloon) { - error!("Error when resizing VM: {e:?}"); - Err(e) - } else { - Ok(()) - } + vm.resize(desired_vcpus, desired_ram, desired_balloon) + .inspect_err(|e| error!("Error when resizing VM: {e:?}"))?; + Ok(()) } else { let mut config = self.vm_config.as_ref().unwrap().lock().unwrap(); if let Some(desired_vcpus) = desired_vcpus { @@ -1842,12 +1839,9 @@ impl RequestHandler for Vmm { self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; if let Some(ref mut vm) = self.vm { - if let Err(e) = vm.resize_zone(id, desired_ram) { - error!("Error when resizing VM: {e:?}"); - Err(e) - } else { - Ok(()) - } + vm.resize_zone(id, desired_ram) + .inspect_err(|e| error!("Error when resizing zone: {e:?}"))?; + Ok(()) } else { // Update VmConfig by setting the new desired ram. let memory_config = &mut self.vm_config.as_ref().unwrap().lock().unwrap().memory; @@ -1926,12 +1920,9 @@ impl RequestHandler for Vmm { fn vm_remove_device(&mut self, id: String) -> result::Result<(), VmError> { if let Some(ref mut vm) = self.vm { - if let Err(e) = vm.remove_device(id) { - error!("Error when removing device from the VM: {e:?}"); - Err(e) - } else { - Ok(()) - } + vm.remove_device(id) + .inspect_err(|e| error!("Error when removing device from the VM: {e:?}"))?; + Ok(()) } else if let Some(ref config) = self.vm_config { let mut config = config.lock().unwrap(); if config.remove_device(&id) { From 8ee26286ac647588e391da15bb5f4930eb4ff3cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:39:36 +0000 Subject: [PATCH 0292/1893] build: Bump crate-ci/typos from 1.39.0 to 1.39.2 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.39.0 to 1.39.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.39.0...v1.39.2) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.39.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 8bc37938e6..e60f3dfb5f 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v5 # Executes "typos ." - - uses: crate-ci/typos@v1.39.0 + - uses: crate-ci/typos@v1.39.2 From be495ec64affe7211961d6a04088f57935e7618c Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Mon, 17 Nov 2025 10:31:01 -0800 Subject: [PATCH 0293/1893] arch: x86_64: fix cpuid leaf 0x1 EBX bits 23-16 Commit 5ec47d488366 was intended to patch ebx bits 23-16 in cpuid leaf 0x1, but it was not working as expected, as in rust, operator << has a stronger precedence than & [1]. Later commit b6667f948ed2 fixed the operator precedence clippy warning, but did not fix the actual issue. As a result, the current code is not changing ebx, ``` cpu_ebx |= ((dies_per_package as u32) * (cores_per_die as u32) * (threads_per_core as u32)) & (0xff << 16); ``` Since the total number of logical processors is generally less than 65536, the right hand side of the expression is 0 in most cases. [1] https://doc.rust-lang.org/reference/expressions.html#expression-precedence Fixes: 5ec47d488366 ("arch: x86_64: enable HTT flag") Signed-off-by: Changyuan Lyu --- arch/src/x86_64/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 5d17065c81..9a02ed2963 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -1303,9 +1303,10 @@ fn update_cpuid_topology( let die_width = u16::BITS - (dies_per_package - 1).leading_zeros() + core_width; // The very old way: a flat number of logical CPUs per package: CPUID.1H:EBX[23:16] bits. + let core_count = dies_per_package as u32 * cores_per_die as u32 * threads_per_core as u32; let mut cpu_ebx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX).unwrap_or(0); - cpu_ebx |= ((dies_per_package as u32) * (cores_per_die as u32) * (threads_per_core as u32)) - & (0xff << 16); + cpu_ebx &= !(0xff << 16); + cpu_ebx |= (core_count & 0xff) << 16; CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx); let mut cpu_edx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX).unwrap_or(0); From e3e9e1c84cc6bca6e3d87199b358f951cf3419a9 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 20 Nov 2025 10:37:28 -0800 Subject: [PATCH 0294/1893] tests: Add retries for artifact downloads This change makes integration tests more resilient to transient download failures. This will reduce churn in our CI workflow, specifically for the Merge Queue. Examples: https://github.com/cloud-hypervisor/cloud-hypervisor/actions/runs/19545345066/job/55962570896 https://github.com/cloud-hypervisor/cloud-hypervisor/actions/runs/19545345122/job/55962570736 https://github.com/cloud-hypervisor/cloud-hypervisor/actions/runs/19545345034/job/55962570724 Signed-off-by: Bo Chen --- scripts/test-util.sh | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/scripts/test-util.sh b/scripts/test-util.sh index ede00b7255..04d79371b4 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -5,6 +5,28 @@ hypervisor="kvm" test_filter="" build_kernel=false +# Download from a url with retries +# Args: +# $1: URL +# $2: Maximum number of retries (optional), default 3 +# $3: Delays between retries in seconds (optional), default 5 +download_with_retries() { + local URL="$1" + local MAX_RETRIES="${2:-3}" + local RETRY_DELAY="${3:-5}" + local i + + for i in $(seq 1 "$MAX_RETRIES"); do + echo "Attempt $i/$MAX_RETRIES: downloading $url" + time wget -N --quiet "$URL" && return 0 + echo "Warning: wget failed on attempt $i. Retrying in ${RETRY_DELAY}s..." >&2 + sleep "$RETRY_DELAY" + done + + echo "ERROR: Failed to download $url after $MAX_RETRIES attempts." >&2 + return 1 +} + # Checkout source code of a GIT repo with specified branch and commit # Args: # $1: Target directory @@ -135,7 +157,7 @@ download_hypervisor_fw() { FW="$WORKLOADS_DIR/hypervisor-fw" pushd "$WORKLOADS_DIR" || exit rm -f "$FW" - time wget --quiet "$FW_URL" || exit 1 + download_with_retries "$FW_URL" || exit 1 popd || exit } @@ -152,7 +174,7 @@ download_linux() { fi pushd "$WORKLOADS_DIR" || exit for url in $KERNEL_URLS; do - wget -N --quiet "$url" || exit 1 + download_with_retries "$url" || exit 1 done popd || exit @@ -176,7 +198,7 @@ download_ovmf() { OVMF_FW="$WORKLOADS_DIR/CLOUDHV.fd" pushd "$WORKLOADS_DIR" || exit rm -f "$OVMF_FW" - time wget --quiet $OVMF_FW_URL || exit 1 + download_with_retries $OVMF_FW_URL || exit 1 popd || exit } From c990f1bdaaf61594d5c439c98444045113370925 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 07:20:58 +0100 Subject: [PATCH 0295/1893] tests: enable `cargo test --workspace` + `#[cfg(devcli_testenv)]` TL;DR: Massive quality of life improvement for devs Cloud Hypervisor uses the Cargo test framework for multiple tests: - normal unit tests - unit tests requiring special environment (the Tap device tests) - integration tests requiring a special environment This prevented the execution of `cargo test --workspace`, which results in a very poor developer experience. Although `./scripts/run_unit_tests.sh` exists, there are valid reasons why devs cannot or even don't want to use it. By adding a new `chv_testenv` rustc config, we can conditionally only activate tests when the `./scripts/` magic runs them. This improves the general developer experience by a lot. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 4 ++++ net_util/src/tap.rs | 1 + scripts/dev_cli.sh | 3 +++ scripts/run_integration_tests_aarch64.sh | 2 ++ scripts/run_integration_tests_live_migration.sh | 3 +++ scripts/run_integration_tests_rate_limiter.sh | 3 +++ scripts/run_integration_tests_vfio.sh | 3 +++ scripts/run_integration_tests_windows_aarch64.sh | 2 ++ scripts/run_integration_tests_windows_x86_64.sh | 2 ++ scripts/run_integration_tests_x86_64.sh | 7 +++---- tests/integration.rs | 1 + 11 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 842a39d88f..fa15483fe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -162,3 +162,7 @@ zerocopy = { version = "0.8.27", default-features = false } # https://rust-lang.github.io/rust-clippy/master/index.html assertions_on_result_states = "deny" undocumented_unsafe_blocks = "deny" + +[workspace.lints.rust] +# `level = warn` is irrelevant here but mandatory for rustc/cargo +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(devcli_testenv)'] } diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 2916e66cf6..45d6a77de8 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -549,6 +549,7 @@ impl AsRawFd for Tap { } #[cfg(test)] +#[cfg(devcli_testenv)] // we need special permissions in the ENV to create Tap devices mod tests { use std::net::Ipv4Addr; use std::sync::{LazyLock, Mutex, mpsc}; diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index ee95b99b37..d055ed7438 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -47,6 +47,9 @@ CARGO_GIT_REGISTRY_DIR="${CLH_BUILD_DIR}/cargo_git_registry" # Full path to the cargo target dir on the host. CARGO_TARGET_DIR="${CLH_BUILD_DIR}/cargo_target" +# Let tests know that the special environment is set up. +RUSTFLAGS="${RUSTFLAGS} --cfg devcli_testenv" + # Send a decorated message to stdout, followed by a new line # say() { diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index af6b4773c1..bd1e3954d1 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -191,7 +191,9 @@ if [ $RES -ne 0 ]; then exit 1 fi +# Common configuration for every test run export RUST_BACKTRACE=1 +export RUSTFLAGS="$RUSTFLAGS" cargo build --features mshv --all --release --target "$BUILD_TARGET" diff --git a/scripts/run_integration_tests_live_migration.sh b/scripts/run_integration_tests_live_migration.sh index 38ddd9e380..9d4af8453d 100755 --- a/scripts/run_integration_tests_live_migration.sh +++ b/scripts/run_integration_tests_live_migration.sh @@ -83,7 +83,10 @@ PAGE_NUM=$((12288 * 1024 / HUGEPAGESIZE)) echo "$PAGE_NUM" | sudo tee /proc/sys/vm/nr_hugepages sudo chmod a+rwX /dev/hugepages +# Common configuration for every test run export RUST_BACKTRACE=1 +export RUSTFLAGS="$RUSTFLAGS" + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? diff --git a/scripts/run_integration_tests_rate_limiter.sh b/scripts/run_integration_tests_rate_limiter.sh index 1ae8975943..9582ddcb2d 100755 --- a/scripts/run_integration_tests_rate_limiter.sh +++ b/scripts/run_integration_tests_rate_limiter.sh @@ -55,7 +55,10 @@ fi cargo build --features mshv --all --release --target "$BUILD_TARGET" +# Common configuration for every test run export RUST_BACKTRACE=1 +export RUSTFLAGS="$RUSTFLAGS" + time cargo nextest run $test_features --test-threads=1 "rate_limiter::$test_filter" -- ${test_binary_args[*]} RES=$? diff --git a/scripts/run_integration_tests_vfio.sh b/scripts/run_integration_tests_vfio.sh index 1f36f9580e..a88f92aad1 100755 --- a/scripts/run_integration_tests_vfio.sh +++ b/scripts/run_integration_tests_vfio.sh @@ -26,7 +26,10 @@ fi cargo build --features mshv --all --release --target "$BUILD_TARGET" +# Common configuration for every test run export RUST_BACKTRACE=1 +export RUSTFLAGS="$RUSTFLAGS" + time cargo nextest run --test-threads=1 "vfio::test_nvidia" -- ${test_binary_args[*]} RES=$? diff --git a/scripts/run_integration_tests_windows_aarch64.sh b/scripts/run_integration_tests_windows_aarch64.sh index a4dcda7a6e..7112609e6e 100755 --- a/scripts/run_integration_tests_windows_aarch64.sh +++ b/scripts/run_integration_tests_windows_aarch64.sh @@ -36,7 +36,9 @@ dmsetup mknodes dmsetup create windows-snapshot-base --table "0 $img_blk_size snapshot-origin /dev/mapper/windows-base" dmsetup mknodes +# Common configuration for every test run export RUST_BACKTRACE=1 +export RUSTFLAGS="$RUSTFLAGS" cargo build --all --release --target "$BUILD_TARGET" diff --git a/scripts/run_integration_tests_windows_x86_64.sh b/scripts/run_integration_tests_windows_x86_64.sh index 9708038295..c95e513059 100755 --- a/scripts/run_integration_tests_windows_x86_64.sh +++ b/scripts/run_integration_tests_windows_x86_64.sh @@ -41,7 +41,9 @@ dmsetup mknodes cargo build --features mshv --all --release --target "$BUILD_TARGET" +# Common configuration for every test run export RUST_BACKTRACE=1 +export RUSTFLAGS="$RUSTFLAGS" # Only run with 1 thread to avoid tests interfering with one another because # Windows has a static IP configured diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 952f7e8cd2..6f89cf865a 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -177,14 +177,16 @@ ulimit -l unlimited # Set number of open descriptors high enough for VFIO tests to run ulimit -n 4096 +# Common configuration for every test run export RUST_BACKTRACE=1 +export RUSTFLAGS="$RUSTFLAGS" + time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "common_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? # Run some tests in sequence since the result could be affected by other tests # running in parallel. if [ $RES -eq 0 ]; then - export RUST_BACKTRACE=1 cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=1 "common_sequential::$test_filter" -- ${test_binary_args[*]} RES=$? fi @@ -192,7 +194,6 @@ fi # Run tests on dbus_api if [ $RES -eq 0 ]; then cargo build --features "mshv,dbus_api" --all --release --target "$BUILD_TARGET" - export RUST_BACKTRACE=1 # integration tests now do not reply on build feature "dbus_api" time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} RES=$? @@ -201,14 +202,12 @@ fi # Run tests on fw_cfg if [ $RES -eq 0 ]; then cargo build --features "mshv,fw_cfg" --all --release --target "$BUILD_TARGET" - export RUST_BACKTRACE=1 time cargo nextest run $test_features --no-tests=warn --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} RES=$? fi if [ $RES -eq 0 ]; then cargo build --features "mshv,ivshmem" --all --release --target "$BUILD_TARGET" - export RUST_BACKTRACE=1 time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} RES=$? fi diff --git a/tests/integration.rs b/tests/integration.rs index 14ace3850d..72d6aed565 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 // +#![cfg(devcli_testenv)] #![allow(clippy::undocumented_unsafe_blocks)] // When enabling the `mshv` feature, we skip quite some tests and // hence have known dead-code. This annotation silences dead-code From d1680b9ff9d1a861ebcc646d1c3abf8bb1948fcb Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 07:24:05 +0100 Subject: [PATCH 0296/1893] tests: streamline module names to unit_tests This better aligns with the rest of the code and makes it clearer that these tests can run "as is" in a normal hosted environments without the special test environment. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- arch/src/aarch64/mod.rs | 2 +- arch/src/riscv64/mod.rs | 2 +- arch/src/x86_64/mod.rs | 2 +- arch/src/x86_64/mptable.rs | 2 +- arch/src/x86_64/regs.rs | 2 +- arch/src/x86_64/smbios.rs | 2 +- arch/src/x86_64/tdx/mod.rs | 2 +- block/src/qcow/mod.rs | 2 +- block/src/qcow/vec_cache.rs | 2 +- block/src/vhd.rs | 2 +- devices/src/legacy/fw_cfg.rs | 2 +- devices/src/legacy/gpio_pl061.rs | 2 +- devices/src/legacy/rtc_pl031.rs | 2 +- devices/src/legacy/serial.rs | 2 +- devices/src/legacy/uart_pl011.rs | 2 +- devices/src/tpm.rs | 2 +- fuzz/fuzz_targets/vsock.rs | 2 +- hypervisor/src/arch/x86/emulator/instructions/cmp.rs | 2 +- hypervisor/src/arch/x86/emulator/instructions/mov.rs | 2 +- hypervisor/src/arch/x86/emulator/instructions/movs.rs | 2 +- hypervisor/src/arch/x86/emulator/instructions/or.rs | 2 +- hypervisor/src/arch/x86/emulator/instructions/stos.rs | 2 +- hypervisor/src/arch/x86/emulator/mod.rs | 2 +- hypervisor/src/arch/x86/gdt.rs | 2 +- hypervisor/src/kvm/aarch64/gic/mod.rs | 2 +- hypervisor/src/kvm/mod.rs | 2 +- hypervisor/src/kvm/riscv64/aia.rs | 2 +- net_util/src/lib.rs | 2 +- net_util/src/mac.rs | 2 +- net_util/src/tap.rs | 2 +- option_parser/src/lib.rs | 2 +- pci/src/configuration.rs | 2 +- performance-metrics/src/performance_tests.rs | 2 +- rate_limiter/src/group.rs | 2 +- rate_limiter/src/lib.rs | 2 +- src/bin/ch-remote.rs | 2 +- tpm/src/lib.rs | 2 +- virtio-devices/src/transport/pci_common_config.rs | 2 +- virtio-devices/src/vsock/csm/connection.rs | 4 ++-- virtio-devices/src/vsock/csm/txbuf.rs | 2 +- virtio-devices/src/vsock/device.rs | 4 ++-- virtio-devices/src/vsock/mod.rs | 2 +- virtio-devices/src/vsock/packet.rs | 4 ++-- virtio-devices/src/vsock/unix/muxer.rs | 4 ++-- vm-allocator/src/address.rs | 2 +- vm-device/src/bus.rs | 2 +- vm-migration/src/bitpos_iterator.rs | 2 +- vm-migration/src/protocol.rs | 2 +- vmm/src/config.rs | 2 +- vmm/src/cpu.rs | 4 ++-- vmm/src/device_manager.rs | 2 +- vmm/src/device_tree.rs | 2 +- vmm/src/vm.rs | 4 ++-- 53 files changed, 59 insertions(+), 59 deletions(-) diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index 95c4cb3fd7..910c14998a 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -192,7 +192,7 @@ pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index b4feb99d57..0ee66db32d 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -218,7 +218,7 @@ pub fn get_host_cpu_phys_bits(_hypervisor: &dyn hypervisor::Hypervisor) -> u8 { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 9a02ed2963..91fba56c09 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -1428,7 +1428,7 @@ fn update_cpuid_topology( } } #[cfg(test)] -mod tests { +mod unit_tests { use linux_loader::loader::bootparam::boot_e820_entry; use super::*; diff --git a/arch/src/x86_64/mptable.rs b/arch/src/x86_64/mptable.rs index 2e2669b38a..a0e6dc47e2 100644 --- a/arch/src/x86_64/mptable.rs +++ b/arch/src/x86_64/mptable.rs @@ -297,7 +297,7 @@ pub fn setup_mptable( } #[cfg(test)] -mod tests { +mod unit_tests { use vm_memory::bitmap::BitmapSlice; use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile}; diff --git a/arch/src/x86_64/regs.rs b/arch/src/x86_64/regs.rs index 1993985706..6e7292f3c0 100644 --- a/arch/src/x86_64/regs.rs +++ b/arch/src/x86_64/regs.rs @@ -196,7 +196,7 @@ pub fn configure_segments_and_sregs( } #[cfg(test)] -mod tests { +mod unit_tests { use vm_memory::GuestAddress; use super::*; diff --git a/arch/src/x86_64/smbios.rs b/arch/src/x86_64/smbios.rs index 7d867a43c6..ee1499982e 100644 --- a/arch/src/x86_64/smbios.rs +++ b/arch/src/x86_64/smbios.rs @@ -272,7 +272,7 @@ pub fn setup_smbios( } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/arch/src/x86_64/tdx/mod.rs b/arch/src/x86_64/tdx/mod.rs index c68b878be4..814c16aaf0 100644 --- a/arch/src/x86_64/tdx/mod.rs +++ b/arch/src/x86_64/tdx/mod.rs @@ -519,7 +519,7 @@ impl TdHob { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index d9684a4ad2..e4f9790c95 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1815,7 +1815,7 @@ pub fn detect_image_type(file: &mut RawFile) -> Result { } #[cfg(test)] -mod tests { +mod unit_tests { use std::fs::File; use std::path::Path; diff --git a/block/src/qcow/vec_cache.rs b/block/src/qcow/vec_cache.rs index 4b18518b0e..d0c3acf086 100644 --- a/block/src/qcow/vec_cache.rs +++ b/block/src/qcow/vec_cache.rs @@ -135,7 +135,7 @@ impl CacheMap { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; struct NumCache(()); diff --git a/block/src/vhd.rs b/block/src/vhd.rs index 6659ed0385..5a8e59de39 100644 --- a/block/src/vhd.rs +++ b/block/src/vhd.rs @@ -117,7 +117,7 @@ pub fn is_fixed_vhd(f: &mut File) -> std::io::Result { } #[cfg(test)] -mod tests { +mod unit_tests { use std::fs::File; use std::io::{Seek, SeekFrom, Write}; diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 00b5bd7450..1a5c6478c1 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -798,7 +798,7 @@ impl BusDevice for FwCfg { } #[cfg(test)] -mod tests { +mod unit_tests { use std::ffi::CString; use std::io::Write; diff --git a/devices/src/legacy/gpio_pl061.rs b/devices/src/legacy/gpio_pl061.rs index 3a61238dd2..a5ec570b8f 100644 --- a/devices/src/legacy/gpio_pl061.rs +++ b/devices/src/legacy/gpio_pl061.rs @@ -323,7 +323,7 @@ impl Transportable for Gpio {} impl Migratable for Gpio {} #[cfg(test)] -mod tests { +mod unit_tests { use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig}; use vmm_sys_util::eventfd::EventFd; diff --git a/devices/src/legacy/rtc_pl031.rs b/devices/src/legacy/rtc_pl031.rs index 9ff0c8c1b1..985edd5703 100644 --- a/devices/src/legacy/rtc_pl031.rs +++ b/devices/src/legacy/rtc_pl031.rs @@ -216,7 +216,7 @@ impl BusDevice for Rtc { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; use crate::{ read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16, diff --git a/devices/src/legacy/serial.rs b/devices/src/legacy/serial.rs index cbfb2c10b9..11c5769b6b 100644 --- a/devices/src/legacy/serial.rs +++ b/devices/src/legacy/serial.rs @@ -340,7 +340,7 @@ impl Transportable for Serial {} impl Migratable for Serial {} #[cfg(test)] -mod tests { +mod unit_tests { use std::sync::Mutex; use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig}; diff --git a/devices/src/legacy/uart_pl011.rs b/devices/src/legacy/uart_pl011.rs index 364dd59278..8af77d0e4f 100644 --- a/devices/src/legacy/uart_pl011.rs +++ b/devices/src/legacy/uart_pl011.rs @@ -453,7 +453,7 @@ impl Transportable for Pl011 {} impl Migratable for Pl011 {} #[cfg(test)] -mod tests { +mod unit_tests { use std::sync::Mutex; use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig}; diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs index fa8d468b8d..1c6879b1ae 100644 --- a/devices/src/tpm.rs +++ b/devices/src/tpm.rs @@ -531,7 +531,7 @@ impl BusDevice for Tpm { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/fuzz/fuzz_targets/vsock.rs b/fuzz/fuzz_targets/vsock.rs index 24763de269..144b8b4057 100644 --- a/fuzz/fuzz_targets/vsock.rs +++ b/fuzz/fuzz_targets/vsock.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use libfuzzer_sys::{fuzz_target, Corpus}; use seccompiler::SeccompAction; -use virtio_devices::vsock::tests::TestBackend; +use virtio_devices::vsock::unit_tests::TestBackend; use virtio_devices::{VirtioDevice, VirtioInterrupt, VirtioInterruptType}; use virtio_queue::{Queue, QueueT}; use vm_memory::bitmap::AtomicBitmap; diff --git a/hypervisor/src/arch/x86/emulator/instructions/cmp.rs b/hypervisor/src/arch/x86/emulator/instructions/cmp.rs index 21974a5781..4dd139c9dd 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/cmp.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/cmp.rs @@ -208,7 +208,7 @@ impl InstructionHandler for Cmp_rm64_imm8 { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; use crate::arch::x86::emulator::mock_vmm::*; diff --git a/hypervisor/src/arch/x86/emulator/instructions/mov.rs b/hypervisor/src/arch/x86/emulator/instructions/mov.rs index 98d71a1074..660462fbde 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/mov.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/mov.rs @@ -268,7 +268,7 @@ impl InstructionHandler for Mov_RAX_moffs64 { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; use crate::arch::x86::emulator::mock_vmm::*; diff --git a/hypervisor/src/arch/x86/emulator/instructions/movs.rs b/hypervisor/src/arch/x86/emulator/instructions/movs.rs index 039172e8ae..5642e04f4f 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/movs.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/movs.rs @@ -104,7 +104,7 @@ impl InstructionHandler for Movsb_m8_m8 { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; use crate::arch::x86::emulator::mock_vmm::*; diff --git a/hypervisor/src/arch/x86/emulator/instructions/or.rs b/hypervisor/src/arch/x86/emulator/instructions/or.rs index 0e5fbc02a3..903c64be41 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/or.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/or.rs @@ -49,7 +49,7 @@ impl InstructionHandler for Or_rm8_r8 { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; use crate::arch::x86::emulator::mock_vmm::*; diff --git a/hypervisor/src/arch/x86/emulator/instructions/stos.rs b/hypervisor/src/arch/x86/emulator/instructions/stos.rs index b959ff4646..38233eea4f 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/stos.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/stos.rs @@ -90,7 +90,7 @@ impl InstructionHandler for Stosb_m8_AL { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; use crate::arch::x86::emulator::mock_vmm::*; diff --git a/hypervisor/src/arch/x86/emulator/mod.rs b/hypervisor/src/arch/x86/emulator/mod.rs index 778fd47f57..15239a4f8f 100644 --- a/hypervisor/src/arch/x86/emulator/mod.rs +++ b/hypervisor/src/arch/x86/emulator/mod.rs @@ -786,7 +786,7 @@ mod mock_vmm { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; use crate::arch::x86::emulator::mock_vmm::*; diff --git a/hypervisor/src/arch/x86/gdt.rs b/hypervisor/src/arch/x86/gdt.rs index 88eeb350d0..6d0a81c8bf 100644 --- a/hypervisor/src/arch/x86/gdt.rs +++ b/hypervisor/src/arch/x86/gdt.rs @@ -114,7 +114,7 @@ pub fn segment_from_gdt(entry: u64, table_index: u8) -> SegmentRegister { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/hypervisor/src/kvm/aarch64/gic/mod.rs b/hypervisor/src/kvm/aarch64/gic/mod.rs index 8bd0ed2403..2e5523e972 100644 --- a/hypervisor/src/kvm/aarch64/gic/mod.rs +++ b/hypervisor/src/kvm/aarch64/gic/mod.rs @@ -481,7 +481,7 @@ impl Vgic for KvmGicV3Its { } #[cfg(test)] -mod tests { +mod unit_tests { use crate::HypervisorVmConfig; use crate::aarch64::gic::{ get_dist_regs, get_icc_regs, get_redist_regs, set_dist_regs, set_icc_regs, set_redist_regs, diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 5ce110d89a..f4738cbf0e 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -2937,7 +2937,7 @@ impl KvmVcpu { } #[cfg(test)] -mod tests { +mod unit_tests { #[test] #[cfg(target_arch = "riscv64")] fn test_get_and_set_regs() { diff --git a/hypervisor/src/kvm/riscv64/aia.rs b/hypervisor/src/kvm/riscv64/aia.rs index 88ca061c05..9eb6e5f397 100644 --- a/hypervisor/src/kvm/riscv64/aia.rs +++ b/hypervisor/src/kvm/riscv64/aia.rs @@ -250,7 +250,7 @@ impl Vaia for KvmAiaImsics { } #[cfg(test)] -mod tests { +mod unit_tests { use crate::HypervisorVmConfig; use crate::arch::riscv64::aia::VaiaConfig; use crate::kvm::KvmAiaImsics; diff --git a/net_util/src/lib.rs b/net_util/src/lib.rs index 4ad7a1c77e..1cc43d4758 100644 --- a/net_util/src/lib.rs +++ b/net_util/src/lib.rs @@ -189,7 +189,7 @@ pub fn virtio_features_to_tap_offload(features: u64) -> c_uint { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/net_util/src/mac.rs b/net_util/src/mac.rs index 432bfebae2..d3a3c9575c 100644 --- a/net_util/src/mac.rs +++ b/net_util/src/mac.rs @@ -139,7 +139,7 @@ impl FromStr for MacAddr { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 45d6a77de8..111bf07de8 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -550,7 +550,7 @@ impl AsRawFd for Tap { #[cfg(test)] #[cfg(devcli_testenv)] // we need special permissions in the ENV to create Tap devices -mod tests { +mod unit_tests { use std::net::Ipv4Addr; use std::sync::{LazyLock, Mutex, mpsc}; use std::time::Duration; diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index d28c2db5ed..39fb1e51e4 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -442,7 +442,7 @@ impl Parseable for StringList { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index 7998370688..b3dd5a6105 100644 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -1180,7 +1180,7 @@ impl PciBarConfiguration { } #[cfg(test)] -mod tests { +mod unit_tests { use vm_memory::ByteValued; use super::*; diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index f52156c70f..cf8a70c149 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -553,7 +553,7 @@ pub fn performance_restore_latency(control: &PerformanceTestControl) -> f64 { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/rate_limiter/src/group.rs b/rate_limiter/src/group.rs index e89dddae7f..9ef2a56cd6 100644 --- a/rate_limiter/src/group.rs +++ b/rate_limiter/src/group.rs @@ -297,7 +297,7 @@ impl Drop for RateLimiterGroup { } #[cfg(test)] -pub(crate) mod tests { +pub(crate) mod unit_tests { use std::os::fd::AsRawFd; use std::thread; use std::time::Duration; diff --git a/rate_limiter/src/lib.rs b/rate_limiter/src/lib.rs index 72221416f1..6c71036b2f 100644 --- a/rate_limiter/src/lib.rs +++ b/rate_limiter/src/lib.rs @@ -522,7 +522,7 @@ impl Default for RateLimiter { } #[cfg(test)] -pub(crate) mod tests { +pub(crate) mod unit_tests { use std::{fmt, thread}; use super::*; diff --git a/src/bin/ch-remote.rs b/src/bin/ch-remote.rs index 803ffc7ee9..1393d25f1c 100644 --- a/src/bin/ch-remote.rs +++ b/src/bin/ch-remote.rs @@ -1205,7 +1205,7 @@ fn main() { } #[cfg(test)] -mod tests { +mod unit_tests { use std::cmp::Ordering; use super::*; diff --git a/tpm/src/lib.rs b/tpm/src/lib.rs index 9153869798..80c28fa976 100644 --- a/tpm/src/lib.rs +++ b/tpm/src/lib.rs @@ -381,7 +381,7 @@ impl Ptm for PtmSetBufferSize { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] fn test_ptmresult() -> Result<()> { diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 0680b4a303..448935cde1 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -392,7 +392,7 @@ impl Snapshottable for VirtioPciCommonConfig { } #[cfg(test)] -mod tests { +mod unit_tests { use vm_memory::GuestMemoryAtomic; use vmm_sys_util::eventfd::EventFd; diff --git a/virtio-devices/src/vsock/csm/connection.rs b/virtio-devices/src/vsock/csm/connection.rs index e36f58ccb7..69589a45be 100644 --- a/virtio-devices/src/vsock/csm/connection.rs +++ b/virtio-devices/src/vsock/csm/connection.rs @@ -670,14 +670,14 @@ where } #[cfg(test)] -mod tests { +mod unit_tests { use std::io::{Error as IoError, Result as IoResult}; use libc::EFD_NONBLOCK; use virtio_queue::QueueOwnedT; use vmm_sys_util::eventfd::EventFd; - use super::super::super::tests::TestContext; + use super::super::super::unit_tests::TestContext; use super::super::defs as csm_defs; use super::*; diff --git a/virtio-devices/src/vsock/csm/txbuf.rs b/virtio-devices/src/vsock/csm/txbuf.rs index 1a8c5dd151..38906fc837 100644 --- a/virtio-devices/src/vsock/csm/txbuf.rs +++ b/virtio-devices/src/vsock/csm/txbuf.rs @@ -142,7 +142,7 @@ impl TxBuf { } #[cfg(test)] -mod tests { +mod unit_tests { use std::io::{Error as IoError, ErrorKind, Result as IoResult}; use super::*; diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 657e80879e..364b78a258 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -514,10 +514,10 @@ impl Transportable for Vsock where B: VsockBackend + Sync + 'static {} impl Migratable for Vsock where B: VsockBackend + Sync + 'static {} #[cfg(test)] -mod tests { +mod unit_tests { use libc::EFD_NONBLOCK; - use super::super::tests::{NoopVirtioInterrupt, TestContext}; + use super::super::unit_tests::{NoopVirtioInterrupt, TestContext}; use super::super::*; use super::*; use crate::ActivateError; diff --git a/virtio-devices/src/vsock/mod.rs b/virtio-devices/src/vsock/mod.rs index 3cd9592a64..7895587855 100644 --- a/virtio-devices/src/vsock/mod.rs +++ b/virtio-devices/src/vsock/mod.rs @@ -161,7 +161,7 @@ pub trait VsockChannel { pub trait VsockBackend: VsockChannel + VsockEpollListener + Send {} #[cfg(any(test, fuzzing))] -pub mod tests { +pub mod unit_tests { use std::os::unix::io::AsRawFd; use std::path::PathBuf; use std::sync::{Arc, RwLock}; diff --git a/virtio-devices/src/vsock/packet.rs b/virtio-devices/src/vsock/packet.rs index 4606c93b11..d28b7b508b 100644 --- a/virtio-devices/src/vsock/packet.rs +++ b/virtio-devices/src/vsock/packet.rs @@ -418,13 +418,13 @@ impl VsockPacket { #[cfg(test)] #[allow(clippy::undocumented_unsafe_blocks)] -mod tests { +mod unit_tests { use virtio_bindings::virtio_ring::VRING_DESC_F_WRITE; use virtio_queue::QueueOwnedT; use vm_memory::GuestAddress; use vm_virtio::queue::testing::VirtqDesc as GuestQDesc; - use super::super::tests::TestContext; + use super::super::unit_tests::TestContext; use super::*; use crate::GuestMemoryMmap; use crate::vsock::defs::MAX_PKT_BUF_SIZE; diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index e2e9fe2e22..e02d3429fa 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -867,7 +867,7 @@ impl VsockMuxer { } #[cfg(test)] -mod tests { +mod unit_tests { use std::cmp::min; use std::io::Write; use std::path::{Path, PathBuf}; @@ -875,7 +875,7 @@ mod tests { use virtio_queue::QueueOwnedT; use super::super::super::csm::defs as csm_defs; - use super::super::super::tests::TestContext as VsockTestContext; + use super::super::super::unit_tests::TestContext as VsockTestContext; use super::*; impl PartiallyReadCommand { diff --git a/vm-allocator/src/address.rs b/vm-allocator/src/address.rs index c847dd473a..3cc5ed9c81 100644 --- a/vm-allocator/src/address.rs +++ b/vm-allocator/src/address.rs @@ -215,7 +215,7 @@ impl AddressAllocator { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/vm-device/src/bus.rs b/vm-device/src/bus.rs index fdfb82c9f1..fc3bbeb464 100644 --- a/vm-device/src/bus.rs +++ b/vm-device/src/bus.rs @@ -259,7 +259,7 @@ impl Bus { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; struct DummyDevice; diff --git a/vm-migration/src/bitpos_iterator.rs b/vm-migration/src/bitpos_iterator.rs index cce89ff0fe..35d0886345 100644 --- a/vm-migration/src/bitpos_iterator.rs +++ b/vm-migration/src/bitpos_iterator.rs @@ -74,7 +74,7 @@ pub trait BitposIteratorExt: Iterator + Sized { impl + Sized> BitposIteratorExt for I {} #[cfg(test)] -mod tests { +mod unit_tests { use super::*; fn bitpos_check(inp: &[u64], out: &[u64]) { diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index b66c9a7ada..eb2bcb0e6e 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -320,7 +320,7 @@ impl MemoryRangeTable { } #[cfg(test)] -mod tests { +mod unit_tests { use crate::protocol::{MemoryRange, MemoryRangeTable}; #[test] diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 6ecdd3c800..86136c9ec4 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -3170,7 +3170,7 @@ impl Drop for VmConfig { } #[cfg(test)] -mod tests { +mod unit_tests { use std::fs::File; use std::os::unix::io::AsRawFd; diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 02e1cc315f..5c80e5bb16 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -2992,7 +2992,7 @@ impl CpuElf64Writable for CpuManager { #[cfg(all(feature = "kvm", target_arch = "x86_64"))] #[cfg(test)] -mod tests { +mod unit_tests { use arch::layout::{BOOT_STACK_POINTER, ZERO_PAGE_START}; use arch::x86_64::interrupts::*; use arch::x86_64::regs::*; @@ -3140,7 +3140,7 @@ mod tests { #[cfg(target_arch = "aarch64")] #[cfg(test)] -mod tests { +mod unit_tests { #[cfg(feature = "kvm")] use std::{mem, mem::offset_of}; diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index e52c5900ba..fed3eee63b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -5496,7 +5496,7 @@ impl Drop for DeviceManager { } #[cfg(test)] -mod tests { +mod unit_tests { use super::*; #[test] diff --git a/vmm/src/device_tree.rs b/vmm/src/device_tree.rs index f837886873..e84cb524de 100644 --- a/vmm/src/device_tree.rs +++ b/vmm/src/device_tree.rs @@ -152,7 +152,7 @@ impl DoubleEndedIterator for BftIter<'_> { } #[cfg(test)] -mod tests { +mod unit_tests { use super::{DeviceNode, DeviceTree}; #[test] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 2271c65fd9..3aa77a69b2 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -3167,7 +3167,7 @@ impl GuestDebuggable for Vm { #[cfg(all(feature = "kvm", target_arch = "x86_64"))] #[cfg(test)] -mod tests { +mod unit_tests { use super::*; fn test_vm_state_transitions(state: VmState) { @@ -3443,7 +3443,7 @@ mod tests { #[cfg(target_arch = "aarch64")] #[cfg(test)] -mod tests { +mod unit_tests { use arch::aarch64::fdt::create_fdt; use arch::aarch64::layout; use arch::{DeviceType, MmioDeviceInfo}; From 35b91f76afa60061579a719988e36b0e98ea865f Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 07:33:24 +0100 Subject: [PATCH 0297/1893] tests: prevent broken terminal after running `cargo test -p vmm` Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index d0a538b6da..a1c3c31aa2 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2401,7 +2401,8 @@ mod unit_tests { }, console: ConsoleConfig { file: None, - mode: ConsoleOutputMode::Tty, + // Caution: Don't use `Tty` to not mess with users terminal + mode: ConsoleOutputMode::Off, iommu: false, socket: None, }, From 7364fbdc8eb42613d99c52c6c5a589c70b20c919 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 07:55:26 +0100 Subject: [PATCH 0298/1893] tests: move VM test into a test module Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/vm.rs | 133 +++++++++++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 67 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 3aa77a69b2..37a2f73cc1 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -3439,6 +3439,72 @@ mod unit_tests { ) ); } + + #[test] + pub fn test_vm() { + use hypervisor::VmExit; + use vm_memory::{Address, GuestMemory, GuestMemoryRegion}; + // This example based on https://lwn.net/Articles/658511/ + let code = [ + 0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */ + 0x00, 0xd8, /* add %bl, %al */ + 0x04, b'0', /* add $'0', %al */ + 0xee, /* out %al, (%dx) */ + 0xb0, b'\n', /* mov $'\n', %al */ + 0xee, /* out %al, (%dx) */ + 0xf4, /* hlt */ + ]; + + let mem_size = 0x1000; + let load_addr = GuestAddress(0x1000); + let mem = GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap(); + + let hv = hypervisor::new().unwrap(); + let vm = hv + .create_vm(HypervisorVmConfig::default()) + .expect("new VM creation failed"); + + for (index, region) in mem.iter().enumerate() { + let mem_region = vm.make_user_memory_region( + index as u32, + region.start_addr().raw_value(), + region.len(), + region.as_ptr() as u64, + false, + false, + ); + + vm.create_user_memory_region(mem_region) + .expect("Cannot configure guest memory"); + } + mem.write_slice(&code, load_addr) + .expect("Writing code to memory failed"); + + let mut vcpu = vm.create_vcpu(0, None).expect("new Vcpu failed"); + + let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed"); + vcpu_sregs.cs.base = 0; + vcpu_sregs.cs.selector = 0; + vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed"); + + let mut vcpu_regs = vcpu.get_regs().expect("get regs failed"); + vcpu_regs.set_rip(0x1000); + vcpu_regs.set_rax(2); + vcpu_regs.set_rbx(3); + vcpu_regs.set_rflags(2); + vcpu.set_regs(&vcpu_regs).expect("set regs failed"); + + loop { + match vcpu.run().expect("run failed") { + VmExit::Reset => { + println!("HLT"); + break; + } + VmExit::Ignore => {} + r => panic!("unexpected exit reason: {r:?}"), + } + } + } } #[cfg(target_arch = "aarch64")] @@ -3509,70 +3575,3 @@ mod unit_tests { .unwrap(); } } - -#[cfg(all(feature = "kvm", target_arch = "x86_64"))] -#[test] -pub fn test_vm() { - use hypervisor::VmExit; - use vm_memory::{Address, GuestMemory, GuestMemoryRegion}; - // This example based on https://lwn.net/Articles/658511/ - let code = [ - 0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */ - 0x00, 0xd8, /* add %bl, %al */ - 0x04, b'0', /* add $'0', %al */ - 0xee, /* out %al, (%dx) */ - 0xb0, b'\n', /* mov $'\n', %al */ - 0xee, /* out %al, (%dx) */ - 0xf4, /* hlt */ - ]; - - let mem_size = 0x1000; - let load_addr = GuestAddress(0x1000); - let mem = GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap(); - - let hv = hypervisor::new().unwrap(); - let vm = hv - .create_vm(HypervisorVmConfig::default()) - .expect("new VM creation failed"); - - for (index, region) in mem.iter().enumerate() { - let mem_region = vm.make_user_memory_region( - index as u32, - region.start_addr().raw_value(), - region.len(), - region.as_ptr() as u64, - false, - false, - ); - - vm.create_user_memory_region(mem_region) - .expect("Cannot configure guest memory"); - } - mem.write_slice(&code, load_addr) - .expect("Writing code to memory failed"); - - let mut vcpu = vm.create_vcpu(0, None).expect("new Vcpu failed"); - - let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed"); - vcpu_sregs.cs.base = 0; - vcpu_sregs.cs.selector = 0; - vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed"); - - let mut vcpu_regs = vcpu.get_regs().expect("get regs failed"); - vcpu_regs.set_rip(0x1000); - vcpu_regs.set_rax(2); - vcpu_regs.set_rbx(3); - vcpu_regs.set_rflags(2); - vcpu.set_regs(&vcpu_regs).expect("set regs failed"); - - loop { - match vcpu.run().expect("run failed") { - VmExit::Reset => { - println!("HLT"); - break; - } - VmExit::Ignore => {} - r => panic!("unexpected exit reason: {r:?}"), - } - } -} From e160a17131a8e10e91dc479e5e1342576688e203 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 07:21:28 +0100 Subject: [PATCH 0299/1893] net_util: unrelated code improvement Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- net_util/src/tap.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 111bf07de8..413bd73504 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -638,13 +638,6 @@ mod unit_tests { } } - fn tap_name_to_string(tap: &Tap) -> String { - let null_pos = tap.if_name.iter().position(|x| *x == 0).unwrap(); - str::from_utf8(&tap.if_name[..null_pos]) - .unwrap() - .to_string() - } - // Given a buffer of appropriate size, this fills in the relevant fields based on the // provided information. Payload refers to the UDP payload. fn pnet_build_packet(buf: &mut [u8], dst_mac: MacAddr, payload: &[u8]) { @@ -785,7 +778,7 @@ mod unit_tests { tap.enable().unwrap(); // Send a packet to the interface. We expect to be able to receive it on the associated fd. - pnet_send_packet(tap_name_to_string(&tap)); + pnet_send_packet(tap.if_name_as_str().to_owned()); let mut buf = [0u8; 4096]; @@ -843,7 +836,7 @@ mod unit_tests { tap.set_ip_addr(ip_addr, Some(netmask)).unwrap(); tap.enable().unwrap(); - let (mac, _, mut rx) = pnet_get_mac_tx_rx(tap_name_to_string(&tap)); + let (mac, _, mut rx) = pnet_get_mac_tx_rx(tap.if_name_as_str().to_owned()); let payload = DATA_STRING.as_bytes(); From 11d17fbf7954356305815c3492343f6e1b5625d3 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 09:04:44 +0100 Subject: [PATCH 0300/1893] docs: update CONTRIBUTING.md Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- CONTRIBUTING.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c77d3e36da..04738e0dc0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,11 +14,30 @@ License](https://opensource.org/licenses/Apache-2.0). ## Coding Style 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` -for each submitted Pull Request (PR). +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). ## Basic Checks +```sh +# We currently rely on nightly-only formatting features +cargo +nightly fmt --all +cargo check --all --all-targets --tests +cargo clippy --all --all-targets --tests +# Please note that this will not execute integration tests. +cargo test --all --all-targets --tests +``` + +### \[Optional\] Run Integration Tests + +_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 '' +``` + +### Setup Commit Hook + Please consider creating the following hook as `.git/hooks/pre-commit` in order to ensure basic correctness of your code. You can extend this further if you have specific features that you regularly develop against. @@ -26,7 +45,7 @@ have specific features that you regularly develop against. ```sh #!/bin/sh -cargo fmt -- --check || exit 1 +cargo +nightly fmt -- --check || exit 1 cargo check --locked --all --all-targets --tests || exit 1 cargo clippy --locked --all --all-targets --tests -- -D warnings || exit 1 ``` From 06390342a6b60341910c666c4c6d47161e664f97 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 09:24:53 +0100 Subject: [PATCH 0301/1893] misc: clippy: add default clippy lint groups This is the first commit in a series of commits to improve the Code Quality in Cloud Hypervisor in a sustainable way. These are the default rules from `clippy::all` but written here to be more explicit. `clippy::all` refers to all "default sensible" lints, not all existing lints. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fa15483fe9..94329850d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,8 +158,18 @@ wait-timeout = "0.2.1" zerocopy = { version = "0.8.27", default-features = false } [workspace.lints.clippy] -# Any clippy lint in alphabetical order, including lint groups: +# Any clippy lint (group) in alphabetical order: # https://rust-lang.github.io/rust-clippy/master/index.html + +# Groups +all = "deny" # shorthand for the other groups but here for compleness +complexity = "deny" +correctness = "deny" +perf = "deny" +style = "deny" +suspicious = "deny" + +# Individual Lints assertions_on_result_states = "deny" undocumented_unsafe_blocks = "deny" From 7cb73e9e561072ffdce02d165ebfd8e896ee115b Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 12:23:48 +0100 Subject: [PATCH 0302/1893] misc: clippy: add unnecessary_semicolon Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 1 + arch/src/aarch64/fdt.rs | 2 +- arch/src/riscv64/fdt.rs | 2 +- arch/src/x86_64/mod.rs | 2 +- arch/src/x86_64/regs.rs | 2 +- block/src/lib.rs | 4 +-- block/src/qcow/raw_file.rs | 2 +- block/src/vhdx/vhdx_io.rs | 4 +-- devices/src/ivshmem.rs | 28 +++++++++---------- devices/src/legacy/cmos.rs | 2 +- devices/src/legacy/fw_cfg.rs | 6 ++-- .../src/arch/x86/emulator/instructions/mod.rs | 2 +- net_util/src/tap.rs | 2 +- option_parser/src/lib.rs | 2 +- pci/src/configuration.rs | 2 +- pci/src/msix.rs | 6 ++-- pci/src/vfio.rs | 4 +-- performance-metrics/src/main.rs | 2 +- rate_limiter/src/group.rs | 2 +- rate_limiter/src/lib.rs | 4 +-- src/bin/ch-remote.rs | 2 +- test_infra/src/lib.rs | 6 ++-- tests/integration.rs | 16 +++++------ virtio-devices/src/block.rs | 2 +- virtio-devices/src/net.rs | 2 +- virtio-devices/src/thread_helper.rs | 2 +- virtio-devices/src/transport/pci_device.rs | 2 +- virtio-devices/src/vsock/csm/connection.rs | 6 ++-- virtio-devices/src/vsock/unix/muxer.rs | 4 +-- virtio-devices/src/vsock/unix/muxer_rxq.rs | 2 +- vmm/src/acpi.rs | 4 +-- vmm/src/config.rs | 4 +-- vmm/src/landlock.rs | 2 +- vmm/src/lib.rs | 4 +-- vmm/src/memory_manager.rs | 2 +- vmm/src/vm.rs | 4 +-- 36 files changed, 72 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 94329850d0..0c7a34dbd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,6 +172,7 @@ suspicious = "deny" # Individual Lints assertions_on_result_states = "deny" undocumented_unsafe_blocks = "deny" +unnecessary_semicolon = "deny" [workspace.lints.rust] # `level = warn` is irrelevant here but mandatory for rustc/cargo diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index 90d6877316..ce3551cc71 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -1150,7 +1150,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) { array, indent = (n_spaces + 2) ); - }; + } } // Print children nodes if there is any diff --git a/arch/src/riscv64/fdt.rs b/arch/src/riscv64/fdt.rs index e781388616..b30d2ce197 100644 --- a/arch/src/riscv64/fdt.rs +++ b/arch/src/riscv64/fdt.rs @@ -472,7 +472,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) { array, indent = (n_spaces + 2) ); - }; + } } // Print children nodes if there is any diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 91fba56c09..3746730ad9 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -824,7 +824,7 @@ pub fn configure_vcpu( * APIC_BUS_CYCLE_NS */ ..Default::default() }); - }; + } } for c in &cpuid { diff --git a/arch/src/x86_64/regs.rs b/arch/src/x86_64/regs.rs index 6e7292f3c0..f02be41bcf 100644 --- a/arch/src/x86_64/regs.rs +++ b/arch/src/x86_64/regs.rs @@ -108,7 +108,7 @@ pub fn setup_regs(vcpu: &dyn hypervisor::Vcpu, entry_point: EntryPoint) -> Resul regs.set_rsp(BOOT_STACK_POINTER.raw_value()); regs.set_rsi(ZERO_PAGE_START.raw_value()); } - }; + } vcpu.set_regs(®s).map_err(Error::SetBaseRegisters) } diff --git a/block/src/lib.rs b/block/src/lib.rs index f7388e5055..bdd242f3ad 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -398,7 +398,7 @@ impl Request { .map_err(ExecuteError::Write)?; } RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), - }; + } } Ok(len) } @@ -901,7 +901,7 @@ impl DiskTopology { }; if ret != 0 { return Err(std::io::Error::last_os_error()); - }; + } Ok(block_size) } diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index f0eff54df3..232871472a 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -259,7 +259,7 @@ impl Write for RawFile { // SAFETY: tmp_ptr was allocated by alloc_zeroed with layout unsafe { dealloc(tmp_ptr, layout) }; return Err(io::Error::last_os_error()); - }; + } tmp_buf[file_offset..(file_offset + buf_len)].copy_from_slice(buf); diff --git a/block/src/vhdx/vhdx_io.rs b/block/src/vhdx/vhdx_io.rs index 14feac8d9d..96ce4ef4ce 100644 --- a/block/src/vhdx/vhdx_io.rs +++ b/block/src/vhdx/vhdx_io.rs @@ -128,7 +128,7 @@ pub fn read( _ => { return Err(VhdxIoError::InvalidBatEntryState); } - }; + } sector_count -= sector.free_sectors; sector_index += sector.free_sectors; read_count += sector.free_bytes as usize; @@ -210,7 +210,7 @@ pub fn write( _ => { return Err(VhdxIoError::InvalidBatEntryState); } - }; + } sector_count -= sector.free_sectors; sector_index += sector.free_sectors; write_count += sector.free_bytes as usize; diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs index 50c056edf3..dccad21d7e 100644 --- a/devices/src/ivshmem.rs +++ b/devices/src/ivshmem.rs @@ -233,20 +233,18 @@ impl PciDevice for IvshmemDevice { if let Some(resources) = resources { for resource in resources { match resource { - Resource::PciBar { index, base, .. } => { - match index { - IVSHMEM_BAR0_IDX => { - bar0_addr = Some(GuestAddress(base)); - } - IVSHMEM_BAR1_IDX => {} - IVSHMEM_BAR2_IDX => { - bar2_addr = Some(GuestAddress(base)); - } - _ => { - error!("Unexpected pci bar index {index}"); - } - }; - } + Resource::PciBar { index, base, .. } => match index { + IVSHMEM_BAR0_IDX => { + bar0_addr = Some(GuestAddress(base)); + } + IVSHMEM_BAR1_IDX => {} + IVSHMEM_BAR2_IDX => { + bar2_addr = Some(GuestAddress(base)); + } + _ => { + error!("Unexpected pci bar index {index}"); + } + }, _ => { error!("Unexpected resource {resource:?}"); } @@ -348,7 +346,7 @@ impl PciDevice for IvshmemDevice { _ => { warn!("Invalid bar_idx: {bar_idx}"); } - }; + } } fn write_bar(&mut self, base: u64, offset: u64, _data: &[u8]) -> Option> { diff --git a/devices/src/legacy/cmos.rs b/devices/src/legacy/cmos.rs index d5528fb775..ff85d63287 100644 --- a/devices/src/legacy/cmos.rs +++ b/devices/src/legacy/cmos.rs @@ -91,7 +91,7 @@ impl BusDevice for Cmos { } } o => warn!("bad write offset on CMOS device: {o}"), - }; + } None } diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 1a5c6478c1..5013835c84 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -710,7 +710,7 @@ impl FwCfg { let bytes = n.to_le_bytes(); data.copy_from_slice(&bytes[start..end]); } - }; + } Some(size as u8) } @@ -756,7 +756,7 @@ impl BusDevice for FwCfg { "fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}." ); } - }; + } } fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { @@ -792,7 +792,7 @@ impl BusDevice for FwCfg { _ => debug!( "fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ." ), - }; + } None } } diff --git a/hypervisor/src/arch/x86/emulator/instructions/mod.rs b/hypervisor/src/arch/x86/emulator/instructions/mod.rs index 945ce16bac..a3ee3f33fe 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/mod.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/mod.rs @@ -101,7 +101,7 @@ fn set_op( platform.write_memory(addr, &value.to_le_bytes()[..op_size])?; } k => return Err(PlatformError::InvalidOperand(anyhow!("{:?}", k))), - }; + } Ok(()) } diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 413bd73504..36f0e2ba33 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -314,7 +314,7 @@ impl Tap { &ifreq, )?; } - }; + } Ok(()) } diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index 39fb1e51e4..ac01831b65 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -73,7 +73,7 @@ fn split_commas(s: &str) -> OptionParserResult> { continue; } _ => {} - }; + } current.push(c); } list.push(current); diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index b3dd5a6105..0e45b1c4c3 100644 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -593,7 +593,7 @@ impl PciConfiguration { writable_bits[9] = 0xfff0_fff0; // Memory base and limit writable_bits[15] = 0xffff_00ff; // Bridge control (r/w), interrupt line (r/w) } - }; + } registers[11] = (u32::from(subsystem_id) << 16) | u32::from(subsystem_vendor_id); ( diff --git a/pci/src/msix.rs b/pci/src/msix.rs index 7007eb3e5d..efd0e7b67e 100644 --- a/pci/src/msix.rs +++ b/pci/src/msix.rs @@ -288,7 +288,7 @@ impl MsixConfig { self.table_entries[index].vector_ctl = value; } _ => error!("invalid offset"), - }; + } debug!("MSI_W TABLE offset 0x{offset:x} data 0x{value:x}"); } @@ -304,12 +304,12 @@ impl MsixConfig { self.table_entries[index].vector_ctl = (value >> 32) as u32; } _ => error!("invalid offset"), - }; + } debug!("MSI_W TABLE offset 0x{offset:x} data 0x{value:x}"); } _ => error!("invalid data length"), - }; + } let table_entry = &self.table_entries[index]; diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 13a528eba3..7c12f116af 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -642,7 +642,7 @@ impl VfioCommon { PCI_CONFIG_BAR_PREFETCHABLE ) { prefetchable = PciBarPrefetchable::Prefetchable - }; + } // To get size write all 1s self.vfio_wrapper @@ -941,7 +941,7 @@ impl VfioCommon { PciCapabilityId::PciExpress => pci_express_cap_found = true, PciCapabilityId::PowerManagement => power_management_cap_found = true, _ => {} - }; + } let cap_next = self.vfio_wrapper.read_config_byte((cap_iter + 1).into()) & PCI_CONFIG_CAPABILITY_PTR_MASK; diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 220532f6cb..b3f1ae018c 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -790,7 +790,7 @@ fn main() { eprintln!("Aborting test due to error: '{e:?}'"); std::process::exit(1); } - }; + } } } diff --git a/rate_limiter/src/group.rs b/rate_limiter/src/group.rs index 9ef2a56cd6..a4b44dfe3c 100644 --- a/rate_limiter/src/group.rs +++ b/rate_limiter/src/group.rs @@ -275,7 +275,7 @@ impl RateLimiterGroup { error!("rate-limit-group worker panicked"); exit_evt.write(1).unwrap(); } - }; + } }) .map(|thread| self.epoll_thread.insert(thread)) .map_err(Error::ThreadSpawn)?; diff --git a/rate_limiter/src/lib.rs b/rate_limiter/src/lib.rs index 6c71036b2f..4101c30b3b 100644 --- a/rate_limiter/src/lib.rs +++ b/rate_limiter/src/lib.rs @@ -491,12 +491,12 @@ impl RateLimiter { BucketUpdate::Disabled => guard.bandwidth = None, BucketUpdate::Update(tb) => guard.bandwidth = Some(tb), BucketUpdate::None => (), - }; + } match ops { BucketUpdate::Disabled => guard.ops = None, BucketUpdate::Update(tb) => guard.ops = Some(tb), BucketUpdate::None => (), - }; + } } } diff --git a/src/bin/ch-remote.rs b/src/bin/ch-remote.rs index 1393d25f1c..13af148bd0 100644 --- a/src/bin/ch-remote.rs +++ b/src/bin/ch-remote.rs @@ -1201,7 +1201,7 @@ fn main() { server_api_error_display_modifier, ); process::exit(1) - }; + } } #[cfg(test)] diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 12d9c5940a..99f41f5273 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -644,7 +644,7 @@ fn scp_to_guest_with_auth( return Err(e); } } - }; + } thread::sleep(std::time::Duration::new((timeout * counter).into(), 0)); } Ok(()) @@ -730,7 +730,7 @@ pub fn ssh_command_ip_with_auth( return Err(e); } } - }; + } thread::sleep(std::time::Duration::new((timeout * counter).into(), 0)); } Ok(s) @@ -1334,7 +1334,7 @@ impl<'a> GuestCommand<'a> { Debug => { self.command.args(["-vv"]); } - }; + } if self.print_cmd { println!( diff --git a/tests/integration.rs b/tests/integration.rs index 72d6aed565..083f1ef31f 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -7683,7 +7683,7 @@ mod ivshmem { None, "source VM was not terminated successfully.", ); - }; + } // Post live-migration check to make sure the destination VM is functional let r = std::panic::catch_unwind(|| { @@ -10098,7 +10098,7 @@ mod live_migration { None, "source VM was not terminated successfully.", ); - }; + } // Post live-migration check to make sure the destination VM is functional let r = std::panic::catch_unwind(|| { @@ -10272,7 +10272,7 @@ mod live_migration { None, "source VM was not terminated successfully.", ); - }; + } // Post live-migration check to make sure the destination VM is functional let r = std::panic::catch_unwind(|| { @@ -10490,7 +10490,7 @@ mod live_migration { None, "source VM was not terminated successfully.", ); - }; + } // Post live-migration check to make sure the destination VM is functional let r = std::panic::catch_unwind(|| { @@ -10706,7 +10706,7 @@ mod live_migration { None, "source VM was not terminated successfully.", ); - }; + } // Post live-migration check to make sure the destination VM is functional let r = std::panic::catch_unwind(|| { @@ -10816,7 +10816,7 @@ mod live_migration { Some(ovs_child), "source VM was not terminated successfully.", ); - }; + } // Post live-migration check to make sure the destination VM is functional let r = std::panic::catch_unwind(|| { @@ -10963,7 +10963,7 @@ mod live_migration { None, "source VM was not terminated successfully.", ); - }; + } // Post live-migration check to make sure the destination VM is functioning let r = std::panic::catch_unwind(|| { @@ -11177,7 +11177,7 @@ mod live_migration { None, "Source VM was not terminated successfully.", ); - }; + } // After live migration, ensure the destination VM is running normally let r = std::panic::catch_unwind(|| { diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index ff8adf3b4e..38eeaf938b 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -223,7 +223,7 @@ impl BlockEpollHandler { queue.go_to_previous_position(); break; } - }; + } } request.set_writeback(self.writeback.load(Ordering::Acquire)); diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index fd6a4a3b07..17ca855fc6 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -124,7 +124,7 @@ impl EpollHelperHandler for NetCtrlEpollHandler { "Error getting notification state of control queue: {e}" ))); } - }; + } } _ => { return Err(EpollHelperError::HandleEvent(anyhow!( diff --git a/virtio-devices/src/thread_helper.rs b/virtio-devices/src/thread_helper.rs index df05a60ed6..735aebbf55 100644 --- a/virtio-devices/src/thread_helper.rs +++ b/virtio-devices/src/thread_helper.rs @@ -54,7 +54,7 @@ where thread_exit_evt.write(1).ok(); } } - }; + } }) .map(|thread| epoll_threads.push(thread)) .map_err(|e| { diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 4f34ffea6f..1a21c7a403 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -1192,7 +1192,7 @@ impl PciDevice for VirtioPciDevice { } } _ => (), - }; + } // Try and activate the device if the driver status has changed if self.needs_activation() { diff --git a/virtio-devices/src/vsock/csm/connection.rs b/virtio-devices/src/vsock/csm/connection.rs index 69589a45be..3b87194da8 100644 --- a/virtio-devices/src/vsock/csm/connection.rs +++ b/virtio-devices/src/vsock/csm/connection.rs @@ -252,7 +252,7 @@ where self.last_fwd_cnt_to_peer = self.fwd_cnt; return Ok(()); } - }; + } } // A credit update is basically a no-op, so we should only waste a perfectly fine RX @@ -380,7 +380,7 @@ where pkt.hdr() ); } - }; + } Ok(()) } @@ -459,7 +459,7 @@ where // it does, so let's absorb it. } _ => self.kill(), - }; + } 0 }); self.fwd_cnt += Wrapping(flushed as u32); diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index e02d3429fa..e6957efd63 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -753,7 +753,7 @@ impl VsockMuxer { conn.kill(); warn!("vsock: unable to ack host connection: {err:?}"); } - }; + } } // If the connection wasn't previously scheduled for RX, add it to our RX queue. @@ -975,7 +975,7 @@ mod unit_tests { EpollListener::LocalStream(_) => local_lsn_count += 1, EpollListener::Connection { .. } => conn_lsn_count += 1, _ => (), - }; + } } (local_lsn_count, conn_lsn_count) } diff --git a/virtio-devices/src/vsock/unix/muxer_rxq.rs b/virtio-devices/src/vsock/unix/muxer_rxq.rs index 077cbc8899..a319ddbd03 100644 --- a/virtio-devices/src/vsock/unix/muxer_rxq.rs +++ b/virtio-devices/src/vsock/unix/muxer_rxq.rs @@ -102,7 +102,7 @@ impl MuxerRxQ { MuxerRx::ConnRx(_) => { self.synced = false; } - }; + } false } diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index fa107d037d..5a48782907 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -779,7 +779,7 @@ fn create_acpi_tables_internal( prev_tbl_len = slit.len() as u64; prev_tbl_addr = slit_addr; - }; + } #[cfg(target_arch = "aarch64")] { @@ -957,7 +957,7 @@ pub fn create_acpi_tables_tdx( // SLIT tables.push(create_slit_table(numa_nodes)); - }; + } // VIOT if let Some((iommu_bdf, devices_bdf)) = device_manager.lock().unwrap().iommu_attached_devices() diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 86136c9ec4..1ad3cfb30e 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2514,10 +2514,10 @@ impl VmConfig { let mut tty_consoles = Vec::new(); if self.console.mode == ConsoleOutputMode::Tty { tty_consoles.push("virtio-console"); - }; + } if self.serial.mode == ConsoleOutputMode::Tty { tty_consoles.push("serial-console"); - }; + } #[cfg(target_arch = "x86_64")] if self.debug_console.mode == ConsoleOutputMode::Tty { tty_consoles.push("debug-console"); diff --git a/vmm/src/landlock.rs b/vmm/src/landlock.rs index e7efd9cbcc..e637119e27 100644 --- a/vmm/src/landlock.rs +++ b/vmm/src/landlock.rs @@ -61,7 +61,7 @@ impl TryFrom<&str> for LandlockAccess { format!("Invalid access: {c}").to_string(), )); } - }; + } } Ok(LandlockAccess { access }) } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index a1c3c31aa2..df9c94910b 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -748,7 +748,7 @@ impl Vmm { error!("Error creating Landlock object: {e:?}"); exit_evt.write(1).ok(); } - }; + } } std::panic::catch_unwind(AssertUnwindSafe(|| { @@ -1513,7 +1513,7 @@ impl RequestHandler for Vmm { // If we don't have a config, we cannot boot a VM. if self.vm_config.is_none() { return Err(VmError::VmMissingConfig); - }; + } // console_info is set to None in vm_shutdown. re-populate here if empty if self.console_info.is_none() { diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index db00f4d611..5b77afb196 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -507,7 +507,7 @@ impl BusDevice for MemoryManager { _ => { warn!("Unexpected offset for accessing memory manager device: {offset:#}"); } - }; + } None } } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 37a2f73cc1..264c4ef80a 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -464,7 +464,7 @@ impl VmOps for VmOpsHandler { info!("Barrier released"); } _ => {} - }; + } Ok(()) } @@ -488,7 +488,7 @@ impl VmOps for VmOpsHandler { info!("Barrier released"); } _ => {} - }; + } Ok(()) } } From ea4f07d3bf2d6e4c133adb6598e3fff059c8590a Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 12:24:47 +0100 Subject: [PATCH 0303/1893] misc: clippy: add uninlined_format_args Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 1 + arch/src/x86_64/mod.rs | 2 +- block/src/lib.rs | 10 ++-- devices/src/gic.rs | 5 +- devices/src/legacy/gpio_pl061.rs | 2 +- devices/src/legacy/rtc_pl031.rs | 5 +- devices/src/legacy/uart_pl011.rs | 7 +-- devices/src/pvmemcontrol.rs | 6 +- .../src/arch/x86/emulator/instructions/mod.rs | 16 ++---- hypervisor/src/arch/x86/emulator/mod.rs | 42 +++++--------- hypervisor/src/kvm/mod.rs | 6 +- hypervisor/src/mshv/mod.rs | 56 +++++++------------ hypervisor/src/mshv/x86_64/emulator.rs | 2 +- tracer/src/tracer.rs | 2 +- vmm/src/api/mod.rs | 2 +- vmm/src/cpu.rs | 4 +- vmm/src/device_manager.rs | 6 +- vmm/src/gdb.rs | 32 +++++------ vmm/src/interrupt.rs | 2 +- vmm/src/lib.rs | 2 +- vmm/src/memory_manager.rs | 2 +- vmm/src/migration.rs | 2 +- vmm/src/vm.rs | 7 +-- 23 files changed, 91 insertions(+), 130 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0c7a34dbd5..ac0af119ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,6 +172,7 @@ suspicious = "deny" # Individual Lints assertions_on_result_states = "deny" undocumented_unsafe_blocks = "deny" +uninlined_format_args = "deny" unnecessary_semicolon = "deny" [workspace.lints.rust] diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 3746730ad9..333375e7d7 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -622,7 +622,7 @@ pub fn generate_common_cpuid( let caps = hypervisor .tdx_capabilities() .map_err(Error::TdxCapabilities)?; - info!("TDX capabilities {:#?}", caps); + info!("TDX capabilities {caps:#?}"); Some(caps) } else { None diff --git a/block/src/lib.rs b/block/src/lib.rs index bdd242f3ad..29d1c0b447 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -645,7 +645,7 @@ pub fn block_io_uring_is_supported() -> bool { let io_uring = match IoUring::new(1) { Ok(io_uring) => io_uring, Err(e) => { - info!("{} failed to create io_uring instance: {}", error_msg, e); + info!("{error_msg} failed to create io_uring instance: {e}"); return false; } }; @@ -658,26 +658,26 @@ pub fn block_io_uring_is_supported() -> bool { match submitter.register_probe(&mut probe) { Ok(_) => {} Err(e) => { - info!("{} failed to register a probe: {}", error_msg, e); + info!("{error_msg} failed to register a probe: {e}"); return false; } } // Check IORING_OP_FSYNC is supported if !probe.is_supported(opcode::Fsync::CODE) { - info!("{} IORING_OP_FSYNC operation not supported", error_msg); + info!("{error_msg} IORING_OP_FSYNC operation not supported"); return false; } // Check IORING_OP_READV is supported if !probe.is_supported(opcode::Readv::CODE) { - info!("{} IORING_OP_READV operation not supported", error_msg); + info!("{error_msg} IORING_OP_READV operation not supported"); return false; } // Check IORING_OP_WRITEV is supported if !probe.is_supported(opcode::Writev::CODE) { - info!("{} IORING_OP_WRITEV operation not supported", error_msg); + info!("{error_msg} IORING_OP_WRITEV operation not supported"); return false; } diff --git a/devices/src/gic.rs b/devices/src/gic.rs index a157c3f25d..2c6493d885 100644 --- a/devices/src/gic.rs +++ b/devices/src/gic.rs @@ -167,10 +167,7 @@ impl Pausable for Gic { // Flush tables to guest RAM let vgic = self.vgic.as_ref().unwrap().clone(); vgic.lock().unwrap().save_data_tables().map_err(|e| { - MigratableError::Pause(anyhow!( - "Could not save GICv3ITS GIC pending tables {:?}", - e - )) + MigratableError::Pause(anyhow!("Could not save GICv3ITS GIC pending tables {e:?}",)) })?; Ok(()) } diff --git a/devices/src/legacy/gpio_pl061.rs b/devices/src/legacy/gpio_pl061.rs index a5ec570b8f..107c67b702 100644 --- a/devices/src/legacy/gpio_pl061.rs +++ b/devices/src/legacy/gpio_pl061.rs @@ -294,7 +294,7 @@ impl BusDevice for Gpio { if data.len() <= 4 { let value = read_le_u32(data); if let Err(e) = self.handle_write(offset, value) { - warn!("Failed to write to GPIO PL061 device: {}", e); + warn!("Failed to write to GPIO PL061 device: {e}"); } } else { warn!( diff --git a/devices/src/legacy/rtc_pl031.rs b/devices/src/legacy/rtc_pl031.rs index 985edd5703..b365ebef45 100644 --- a/devices/src/legacy/rtc_pl031.rs +++ b/devices/src/legacy/rtc_pl031.rs @@ -201,12 +201,11 @@ impl BusDevice for Rtc { if data.len() <= 4 { let v = read_le_u32(data); if let Err(e) = self.handle_write(offset, v) { - warn!("Failed to write to RTC PL031 device: {}", e); + warn!("Failed to write to RTC PL031 device: {e}"); } } else { warn!( - "Invalid RTC PL031 write: offset {}, data length {}", - offset, + "Invalid RTC PL031 write: offset {offset}, data length {}", data.len() ); } diff --git a/devices/src/legacy/uart_pl011.rs b/devices/src/legacy/uart_pl011.rs index 8af77d0e4f..c64f071e56 100644 --- a/devices/src/legacy/uart_pl011.rs +++ b/devices/src/legacy/uart_pl011.rs @@ -322,7 +322,7 @@ impl Pl011 { self.handle_debug(); } off => { - debug!("PL011: Bad write offset, offset: {}", off); + debug!("PL011: Bad write offset, offset: {off}"); return Err(Error::BadWriteOffset(off)); } } @@ -424,12 +424,11 @@ impl BusDevice for Pl011 { if data.len() <= 4 { let v = read_le_u32(data); if let Err(e) = self.handle_write(offset, v) { - warn!("Failed to write to PL011 device: {}", e); + warn!("Failed to write to PL011 device: {e}"); } } else { warn!( - "Invalid PL011 write: offset {}, data length {}", - offset, + "Invalid PL011 write: offset {offset}, data length {}", data.len() ); } diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index 2977a9a527..8a12f2abfb 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -491,7 +491,7 @@ impl PvmemcontrolBusDevice { } else { std::ptr::null() }; - debug!("addr {:X} length {} name {:?}", addr, length, name); + debug!("addr {addr:X} length {length} name {name:?}"); // SAFETY: [`base`, `base` + `len`) is guest memory self.operate_on_memory_range(addr, length, |base, len| unsafe { @@ -581,7 +581,7 @@ impl PvmemcontrolBusDevice { ..Default::default() }, Error::GuestMemory(err) => { - warn!("{}", err); + warn!("{err}"); PvmemcontrolResp { ret_errno: (libc::EINVAL as u32).into(), ret_code: (func_code as u32).into(), @@ -606,7 +606,7 @@ impl PvmemcontrolBusDevice { let response: PvmemcontrolResp = match self.handle_request(request) { Ok(x) => x, Err(e) => { - warn!("cannot process request {:?} with error {}", request, e); + warn!("cannot process request {request:?} with error {e}"); return; } }; diff --git a/hypervisor/src/arch/x86/emulator/instructions/mod.rs b/hypervisor/src/arch/x86/emulator/instructions/mod.rs index a3ee3f33fe..99d797c0b8 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/mod.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/mod.rs @@ -25,15 +25,13 @@ fn get_op( ) -> Result { if insn.op_count() < op_index + 1 { return Err(PlatformError::InvalidOperand(anyhow!( - "Invalid operand {:?}", - op_index + "Invalid operand {op_index:?}" ))); } if !matches!(op_size, 1 | 2 | 4 | 8) { return Err(PlatformError::InvalidOperand(anyhow!( - "Invalid operand size {:?}", - op_size + "Invalid operand size {op_size:?}" ))); } @@ -59,7 +57,7 @@ fn get_op( OpKind::Immediate32 => insn.immediate32() as u64, OpKind::Immediate32to64 => insn.immediate32to64() as u64, OpKind::Immediate64 => insn.immediate64(), - k => return Err(PlatformError::InvalidOperand(anyhow!("{:?}", k))), + k => return Err(PlatformError::InvalidOperand(anyhow!("{k:?}"))), }; Ok(value) @@ -75,15 +73,13 @@ fn set_op( ) -> Result<(), PlatformError> { if insn.op_count() < op_index + 1 { return Err(PlatformError::InvalidOperand(anyhow!( - "Invalid operand {:?}", - op_index + "Invalid operand {op_index:?}" ))); } if !matches!(op_size, 1 | 2 | 4 | 8) { return Err(PlatformError::InvalidOperand(anyhow!( - "Invalid operand size {:?}", - op_size + "Invalid operand size {op_size:?}" ))); } @@ -100,7 +96,7 @@ fn set_op( let addr = memory_operand_address(insn, state, true)?; platform.write_memory(addr, &value.to_le_bytes()[..op_size])?; } - k => return Err(PlatformError::InvalidOperand(anyhow!("{:?}", k))), + k => return Err(PlatformError::InvalidOperand(anyhow!("{k:?}"))), } Ok(()) diff --git a/hypervisor/src/arch/x86/emulator/mod.rs b/hypervisor/src/arch/x86/emulator/mod.rs index 15239a4f8f..bf5cc3b150 100644 --- a/hypervisor/src/arch/x86/emulator/mod.rs +++ b/hypervisor/src/arch/x86/emulator/mod.rs @@ -136,9 +136,7 @@ pub trait CpuStateManager: Clone { .checked_add(segment_register.base) .ok_or_else(|| { PlatformError::InvalidAddress(anyhow!( - "Logical address {:#x} cannot be linearized with segment {:#x?}", - logical_addr, - segment_register + "Logical address {logical_addr:#x} cannot be linearized with segment {segment_register:#x?}" )) })?) } @@ -164,9 +162,7 @@ pub trait CpuStateManager: Clone { if segment_type_expand_down(segment_type) { if logical_addr >= segment_limit.into() { return Err(PlatformError::InvalidAddress(anyhow!( - "{:#x} is off limits {:#x} (expand down)", - logical_addr, - segment_limit + "{logical_addr:#x} is off limits {segment_limit:#x} (expand down)" ))); } @@ -179,16 +175,14 @@ pub trait CpuStateManager: Clone { if logical_addr > segment_limit.into() { return Err(PlatformError::InvalidAddress(anyhow!( - "{:#x} is off limits {:#x}", - logical_addr, - segment_limit + "{logical_addr:#x} is off limits {segment_limit:#x}" ))); } Ok(logical_addr.wrapping_add(segment_register.base)) } - _ => Err(PlatformError::UnsupportedCpuMode(anyhow!("{:?}", mode))), + _ => Err(PlatformError::UnsupportedCpuMode(anyhow!("{mode:?}"))), } } } @@ -252,8 +246,7 @@ impl CpuStateManager for EmulatorCpuState { r => { return Err(PlatformError::InvalidRegister(anyhow!( - "read_reg invalid GPR {:?}", - r + "read_reg invalid GPR {r:?}" ))); } }; @@ -276,18 +269,17 @@ impl CpuStateManager for EmulatorCpuState { } } else { return Err(PlatformError::InvalidRegister(anyhow!( - "read_reg invalid GPR {:?}", - reg + "read_reg invalid GPR {reg:?}" ))); }; - debug!("Register read: {:#x} from {:?}", reg_value, reg); + debug!("Register read: {reg_value:#x} from {reg:?}"); Ok(reg_value) } fn write_reg(&mut self, reg: Register, val: u64) -> Result<(), PlatformError> { - debug!("Register write: {:#x} to {:?}", val, reg); + debug!("Register write: {val:#x} to {reg:?}"); // SDM Vol 1 - 3.4.1.1 // @@ -312,8 +304,7 @@ impl CpuStateManager for EmulatorCpuState { } } else { return Err(PlatformError::InvalidRegister(anyhow!( - "write_reg invalid register {:?}", - reg + "write_reg invalid register {reg:?}" ))); }; @@ -373,8 +364,7 @@ impl CpuStateManager for EmulatorCpuState { Register::CR8 => set_reg!(self.sregs.cr8, mask, reg_value), _ => { return Err(PlatformError::InvalidRegister(anyhow!( - "write_reg invalid register {:?}", - reg + "write_reg invalid register {reg:?}" ))); } } @@ -385,8 +375,7 @@ impl CpuStateManager for EmulatorCpuState { fn read_segment(&self, reg: Register) -> Result { if !reg.is_segment_register() { return Err(PlatformError::InvalidRegister(anyhow!( - "read_segment {:?} is not a segment register", - reg + "read_segment {reg:?} is not a segment register" ))); } @@ -398,8 +387,7 @@ impl CpuStateManager for EmulatorCpuState { Register::GS => Ok(self.sregs.gs), Register::SS => Ok(self.sregs.ss), r => Err(PlatformError::InvalidRegister(anyhow!( - "read_segment invalid register {:?}", - r + "read_segment invalid register {r:?}" ))), } } @@ -410,7 +398,7 @@ impl CpuStateManager for EmulatorCpuState { segment_register: SegmentRegister, ) -> Result<(), PlatformError> { if !reg.is_segment_register() { - return Err(PlatformError::InvalidRegister(anyhow!("{:?}", reg))); + return Err(PlatformError::InvalidRegister(anyhow!("{reg:?}"))); } match reg { @@ -420,7 +408,7 @@ impl CpuStateManager for EmulatorCpuState { Register::FS => self.sregs.fs = segment_register, Register::GS => self.sregs.gs = segment_register, Register::SS => self.sregs.ss = segment_register, - r => return Err(PlatformError::InvalidRegister(anyhow!("{:?}", r))), + r => return Err(PlatformError::InvalidRegister(anyhow!("{r:?}"))), } Ok(()) @@ -590,7 +578,7 @@ impl Emulator<'_, T> { .fetch(last_decoded_ip, &mut fetched_insn_stream) .map_err(EmulationError::PlatformEmulationError)?; - debug!("Fetched {:x?}", fetched_insn_stream); + debug!("Fetched {fetched_insn_stream:x?}"); // Once we have the new stream, we must create a new decoder // and emulate one last instruction from the last decoded IP. diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index f4738cbf0e..6bbd4839e8 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -627,7 +627,7 @@ impl vm::Vm for KvmVm { /// fn create_vgic(&self, config: VgicConfig) -> vm::Result>> { let gic_device = KvmGicV3Its::new(self, config) - .map_err(|e| vm::HypervisorVmError::CreateVgic(anyhow!("Vgic error {:?}", e)))?; + .map_err(|e| vm::HypervisorVmError::CreateVgic(anyhow!("Vgic error {e:?}")))?; Ok(Arc::new(Mutex::new(gic_device))) } @@ -1971,9 +1971,7 @@ impl cpu::Vcpu for KvmVcpu { Ok(cpu::VmExit::Shutdown) } else { Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( - "Unexpected system event with type 0x{:x}, flags 0x{:x?}", - event_type, - flags + "Unexpected system event with type 0x{event_type:x}, flags 0x{flags:x?}", ))) } } diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 46a6103b52..eace0a78a2 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -705,7 +705,7 @@ impl cpu::Vcpu for MshvVcpu { let gva = info.guest_virtual_address; let gpa = info.guest_physical_address; - debug!("Unmapped GPA exit: GVA {:x} GPA {:x}", gva, gpa); + debug!("Unmapped GPA exit: GVA {gva:x} GPA {gpa:x}"); let context = MshvEmulatorContext { vcpu: self, @@ -739,7 +739,7 @@ impl cpu::Vcpu for MshvVcpu { let gva = info.guest_virtual_address; let gpa = info.guest_physical_address; - debug!("Exit ({:?}) GVA {:x} GPA {:x}", msg_type, gva, gpa); + debug!("Exit ({msg_type:?}) GVA {gva:x} GPA {gpa:x}"); let mut context = MshvEmulatorContext { vcpu: self, @@ -777,8 +777,7 @@ impl cpu::Vcpu for MshvVcpu { assert!(num_ranges >= 1); if num_ranges > 1 { return Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( - "Unhandled VCPU exit(GPA_ATTRIBUTE_INTERCEPT): Expected num_ranges to be 1 but found num_ranges {:?}", - num_ranges + "Unhandled VCPU exit(GPA_ATTRIBUTE_INTERCEPT): Expected num_ranges to be 1 but found num_ranges {num_ranges:?}" ))); } @@ -786,10 +785,7 @@ impl cpu::Vcpu for MshvVcpu { let mut gpas = Vec::new(); let ranges = info.ranges; let (gfn_start, gfn_count) = snp::parse_gpa_range(ranges[0]).unwrap(); - debug!( - "Releasing pages: gfn_start: {:x?}, gfn_count: {:?}", - gfn_start, gfn_count - ); + debug!("Releasing pages: gfn_start: {gfn_start:x?}, gfn_count: {gfn_count:?}"); let gpa_start = gfn_start * HV_PAGE_SIZE as u64; for i in 0..gfn_count { gpas.push(gpa_start + i * HV_PAGE_SIZE as u64); @@ -818,7 +814,7 @@ impl cpu::Vcpu for MshvVcpu { self.vm_fd .modify_gpa_host_access(&gpa_list[0]) .map_err(|e| cpu::HypervisorCpuError::RunVcpu(anyhow!( - "Unhandled VCPU exit: attribute intercept - couldn't modify host access {}", e + "Unhandled VCPU exit: attribute intercept - couldn't modify host access {e}" )))?; // Guest is revoking the shared access, so we need to update the bitmap self.host_access_pages.rcu(|_bitmap| { @@ -835,9 +831,7 @@ impl cpu::Vcpu for MshvVcpu { let gpa = info.guest_physical_address; Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( - "Unhandled VCPU exit: Unaccepted GPA({:x}) found at GVA({:x})", - gpa, - gva, + "Unhandled VCPU exit: Unaccepted GPA({gpa:x}) found at GVA({gva:x})", ))) } #[cfg(target_arch = "x86_64")] @@ -905,8 +899,7 @@ impl cpu::Vcpu for MshvVcpu { << GHCB_INFO_BIT_WIDTH) as u64; debug!( - "GHCB_INFO_HYP_FEATURE_REQUEST: Supported features: {:0x}", - ghcb_response + "GHCB_INFO_HYP_FEATURE_REQUEST: Supported features: {ghcb_response:0x}" ); let arr_reg_name_value = [(hv_register_name_HV_X64_REGISTER_GHCB, ghcb_response)]; @@ -1182,8 +1175,7 @@ impl cpu::Vcpu for MshvVcpu { .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; debug!( - "SNP guest request: req_gpa {:0x} rsp_gpa {:0x}", - req_gpa, rsp_gpa + "SNP guest request: req_gpa {req_gpa:0x} rsp_gpa {rsp_gpa:0x}" ); set_svm_field_u64_ptr!(ghcb, exit_info2, 0); @@ -1194,8 +1186,7 @@ impl cpu::Vcpu for MshvVcpu { let apic_id = info.__bindgen_anon_2.__bindgen_anon_1.sw_exit_info1 >> 32; debug!( - "SNP AP CREATE REQUEST with VMSA GPA {:0x}, and APIC ID {:?}", - vmsa_gpa, apic_id + "SNP AP CREATE REQUEST with VMSA GPA {vmsa_gpa:0x}, and APIC ID {apic_id:?}" ); let mshv_ap_create_req = mshv_sev_snp_ap_create { @@ -1220,16 +1211,14 @@ impl cpu::Vcpu for MshvVcpu { Ok(cpu::VmExit::Ignore) } exit => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( - "Unhandled VCPU exit {:?}", - exit + "Unhandled VCPU exit {exit:?}" ))), }, Err(e) => match e.errno() { libc::EAGAIN | libc::EINTR => Ok(cpu::VmExit::Ignore), _ => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( - "VCPU error {:?}", - e + "VCPU error {e:?}" ))), }, } @@ -1576,7 +1565,7 @@ impl MshvVcpu { // SAFETY: Accessing a union element from bindgen generated bindings. let prev_ghcb_gpa = unsafe { reg_assocs[0].value.reg64 }; - debug!("Prev GHCB GPA is {:x}", prev_ghcb_gpa); + debug!("Prev GHCB GPA is {prev_ghcb_gpa:x}"); let mut ghcb_gpa = hv_x64_register_sev_ghcb::default(); @@ -2224,7 +2213,7 @@ impl vm::Vm for MshvVm { #[cfg(target_arch = "aarch64")] fn create_vgic(&self, config: VgicConfig) -> vm::Result>> { let gic_device = MshvGicV2M::new(self, config) - .map_err(|e| vm::HypervisorVmError::CreateVgic(anyhow!("Vgic error {:?}", e)))?; + .map_err(|e| vm::HypervisorVmError::CreateVgic(anyhow!("Vgic error {e:?}")))?; // Register GICD address with the hypervisor self.fd @@ -2233,7 +2222,7 @@ impl vm::Vm for MshvVm { gic_device.dist_addr, ) .map_err(|e| { - vm::HypervisorVmError::CreateVgic(anyhow!("Failed to set GICD address: {}", e)) + vm::HypervisorVmError::CreateVgic(anyhow!("Failed to set GICD address: {e}")) })?; // Register GITS address with the hypervisor @@ -2244,7 +2233,7 @@ impl vm::Vm for MshvVm { gic_device.gits_addr, ) .map_err(|e| { - vm::HypervisorVmError::CreateVgic(anyhow!("Failed to set GITS address: {}", e)) + vm::HypervisorVmError::CreateVgic(anyhow!("Failed to set GITS address: {e}")) })?; Ok(Arc::new(Mutex::new(gic_device))) @@ -2265,8 +2254,7 @@ impl vm::Vm for MshvVm { ) .map_err(|e| { vm::HypervisorVmError::SetVmProperty(anyhow!( - "Failed to set partition property: {}", - e + "Failed to set partition property: {e}" )) }) } @@ -2281,8 +2269,7 @@ impl vm::Vm for MshvVm { ) .map_err(|e| { vm::HypervisorVmError::SetVmProperty(anyhow!( - "Failed to set partition property: {}", - e + "Failed to set partition property: {e}" )) }) } @@ -2363,8 +2350,7 @@ impl vm::Vm for MshvVm { ) .map_err(|e| { vm::HypervisorVmError::InitializeVm(anyhow!( - "Failed to set GIC LPI support: {}", - e + "Failed to set GIC LPI support: {e}", )) })?; @@ -2375,8 +2361,7 @@ impl vm::Vm for MshvVm { ) .map_err(|e| { vm::HypervisorVmError::InitializeVm(anyhow!( - "Failed to set arch timer interrupt ID: {}", - e + "Failed to set arch timer interrupt ID: {e}", )) })?; @@ -2387,8 +2372,7 @@ impl vm::Vm for MshvVm { ) .map_err(|e| { vm::HypervisorVmError::InitializeVm(anyhow!( - "Failed to set PMU interrupt ID: {}", - e + "Failed to set PMU interrupt ID: {e}", )) })?; } diff --git a/hypervisor/src/mshv/x86_64/emulator.rs b/hypervisor/src/mshv/x86_64/emulator.rs index 4ecdee2a42..80d2a2edab 100644 --- a/hypervisor/src/mshv/x86_64/emulator.rs +++ b/hypervisor/src/mshv/x86_64/emulator.rs @@ -158,7 +158,7 @@ impl PlatformEmulator for MshvEmulatorContext<'_> { .map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?; debug!("mshv emulator: Getting new CPU state"); - debug!("mshv emulator: {:#x?}", regs); + debug!("mshv emulator: {regs:#x?}"); Ok(EmulatorCpuState { regs, sregs }) } diff --git a/tracer/src/tracer.rs b/tracer/src/tracer.rs index 23c53a8c9e..3ff20bebb7 100644 --- a/tracer/src/tracer.rs +++ b/tracer/src/tracer.rs @@ -52,7 +52,7 @@ impl Tracer { file.flush().unwrap(); - warn!("Trace output: {}", path); + warn!("Trace output: {path}"); } fn add_event(&mut self, event: TraceEvent) { diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index e0ffc2f8e1..4bb4d00151 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -757,7 +757,7 @@ impl ApiAction for VmCoredump { response_sender: Sender, ) -> ApiRequest { Box::new(move |vmm| { - info!("API request event: VmCoredump {:?}", coredump_data); + info!("API request event: VmCoredump {coredump_data:?}"); let response = vmm .vm_coredump(&coredump_data.destination_url) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 5c80e5bb16..fd770af59e 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -1266,7 +1266,7 @@ impl CpuManager { warn!("TDG_VP_VMCALL_SETUP_EVENT_NOTIFY_INTERRUPT not supported") } }, - Err(e) => error!("Unexpected TDX VMCALL: {}", e), + Err(e) => error!("Unexpected TDX VMCALL: {e}"), } vcpu.vcpu.set_tdx_status(TdxExitStatus::InvalidOperand); } @@ -1922,7 +1922,7 @@ impl CpuManager { // or low (0x000xxx...). let high_range = extract_bits_64!(gva, 55, 1); if high_range == 0 { - info!("VA (0x{:x}) range is not supported!", gva); + info!("VA (0x{gva:x}) range is not supported!"); return Ok(gva); } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index fed3eee63b..3aaa72b779 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3447,7 +3447,7 @@ impl DeviceManager { let (pci_segment_id, pci_device_bdf, resources) = self.pci_resources(&id, pci_segment_id)?; - info!("Creating pvmemcontrol device: id = {}", id); + info!("Creating pvmemcontrol device: id = {id}"); let (pvmemcontrol_pci_device, pvmemcontrol_bus_device) = devices::pvmemcontrol::PvmemcontrolDevice::make_device( id.clone(), @@ -4222,7 +4222,7 @@ impl DeviceManager { ) -> DeviceManagerResult>>> { let id = String::from(IVSHMEM_DEVICE_NAME); let pci_segment_id = 0x0_u16; - info!("Creating ivshmem device {}", id); + info!("Creating ivshmem device {id}"); let (pci_segment_id, pci_device_bdf, resources) = self.pci_resources(&id, pci_segment_id)?; @@ -4982,7 +4982,7 @@ impl IvshmemOps for IvshmemHandler { size: usize, backing_file: Option, ) -> Result<(Arc, UserspaceMapping), IvshmemError> { - info!("Creating ivshmem mem region at 0x{:x}", start_addr); + info!("Creating ivshmem mem region at 0x{start_addr:x}"); let region = MemoryManager::create_ram_region( &backing_file, diff --git a/vmm/src/gdb.rs b/vmm/src/gdb.rs index ef4f4de8fd..f97fb46253 100644 --- a/vmm/src/gdb.rs +++ b/vmm/src/gdb.rs @@ -223,11 +223,11 @@ impl MultiThreadBase for GdbStub { Ok(()) } Ok(s) => { - error!("Unexpected response for ReadRegs: {:?}", s); + error!("Unexpected response for ReadRegs: {s:?}"); Err(TargetError::NonFatal) } Err(e) => { - error!("Failed to request ReadRegs: {:?}", e); + error!("Failed to request ReadRegs: {e:?}"); Err(TargetError::NonFatal) } } @@ -244,7 +244,7 @@ impl MultiThreadBase for GdbStub { ) { Ok(_) => Ok(()), Err(e) => { - error!("Failed to request WriteRegs: {:?}", e); + error!("Failed to request WriteRegs: {e:?}"); Err(TargetError::NonFatal) } } @@ -267,11 +267,11 @@ impl MultiThreadBase for GdbStub { Ok(std::cmp::min(data.len(), r.len())) } Ok(s) => { - error!("Unexpected response for ReadMem: {:?}", s); + error!("Unexpected response for ReadMem: {s:?}"); Err(TargetError::NonFatal) } Err(e) => { - error!("Failed to request ReadMem: {:?}", e); + error!("Failed to request ReadMem: {e:?}"); Err(TargetError::NonFatal) } } @@ -289,7 +289,7 @@ impl MultiThreadBase for GdbStub { ) { Ok(_) => Ok(()), Err(e) => { - error!("Failed to request WriteMem: {:?}", e); + error!("Failed to request WriteMem: {e:?}"); Err(TargetError::NonFatal) } } @@ -414,7 +414,7 @@ impl HwBreakpoint for GdbStub { match self.vm_request(payload, 0) { Ok(_) => Ok(true), Err(e) => { - error!("Failed to request SetHwBreakPoint: {:?}", e); + error!("Failed to request SetHwBreakPoint: {e:?}"); Err(TargetError::NonFatal) } } @@ -433,7 +433,7 @@ impl HwBreakpoint for GdbStub { match self.vm_request(payload, 0) { Ok(_) => Ok(true), Err(e) => { - error!("Failed to request SetHwBreakPoint: {:?}", e); + error!("Failed to request SetHwBreakPoint: {e:?}"); Err(TargetError::NonFatal) } } @@ -498,7 +498,7 @@ impl run_blocking::BlockingEventLoop for GdbEventLoop { target .vm_request(GdbRequestPayload::Pause, 0) .map_err(|e| { - error!("Failed to pause the target: {:?}", e); + error!("Failed to pause the target: {e:?}"); "Failed to pause the target" })?; Ok(Some(MultiThreadStopReason::Signal(Signal::SIGINT))) @@ -509,7 +509,7 @@ pub fn gdb_thread(mut gdbstub: GdbStub, path: &std::path::Path) { let listener = match UnixListener::bind(path) { Ok(s) => s, Err(e) => { - error!("Failed to create a Unix domain socket listener: {}", e); + error!("Failed to create a Unix domain socket listener: {e}"); return; } }; @@ -518,11 +518,11 @@ pub fn gdb_thread(mut gdbstub: GdbStub, path: &std::path::Path) { let (stream, addr) = match listener.accept() { Ok(v) => v, Err(e) => { - error!("Failed to accept a connection from GDB: {}", e); + error!("Failed to accept a connection from GDB: {e}"); return; } }; - info!("GDB connected from {:?}", addr); + info!("GDB connected from {addr:?}"); let connection: Box> = Box::new(stream); let gdb = gdbstub::stub::GdbStub::new(connection); @@ -533,17 +533,17 @@ pub fn gdb_thread(mut gdbstub: GdbStub, path: &std::path::Path) { info!("GDB client has disconnected. Running..."); if let Err(e) = gdbstub.vm_request(GdbRequestPayload::SetSingleStep(false), 0) { - error!("Failed to disable single step: {:?}", e); + error!("Failed to disable single step: {e:?}"); } if let Err(e) = gdbstub.vm_request(GdbRequestPayload::SetHwBreakPoint(Vec::new()), 0) { - error!("Failed to remove breakpoints: {:?}", e); + error!("Failed to remove breakpoints: {e:?}"); } if let Err(e) = gdbstub.vm_request(GdbRequestPayload::Resume, 0) { - error!("Failed to resume the VM: {:?}", e); + error!("Failed to resume the VM: {e:?}"); } } _ => { @@ -551,7 +551,7 @@ pub fn gdb_thread(mut gdbstub: GdbStub, path: &std::path::Path) { } }, Err(e) => { - error!("error occurred in GDB session: {}", e); + error!("error occurred in GDB session: {e}"); } } } diff --git a/vmm/src/interrupt.rs b/vmm/src/interrupt.rs index 8c4c94b0e0..e42ba2f76b 100644 --- a/vmm/src/interrupt.rs +++ b/vmm/src/interrupt.rs @@ -219,7 +219,7 @@ impl InterruptSourceGroup for LegacyUserspaceInterruptGroup { .lock() .unwrap() .service_irq(self.irq as usize) - .map_err(|e| io::Error::other(format!("failed to inject IRQ #{}: {:?}", self.irq, e))) + .map_err(|e| io::Error::other(format!("failed to inject IRQ #{}: {e:?}", self.irq))) } fn update( diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index df9c94910b..8c3ffc5e54 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -945,7 +945,7 @@ impl Vmm { })?; #[cfg(feature = "guest_debug")] let debug_evt = self.vm_debug_evt.try_clone().map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error cloning debug EventFd: {}", e)) + MigratableError::MigrateReceive(anyhow!("Error cloning debug EventFd: {e}")) })?; let activate_evt = self.activate_evt.try_clone().map_err(|e| { MigratableError::MigrateReceive(anyhow!("Error cloning activate EventFd: {e}")) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 5b77afb196..3f7ef1f257 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -2074,7 +2074,7 @@ impl MemoryManager { } } - debug!("coredump total bytes {}", total_bytes); + debug!("coredump total bytes {total_bytes}"); Ok(()) } diff --git a/vmm/src/migration.rs b/vmm/src/migration.rs index 3a8404bfb0..7046d838e3 100644 --- a/vmm/src/migration.rs +++ b/vmm/src/migration.rs @@ -39,7 +39,7 @@ pub fn url_to_file(url: &str) -> std::result::Result { info!("Copying section to guest memory"); From b4c62bf159de6e7470835584b4df617f5fdec7a7 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 12:26:21 +0100 Subject: [PATCH 0304/1893] misc: clippy: add semicolon_if_nothing_returned Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 1 + arch/src/x86_64/mod.rs | 4 +- arch/src/x86_64/tdx/mod.rs | 4 +- block/src/lib.rs | 8 +- block/src/raw_async.rs | 10 +- devices/src/acpi.rs | 4 +- devices/src/ivshmem.rs | 2 +- devices/src/legacy/cmos.rs | 2 +- devices/src/legacy/debug_port.rs | 2 +- devices/src/legacy/fw_cfg.rs | 6 +- devices/src/legacy/fwdebug.rs | 6 +- devices/src/legacy/gpio_pl061.rs | 2 +- devices/src/legacy/serial.rs | 8 +- devices/src/pvmemcontrol.rs | 12 +- devices/src/pvpanic.rs | 2 +- hypervisor/src/arch/aarch64/gic.rs | 2 +- .../src/arch/x86/emulator/instructions/mov.rs | 4 +- hypervisor/src/arch/x86/emulator/mod.rs | 6 +- hypervisor/src/arch/x86/mod.rs | 2 +- hypervisor/src/mshv/mod.rs | 2 +- net_util/src/ctrl_queue.rs | 2 +- net_util/src/open_tap.rs | 4 +- option_parser/src/lib.rs | 4 +- pci/src/msi.rs | 14 +- pci/src/vfio.rs | 20 +-- pci/src/vfio_user.rs | 6 +- performance-metrics/src/performance_tests.rs | 2 +- rate_limiter/src/group.rs | 4 +- rate_limiter/src/lib.rs | 2 +- src/main.rs | 4 +- tests/integration.rs | 144 +++++++++--------- tpm/src/lib.rs | 10 +- vhost_user_block/src/lib.rs | 2 +- vhost_user_net/src/lib.rs | 6 +- virtio-devices/src/balloon.rs | 2 +- virtio-devices/src/block.rs | 10 +- virtio-devices/src/console.rs | 4 +- virtio-devices/src/iommu.rs | 2 +- virtio-devices/src/mem.rs | 2 +- virtio-devices/src/net.rs | 4 +- virtio-devices/src/pmem.rs | 4 +- virtio-devices/src/rng.rs | 4 +- .../src/transport/pci_common_config.rs | 7 +- virtio-devices/src/transport/pci_device.rs | 6 +- virtio-devices/src/vdpa.rs | 4 +- virtio-devices/src/vhost_user/blk.rs | 4 +- virtio-devices/src/vhost_user/fs.rs | 6 +- virtio-devices/src/vhost_user/net.rs | 2 +- virtio-devices/src/vsock/device.rs | 6 +- virtio-devices/src/vsock/unix/muxer.rs | 2 +- virtio-devices/src/watchdog.rs | 2 +- vm-allocator/src/memory_slot.rs | 2 +- vm-allocator/src/system.rs | 4 +- vm-device/src/bus.rs | 4 +- vm-migration/src/protocol.rs | 4 +- vm-virtio/src/queue.rs | 4 +- vmm/src/acpi.rs | 4 +- vmm/src/config.rs | 14 +- vmm/src/cpu.rs | 18 +-- vmm/src/device_manager.rs | 20 +-- vmm/src/lib.rs | 4 +- vmm/src/memory_manager.rs | 6 +- vmm/src/pci_segment.rs | 8 +- vmm/src/vm.rs | 4 +- 64 files changed, 244 insertions(+), 236 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ac0af119ab..6953818920 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -171,6 +171,7 @@ suspicious = "deny" # Individual Lints assertions_on_result_states = "deny" +semicolon_if_nothing_returned = "deny" undocumented_unsafe_blocks = "deny" uninlined_format_args = "deny" unnecessary_semicolon = "deny" diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 333375e7d7..edc3629921 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -634,7 +634,7 @@ pub fn generate_common_cpuid( // Clear AMX related bits if the AMX feature is not enabled 0x7 => { if !config.amx && entry.index == 0 { - entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8)) + entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8)); } } 0xd => @@ -701,7 +701,7 @@ pub fn generate_common_cpuid( | (1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT) | (1 << KVM_FEATURE_ASYNC_PF_BIT) | (1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT) - | (1 << KVM_FEATURE_STEAL_TIME_BIT)) + | (1 << KVM_FEATURE_STEAL_TIME_BIT)); } } _ => {} diff --git a/arch/src/x86_64/tdx/mod.rs b/arch/src/x86_64/tdx/mod.rs index 814c16aaf0..4ce1aa9a07 100644 --- a/arch/src/x86_64/tdx/mod.rs +++ b/arch/src/x86_64/tdx/mod.rs @@ -304,7 +304,7 @@ fn align_hob(v: u64) -> u64 { impl TdHob { fn update_offset(&mut self) { - self.current_offset = align_hob(self.current_offset + std::mem::size_of::() as u64) + self.current_offset = align_hob(self.current_offset + std::mem::size_of::() as u64); } pub fn start(offset: u64) -> TdHob { @@ -528,7 +528,7 @@ mod unit_tests { let mut f = std::fs::File::open("tdvf.fd").unwrap(); let (sections, _) = parse_tdvf_sections(&mut f).unwrap(); for section in sections { - eprintln!("{section:x?}") + eprintln!("{section:x?}"); } } } diff --git a/block/src/lib.rs b/block/src/lib.rs index 29d1c0b447..9ad36ac832 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -122,7 +122,7 @@ pub fn build_serial(disk_path: &Path) -> Vec { // This will also zero out any leftover bytes. let disk_id = m.as_bytes(); let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize); - default_serial[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy]) + default_serial[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy]); } } default_serial @@ -563,7 +563,7 @@ impl Request { aligned_operation.aligned_ptr as *const u8, aligned_operation.origin_ptr as *mut u8, aligned_operation.size, - ) + ); }; } @@ -574,7 +574,7 @@ impl Request { dealloc( aligned_operation.aligned_ptr as *mut u8, aligned_operation.layout, - ) + ); }; } @@ -582,7 +582,7 @@ impl Request { } pub fn set_writeback(&mut self, writeback: bool) { - self.writeback = writeback + self.writeback = writeback; } } diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 1a582073b0..cd786033e9 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -97,7 +97,7 @@ impl AsyncIo for RawFileAsync { .build() .user_data(user_data), ) - .map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))? + .map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?; }; // Update the submission queue and submit new operations to the @@ -125,7 +125,7 @@ impl AsyncIo for RawFileAsync { .build() .user_data(user_data), ) - .map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))? + .map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?; }; // Update the submission queue and submit new operations to the @@ -147,7 +147,7 @@ impl AsyncIo for RawFileAsync { .build() .user_data(user_data), ) - .map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))? + .map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?; }; // Update the submission queue and submit new operations to the @@ -199,7 +199,7 @@ impl AsyncIo for RawFileAsync { ) .map_err(|_| { AsyncIoError::ReadVectored(Error::other("Submission queue is full")) - })? + })?; }; submitted = true; } @@ -219,7 +219,7 @@ impl AsyncIo for RawFileAsync { ) .map_err(|_| { AsyncIoError::WriteVectored(Error::other("Submission queue is full")) - })? + })?; }; submitted = true; } diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs index 4791d52db0..9e6790d857 100644 --- a/devices/src/acpi.rs +++ b/devices/src/acpi.rs @@ -44,7 +44,7 @@ impl AcpiShutdownDevice { impl BusDevice for AcpiShutdownDevice { // Spec has all fields as zero fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) { - data.fill(0) + data.fill(0); } fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option> { @@ -213,7 +213,7 @@ impl Aml for AcpiGedDevice { ), ], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs index dccad21d7e..5997e10a2b 100644 --- a/devices/src/ivshmem.rs +++ b/devices/src/ivshmem.rs @@ -209,7 +209,7 @@ impl IvshmemDevice { impl BusDevice for IvshmemDevice { fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) { - self.read_bar(base, offset, data) + self.read_bar(base, offset, data); } fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { diff --git a/devices/src/legacy/cmos.rs b/devices/src/legacy/cmos.rs index ff85d63287..6076db7a90 100644 --- a/devices/src/legacy/cmos.rs +++ b/devices/src/legacy/cmos.rs @@ -87,7 +87,7 @@ impl BusDevice for Cmos { } } } else { - self.data[(self.index & INDEX_MASK) as usize] = data[0] + self.data[(self.index & INDEX_MASK) as usize] = data[0]; } } o => warn!("bad write offset on CMOS device: {o}"), diff --git a/devices/src/legacy/debug_port.rs b/devices/src/legacy/debug_port.rs index 3050e9e618..28a58b319d 100644 --- a/devices/src/legacy/debug_port.rs +++ b/devices/src/legacy/debug_port.rs @@ -62,7 +62,7 @@ impl DebugPort { impl BusDevice for DebugPort { fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) { - error!("Invalid read to debug port") + error!("Invalid read to debug port"); } fn write( diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 5013835c84..df31104aff 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -442,7 +442,7 @@ impl FwCfg { fw_cfg_item_list: Option>, ) -> Result<()> { if let Some(mem_size) = mem_size { - self.add_e820(mem_size)? + self.add_e820(mem_size)?; } if let Some(kernel) = kernel { self.add_kernel_data(&kernel)?; @@ -451,7 +451,7 @@ impl FwCfg { self.add_kernel_cmdline(cmdline); } if let Some(initramfs) = initramfs { - self.add_initramfs_data(&initramfs)? + self.add_initramfs_data(&initramfs)?; } if let Some(fw_cfg_item_list) = fw_cfg_item_list { for item in fw_cfg_item_list { @@ -626,7 +626,7 @@ impl FwCfg { &access_resp.0.to_be_bytes(), GuestAddress(dma_address + core::mem::offset_of!(FwCfgDmaAccess, control_be) as u64), ) { - error!("fw_cfg: finishing dma: {e:?}") + error!("fw_cfg: finishing dma: {e:?}"); } } diff --git a/devices/src/legacy/fwdebug.rs b/devices/src/legacy/fwdebug.rs index 0de5b6eea9..e678f44977 100644 --- a/devices/src/legacy/fwdebug.rs +++ b/devices/src/legacy/fwdebug.rs @@ -26,9 +26,9 @@ impl BusDevice for FwDebugDevice { /// Upon read return the magic value to indicate that there is a debug port fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) { if data.len() == 1 { - data[0] = 0xe9 + data[0] = 0xe9; } else { - error!("Invalid read size on debug port: {}", data.len()) + error!("Invalid read size on debug port: {}", data.len()); } } @@ -36,7 +36,7 @@ impl BusDevice for FwDebugDevice { if data.len() == 1 { print!("{}", data[0] as char); } else { - error!("Invalid write size on debug port: {}", data.len()) + error!("Invalid write size on debug port: {}", data.len()); } None diff --git a/devices/src/legacy/gpio_pl061.rs b/devices/src/legacy/gpio_pl061.rs index 107c67b702..6e7b057d81 100644 --- a/devices/src/legacy/gpio_pl061.rs +++ b/devices/src/legacy/gpio_pl061.rs @@ -261,7 +261,7 @@ impl BusDevice for Gpio { let index = ((offset - GPIO_ID_LOW) >> 2) as usize; value = u32::from(GPIO_ID[index]); } else if offset < OFS_DATA { - value = self.data & ((offset >> 2) as u32) + value = self.data & ((offset >> 2) as u32); } else { value = match offset { GPIODIR => self.dir, diff --git a/devices/src/legacy/serial.rs b/devices/src/legacy/serial.rs index 11c5769b6b..be6fc126eb 100644 --- a/devices/src/legacy/serial.rs +++ b/devices/src/legacy/serial.rs @@ -215,7 +215,7 @@ impl Serial { fn thr_empty(&mut self) -> Result<()> { if self.is_thr_intr_enabled() { self.add_intr_bit(IIR_THR_BIT); - self.trigger_interrupt()? + self.trigger_interrupt()?; } Ok(()) } @@ -223,7 +223,7 @@ impl Serial { fn recv_data(&mut self) -> Result<()> { if self.is_recv_intr_enabled() { self.add_intr_bit(IIR_RECV_BIT); - self.trigger_interrupt()? + self.trigger_interrupt()?; } self.line_status |= LSR_DATA_BIT; Ok(()) @@ -240,10 +240,10 @@ impl Serial { fn handle_write(&mut self, offset: u8, v: u8) -> Result<()> { match offset { DLAB_LOW if self.is_dlab_set() => { - self.baud_divisor = (self.baud_divisor & 0xff00) | u16::from(v) + self.baud_divisor = (self.baud_divisor & 0xff00) | u16::from(v); } DLAB_HIGH if self.is_dlab_set() => { - self.baud_divisor = (self.baud_divisor & 0x00ff) | ((u16::from(v)) << 8) + self.baud_divisor = (self.baud_divisor & 0x00ff) | ((u16::from(v)) << 8); } DATA => { if self.is_loop() { diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index 8a12f2abfb..f096be69fb 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -390,7 +390,7 @@ impl PvmemcontrolDevice { .iter() .skip(offset as usize) .zip(data.iter_mut()) - .for_each(|(src, dest)| *dest = *src) + .for_each(|(src, dest)| *dest = *src); } /// can only write to transport payload @@ -402,7 +402,7 @@ impl PvmemcontrolDevice { .iter_mut() .skip(offset as usize) .zip(data.iter()) - .for_each(|(dest, src)| *dest = *src) + .for_each(|(dest, src)| *dest = *src); } fn find_connection(&self, conn: GuestConnection) -> Option { @@ -650,13 +650,13 @@ impl PvmemcontrolBusDevice { .ok_or(Error::InvalidConnection(conn.command)) }) .map(|gpa| self.handle_pvmemcontrol_request(gpa)) - .unwrap_or_else(|err| warn!("{:?}", err)); + .unwrap_or_else(|err| warn!("{err:?}")); } } } fn handle_guest_read(&self, offset: u64, data: &mut [u8]) { - self.dev.read().unwrap().read_transport(offset, data) + self.dev.read().unwrap().read_transport(offset, data); } } @@ -760,7 +760,7 @@ impl PciDevice for PvmemcontrolPciDevice { _mmio64_allocator: &mut AddressAllocator, ) -> Result<(), PciDeviceError> { for bar in self.bar_regions.drain(..) { - mmio32_allocator.free(GuestAddress(bar.addr()), bar.size()) + mmio32_allocator.free(GuestAddress(bar.addr()), bar.size()); } Ok(()) } @@ -805,7 +805,7 @@ impl Migratable for PvmemcontrolPciDevice {} impl BusDeviceSync for PvmemcontrolBusDevice { fn read(&self, _base: u64, offset: u64, data: &mut [u8]) { - self.handle_guest_read(offset, data) + self.handle_guest_read(offset, data); } fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option> { diff --git a/devices/src/pvpanic.rs b/devices/src/pvpanic.rs index d6d3f01163..9150e2e986 100644 --- a/devices/src/pvpanic.rs +++ b/devices/src/pvpanic.rs @@ -141,7 +141,7 @@ impl PvPanicDevice { impl BusDevice for PvPanicDevice { fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) { - self.read_bar(base, offset, data) + self.read_bar(base, offset, data); } fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option> { diff --git a/hypervisor/src/arch/aarch64/gic.rs b/hypervisor/src/arch/aarch64/gic.rs index d02103f9d7..fb62d8d893 100644 --- a/hypervisor/src/arch/aarch64/gic.rs +++ b/hypervisor/src/arch/aarch64/gic.rs @@ -66,7 +66,7 @@ impl<'de> Deserialize<'de> for GicState { assert!( std::mem::size_of::() == std::mem::size_of::() - ) + ); }; let value: serde_json::Value = Deserialize::deserialize(deserializer)?; diff --git a/hypervisor/src/arch/x86/emulator/instructions/mov.rs b/hypervisor/src/arch/x86/emulator/instructions/mov.rs index 660462fbde..d5c275392e 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/mov.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/mov.rs @@ -699,7 +699,7 @@ mod unit_tests { } for (register, instruction_prefix) in test_inputs { - helper(register, instruction_prefix) + helper(register, instruction_prefix); } } @@ -760,7 +760,7 @@ mod unit_tests { } for (register, instruction_prefix) in test_inputs { - helper(register, instruction_prefix) + helper(register, instruction_prefix); } } } diff --git a/hypervisor/src/arch/x86/emulator/mod.rs b/hypervisor/src/arch/x86/emulator/mod.rs index bf5cc3b150..591a5e42b0 100644 --- a/hypervisor/src/arch/x86/emulator/mod.rs +++ b/hypervisor/src/arch/x86/emulator/mod.rs @@ -167,9 +167,9 @@ pub trait CpuStateManager: Clone { } if segment_register.db() != 0 { - segment_limit = 0xffffffff + segment_limit = 0xffffffff; } else { - segment_limit = 0xffff + segment_limit = 0xffff; } } @@ -427,7 +427,7 @@ impl CpuStateManager for EmulatorCpuState { } fn set_efer(&mut self, efer: u64) { - self.sregs.efer = efer + self.sregs.efer = efer; } fn flags(&self) -> u64 { diff --git a/hypervisor/src/arch/x86/mod.rs b/hypervisor/src/arch/x86/mod.rs index c337624621..fbe2cee2cb 100644 --- a/hypervisor/src/arch/x86/mod.rs +++ b/hypervisor/src/arch/x86/mod.rs @@ -296,7 +296,7 @@ impl LapicState { // Following call can't fail if the offsets defined above are correct. writer .write_u32::(value) - .expect("Failed to write klapic register") + .expect("Failed to write klapic register"); } } diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index eace0a78a2..92ef648b69 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -1399,7 +1399,7 @@ impl cpu::Vcpu for MshvVcpu { if self.vp_index == 0 { self.fd .set_misc_regs(&state.misc) - .map_err(|e| cpu::HypervisorCpuError::SetMiscRegs(e.into()))? + .map_err(|e| cpu::HypervisorCpuError::SetMiscRegs(e.into()))?; } self.fd .set_debug_regs(&state.dbg) diff --git a/net_util/src/ctrl_queue.rs b/net_util/src/ctrl_queue.rs index 6b9cfb23bf..6b668a27e6 100644 --- a/net_util/src/ctrl_queue.rs +++ b/net_util/src/ctrl_queue.rs @@ -120,7 +120,7 @@ impl CtrlQueue { tap.set_offload(virtio_features_to_tap_offload(features)) .map_err(|e| { error!("Error programming tap offload: {e:?}"); - ok = false + ok = false; }) .ok(); } diff --git a/net_util/src/open_tap.rs b/net_util/src/open_tap.rs index 61e763ba20..cbe77765c7 100644 --- a/net_util/src/open_tap.rs +++ b/net_util/src/open_tap.rs @@ -91,9 +91,9 @@ fn open_tap_rx_q_0( ); } if let Some(mac) = host_mac { - tap.set_mac_addr(*mac).map_err(Error::TapSetMac)? + tap.set_mac_addr(*mac).map_err(Error::TapSetMac)?; } else { - *host_mac = Some(tap.get_mac_addr().map_err(Error::TapGetMac)?) + *host_mac = Some(tap.get_mac_addr().map_err(Error::TapGetMac)?); } if let Some(mtu) = mtu { tap.set_mtu(mtu as i32).map_err(Error::TapSetMtu)?; diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index ac01831b65..1722da39f2 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -407,7 +407,7 @@ fn dequote(s: &str) -> String { } else { out.push(i); } - prev_byte = i + prev_byte = i; } assert!(!in_quotes, "split_commas didn't reject unbalanced quotes"); // SAFETY: the non-ASCII bytes in the output are the same @@ -515,6 +515,6 @@ mod unit_tests { #[test] fn check_dequote() { - assert_eq!(dequote("a\u{3b2}\"a\"\"\""), "a\u{3b2}a\"") + assert_eq!(dequote("a\u{3b2}\"a\"\"\""), "a\u{3b2}a\""); } } diff --git a/pci/src/msi.rs b/pci/src/msi.rs index fd590cb14c..e0d07cec27 100644 --- a/pci/src/msi.rs +++ b/pci/src/msi.rs @@ -142,8 +142,9 @@ impl MsiCap { let value = LittleEndian::read_u16(data); match offset { MSI_MSG_CTL_OFFSET => { - self.msg_ctl = (self.msg_ctl & !(MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)) - | (value & (MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)) + self.msg_ctl = (self.msg_ctl + & !(MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)) + | (value & (MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)); } x if x == msg_data_offset => self.msg_data = value, _ => error!("invalid offset"), @@ -153,16 +154,17 @@ impl MsiCap { let value = LittleEndian::read_u32(data); match offset { 0x0 => { - self.msg_ctl = (self.msg_ctl & !(MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)) - | ((value >> 16) as u16 & (MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)) + self.msg_ctl = (self.msg_ctl + & !(MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)) + | ((value >> 16) as u16 & (MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)); } MSI_MSG_ADDR_LO_OFFSET => self.msg_addr_lo = value & MSI_MSG_ADDR_LO_MASK, x if x == msg_data_offset => self.msg_data = value as u16, x if addr_hi_offset.is_some() && x == addr_hi_offset.unwrap() => { - self.msg_addr_hi = value + self.msg_addr_hi = value; } x if mask_bits_offset.is_some() && x == mask_bits_offset.unwrap() => { - self.mask_bits = value + self.mask_bits = value; } _ => error!("invalid offset"), } diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 7c12f116af..2c07f309c0 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -238,14 +238,14 @@ impl Interrupt { fn msix_write_table(&mut self, offset: u64, data: &[u8]) { if let Some(msix) = &mut self.msix { let offset = offset - u64::from(msix.cap.table_offset()); - msix.bar.write_table(offset, data) + msix.bar.write_table(offset, data); } } fn msix_read_table(&self, offset: u64, data: &mut [u8]) { if let Some(msix) = &self.msix { let offset = offset - u64::from(msix.cap.table_offset()); - msix.bar.read_table(offset, data) + msix.bar.read_table(offset, data); } } @@ -344,7 +344,7 @@ pub(crate) trait Vfio: Send + Sync { fn write_config_dword(&self, offset: u32, buf: u32) { let data: [u8; 4] = buf.to_le_bytes(); - self.write_config(offset, &data) + self.write_config(offset, &data); } fn read_config(&self, offset: u32, data: &mut [u8]) { @@ -352,7 +352,7 @@ pub(crate) trait Vfio: Send + Sync { } fn write_config(&self, offset: u32, data: &[u8]) { - self.region_write(VFIO_PCI_CONFIG_REGION_INDEX, offset.into(), data) + self.region_write(VFIO_PCI_CONFIG_REGION_INDEX, offset.into(), data); } fn enable_msi(&self, fds: Vec<&EventFd>) -> Result<(), VfioError> { @@ -408,11 +408,11 @@ impl VfioDeviceWrapper { impl Vfio for VfioDeviceWrapper { fn region_read(&self, index: u32, offset: u64, data: &mut [u8]) { - self.device.region_read(index, data, offset) + self.device.region_read(index, data, offset); } fn region_write(&self, index: u32, offset: u64, data: &[u8]) { - self.device.region_write(index, data, offset) + self.device.region_write(index, data, offset); } fn get_irq_info(&self, irq_index: u32) -> Option { @@ -641,7 +641,7 @@ impl VfioCommon { flags & PCI_CONFIG_BAR_PREFETCHABLE, PCI_CONFIG_BAR_PREFETCHABLE ) { - prefetchable = PciBarPrefetchable::Prefetchable + prefetchable = PciBarPrefetchable::Prefetchable; } // To get size write all 1s @@ -1786,7 +1786,7 @@ impl Drop for VfioPciDevice { if let Some(msi) = &self.common.interrupt.msi && msi.cfg.enabled() { - self.common.disable_msi() + self.common.disable_msi(); } if self.common.interrupt.intx_in_use() { @@ -1797,7 +1797,7 @@ impl Drop for VfioPciDevice { impl BusDevice for VfioPciDevice { fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) { - self.read_bar(base, offset, data) + self.read_bar(base, offset, data); } fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { @@ -1870,7 +1870,7 @@ impl PciDevice for VfioPciDevice { } fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) { - self.common.read_bar(base, offset, data) + self.common.read_bar(base, offset, data); } fn write_bar(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index be0c775efc..73de9ee96e 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -274,7 +274,7 @@ impl VfioUserPciDevice { impl BusDevice for VfioUserPciDevice { fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) { - self.read_bar(base, offset, data) + self.read_bar(base, offset, data); } fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { @@ -440,7 +440,7 @@ impl PciDevice for VfioUserPciDevice { } fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) { - self.common.read_bar(base, offset, data) + self.common.read_bar(base, offset, data); } fn write_bar(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { @@ -514,7 +514,7 @@ impl Drop for VfioUserPciDevice { if let Some(msi) = &self.common.interrupt.msi && msi.cfg.enabled() { - self.common.disable_msi() + self.common.disable_msi(); } if self.common.interrupt.intx_in_use() { diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index cf8a70c149..6a6cdfaece 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -487,7 +487,7 @@ fn measure_restore_time( \n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) - ) + ); }) } diff --git a/rate_limiter/src/group.rs b/rate_limiter/src/group.rs index a4b44dfe3c..ce726e40d3 100644 --- a/rate_limiter/src/group.rs +++ b/rate_limiter/src/group.rs @@ -72,7 +72,7 @@ impl RateLimiterGroupHandle { /// Can be used to *manually* add tokens to a bucket. Useful for reverting a /// `consume()` if needed. pub fn manual_replenish(&self, tokens: u64, token_type: TokenType) { - self.inner.rate_limiter.manual_replenish(tokens, token_type) + self.inner.rate_limiter.manual_replenish(tokens, token_type); } /// This function needs to be called every time there is an event on the @@ -250,7 +250,7 @@ impl RateLimiterGroup { inner.rate_limiter.event_handler().unwrap(); let handles = inner.handles.lock().unwrap(); for handle in handles.iter() { - handle.write(1).map_err(Error::EventFdWrite)? + handle.write(1).map_err(Error::EventFdWrite)?; } } EpollDispatch::Kill => { diff --git a/rate_limiter/src/lib.rs b/rate_limiter/src/lib.rs index 4101c30b3b..da3a4c78ff 100644 --- a/rate_limiter/src/lib.rs +++ b/rate_limiter/src/lib.rs @@ -300,7 +300,7 @@ impl RateLimiterInner { self.timer_fd .reset(dur, None) .expect("Can't arm the timer (unexpected 'timerfd_settime' failure)."); - flag.store(true, Ordering::Relaxed) + flag.store(true, Ordering::Relaxed); } } diff --git a/src/main.rs b/src/main.rs index 32dad8dffd..192ac7488c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -770,7 +770,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { .map_err(Error::VmmThread)?; if let Some(api_handle) = vmm_thread_handle.http_api_handle { - http_api_graceful_shutdown(api_handle).map_err(Error::HttpApiShutdown)? + http_api_graceful_shutdown(api_handle).map_err(Error::HttpApiShutdown)?; } #[cfg(feature = "dbus_api")] @@ -2010,6 +2010,6 @@ mod unit_tests { let (default_vcpus, default_memory, default_rng) = prepare_default_values(); let args = get_cli_options_sorted(default_vcpus, default_memory, default_rng); - assert_args_sorted(|| args.iter()) + assert_args_sorted(|| args.iter()); } } diff --git a/tests/integration.rs b/tests/integration.rs index 083f1ef31f..06a0242dd3 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -874,7 +874,7 @@ impl MetaEvent { if v["event"].as_str().unwrap() == self.event { if let Some(device_id) = &self.device_id { if v["properties"]["id"].as_str().unwrap() == device_id { - matched = true + matched = true; } } else { matched = true; @@ -2028,7 +2028,7 @@ fn _get_vmm_overhead(pid: u32, guest_memory_size: u32) -> HashMap { let values: Vec<&str> = l.split_whitespace().collect(); region_name = values.last().unwrap().trim().to_string(); if region_name == "0" { - region_name = "anonymous".to_string() + region_name = "anonymous".to_string(); } } @@ -2509,13 +2509,13 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_focal_hypervisor_fw() { - test_simple_launch(fw_path(FwType::RustHypervisorFirmware), FOCAL_IMAGE_NAME) + test_simple_launch(fw_path(FwType::RustHypervisorFirmware), FOCAL_IMAGE_NAME); } #[test] #[cfg(target_arch = "x86_64")] fn test_focal_ovmf() { - test_simple_launch(fw_path(FwType::Ovmf), FOCAL_IMAGE_NAME) + test_simple_launch(fw_path(FwType::Ovmf), FOCAL_IMAGE_NAME); } #[cfg(target_arch = "x86_64")] @@ -3470,27 +3470,27 @@ mod common_parallel { #[test] fn test_virtio_block_io_uring() { - _test_virtio_block(FOCAL_IMAGE_NAME, false, true) + _test_virtio_block(FOCAL_IMAGE_NAME, false, true); } #[test] fn test_virtio_block_aio() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, false) + _test_virtio_block(FOCAL_IMAGE_NAME, true, false); } #[test] fn test_virtio_block_sync() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, true) + _test_virtio_block(FOCAL_IMAGE_NAME, true, true); } #[test] fn test_virtio_block_qcow2() { - _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2, false, false) + _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2, false, false); } #[test] fn test_virtio_block_qcow2_backing_file() { - _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE, false, false) + _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE, false, false); } #[test] @@ -3515,7 +3515,7 @@ mod common_parallel { .output() .expect("Expect generating VHD image from RAW image"); - _test_virtio_block(FOCAL_IMAGE_NAME_VHD, false, false) + _test_virtio_block(FOCAL_IMAGE_NAME_VHD, false, false); } #[test] @@ -3539,7 +3539,7 @@ mod common_parallel { .output() .expect("Expect generating dynamic VHDx image from RAW image"); - _test_virtio_block(FOCAL_IMAGE_NAME_VHDX, false, false) + _test_virtio_block(FOCAL_IMAGE_NAME_VHDX, false, false); } #[test] @@ -3677,7 +3677,7 @@ mod common_parallel { #[test] fn test_vhost_user_net_default() { - test_vhost_user_net(None, 2, &prepare_vhost_user_net_daemon, false, false) + test_vhost_user_net(None, 2, &prepare_vhost_user_net_daemon, false, false); } #[test] @@ -3688,7 +3688,7 @@ mod common_parallel { &prepare_vhost_user_net_daemon, false, false, - ) + ); } #[test] @@ -3699,12 +3699,12 @@ mod common_parallel { &prepare_vhost_user_net_daemon, false, false, - ) + ); } #[test] fn test_vhost_user_net_multiple_queues() { - test_vhost_user_net(None, 4, &prepare_vhost_user_net_daemon, false, false) + test_vhost_user_net(None, 4, &prepare_vhost_user_net_daemon, false, false); } #[test] @@ -3715,40 +3715,40 @@ mod common_parallel { &prepare_vhost_user_net_daemon, false, false, - ) + ); } #[test] fn test_vhost_user_net_host_mac() { - test_vhost_user_net(None, 2, &prepare_vhost_user_net_daemon, true, false) + test_vhost_user_net(None, 2, &prepare_vhost_user_net_daemon, true, false); } #[test] fn test_vhost_user_net_client_mode() { - test_vhost_user_net(None, 2, &prepare_vhost_user_net_daemon, false, true) + test_vhost_user_net(None, 2, &prepare_vhost_user_net_daemon, false, true); } #[test] #[cfg(not(target_arch = "aarch64"))] fn test_vhost_user_blk_default() { - test_vhost_user_blk(2, false, false, Some(&prepare_vubd)) + test_vhost_user_blk(2, false, false, Some(&prepare_vubd)); } #[test] #[cfg(not(target_arch = "aarch64"))] fn test_vhost_user_blk_readonly() { - test_vhost_user_blk(1, true, false, Some(&prepare_vubd)) + test_vhost_user_blk(1, true, false, Some(&prepare_vubd)); } #[test] #[cfg(not(target_arch = "aarch64"))] fn test_vhost_user_blk_direct() { - test_vhost_user_blk(1, false, true, Some(&prepare_vubd)) + test_vhost_user_blk(1, false, true, Some(&prepare_vubd)); } #[test] fn test_boot_from_vhost_user_blk_default() { - test_boot_from_vhost_user_blk(1, false, false, Some(&prepare_vubd)) + test_boot_from_vhost_user_blk(1, false, false, Some(&prepare_vubd)); } #[test] @@ -3928,32 +3928,32 @@ mod common_parallel { #[test] fn test_virtio_fs() { - _test_virtio_fs(&prepare_virtiofsd, false, None) + _test_virtio_fs(&prepare_virtiofsd, false, None); } #[test] fn test_virtio_fs_hotplug() { - _test_virtio_fs(&prepare_virtiofsd, true, None) + _test_virtio_fs(&prepare_virtiofsd, true, None); } #[test] fn test_virtio_fs_multi_segment_hotplug() { - _test_virtio_fs(&prepare_virtiofsd, true, Some(15)) + _test_virtio_fs(&prepare_virtiofsd, true, Some(15)); } #[test] fn test_virtio_fs_multi_segment() { - _test_virtio_fs(&prepare_virtiofsd, false, Some(15)) + _test_virtio_fs(&prepare_virtiofsd, false, Some(15)); } #[test] fn test_virtio_pmem_discard_writes() { - test_virtio_pmem(true, false) + test_virtio_pmem(true, false); } #[test] fn test_virtio_pmem_with_size() { - test_virtio_pmem(true, true) + test_virtio_pmem(true, true); } #[test] @@ -4353,7 +4353,7 @@ mod common_parallel { let r = std::panic::catch_unwind(|| { // Check that the cloud-hypervisor binary actually terminated - assert!(output.status.success()) + assert!(output.status.success()); }); handle_child_output(r, &output); } @@ -4806,7 +4806,7 @@ mod common_parallel { #[test] fn test_virtio_vsock() { - _test_virtio_vsock(false) + _test_virtio_vsock(false); } #[test] @@ -4819,7 +4819,7 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_shutdown(TargetApi::new_http_api(&guest.tmp_dir), guest) + _test_api_shutdown(TargetApi::new_http_api(&guest.tmp_dir), guest); } #[test] @@ -4835,7 +4835,7 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_pause_resume(TargetApi::new_http_api(&guest.tmp_dir), guest) + _test_api_pause_resume(TargetApi::new_http_api(&guest.tmp_dir), guest); } #[test] @@ -4843,12 +4843,12 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_create_boot(TargetApi::new_http_api(&guest.tmp_dir), guest) + _test_api_create_boot(TargetApi::new_http_api(&guest.tmp_dir), guest); } #[test] fn test_virtio_iommu() { - _test_virtio_iommu(cfg!(target_arch = "x86_64")) + _test_virtio_iommu(cfg!(target_arch = "x86_64")); } #[test] @@ -4957,7 +4957,7 @@ mod common_parallel { #[test] fn test_memory_mergeable_off() { - test_memory_mergeable(false) + test_memory_mergeable(false); } #[test] @@ -5558,13 +5558,13 @@ mod common_parallel { #[test] fn test_disk_hotplug() { - _test_disk_hotplug(false) + _test_disk_hotplug(false); } #[test] #[cfg(target_arch = "x86_64")] fn test_disk_hotplug_with_landlock() { - _test_disk_hotplug(true) + _test_disk_hotplug(true); } fn create_loop_device(backing_file_path: &str, block_size: u32, num_retries: usize) -> String { @@ -5909,12 +5909,12 @@ mod common_parallel { #[test] fn test_pmem_hotplug() { - _test_pmem_hotplug(None) + _test_pmem_hotplug(None); } #[test] fn test_pmem_multi_segment_hotplug() { - _test_pmem_hotplug(Some(15)) + _test_pmem_hotplug(Some(15)); } fn _test_pmem_hotplug(pci_segment: Option) { @@ -6054,12 +6054,12 @@ mod common_parallel { #[test] fn test_net_hotplug() { - _test_net_hotplug(None) + _test_net_hotplug(None); } #[test] fn test_net_multi_segment_hotplug() { - _test_net_hotplug(Some(15)) + _test_net_hotplug(Some(15)); } fn _test_net_hotplug(pci_segment: Option) { @@ -6772,13 +6772,13 @@ mod common_parallel { #[test] #[cfg_attr(target_arch = "aarch64", ignore = "See #5443")] fn test_macvtap() { - _test_macvtap(false, "guestmacvtap0", "hostmacvtap0") + _test_macvtap(false, "guestmacvtap0", "hostmacvtap0"); } #[test] #[cfg_attr(target_arch = "aarch64", ignore = "See #5443")] fn test_macvtap_hotplug() { - _test_macvtap(true, "guestmacvtap1", "hostmacvtap1") + _test_macvtap(true, "guestmacvtap1", "hostmacvtap1"); } #[test] @@ -7487,7 +7487,7 @@ mod dbus_api { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_create_boot(TargetApi::new_dbus_api(&guest.tmp_dir), guest) + _test_api_create_boot(TargetApi::new_dbus_api(&guest.tmp_dir), guest); } #[test] @@ -7495,7 +7495,7 @@ mod dbus_api { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_shutdown(TargetApi::new_dbus_api(&guest.tmp_dir), guest) + _test_api_shutdown(TargetApi::new_dbus_api(&guest.tmp_dir), guest); } #[test] @@ -7511,7 +7511,7 @@ mod dbus_api { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_pause_resume(TargetApi::new_dbus_api(&guest.tmp_dir), guest) + _test_api_pause_resume(TargetApi::new_dbus_api(&guest.tmp_dir), guest); } } @@ -7921,13 +7921,13 @@ mod ivshmem { #[test] #[cfg(not(feature = "mshv"))] fn test_live_migration_ivshmem() { - _test_live_migration_ivshmem(false) + _test_live_migration_ivshmem(false); } #[test] #[cfg(not(feature = "mshv"))] fn test_live_migration_ivshmem_local() { - _test_live_migration_ivshmem(true) + _test_live_migration_ivshmem(true); } } @@ -7939,7 +7939,7 @@ mod common_sequential { #[test] #[cfg(not(feature = "mshv"))] fn test_memory_mergeable_on() { - test_memory_mergeable(true) + test_memory_mergeable(true); } pub(crate) fn snapshot_and_check_events( @@ -8018,7 +8018,7 @@ mod common_sequential { let mut mem_params = "size=2G"; if use_hotplug { - mem_params = "size=2G,hotplug_method=virtio-mem,hotplug_size=32G" + mem_params = "size=2G,hotplug_method=virtio-mem,hotplug_size=32G"; } let cloudinit_params = format!( @@ -8635,7 +8635,7 @@ mod common_sequential { #[test] fn test_virtio_pmem_persist_writes() { - test_virtio_pmem(false, false) + test_virtio_pmem(false, false); } } @@ -9690,12 +9690,12 @@ mod vfio { #[test] fn test_nvidia_card_memory_hotplug_acpi() { - test_nvidia_card_memory_hotplug("acpi") + test_nvidia_card_memory_hotplug("acpi"); } #[test] fn test_nvidia_card_memory_hotplug_virtio_mem() { - test_nvidia_card_memory_hotplug("virtio-mem") + test_nvidia_card_memory_hotplug("virtio-mem"); } #[test] @@ -11203,12 +11203,12 @@ mod live_migration { use super::*; #[test] fn test_live_migration_basic() { - _test_live_migration(false, false) + _test_live_migration(false, false); } #[test] fn test_live_migration_local() { - _test_live_migration(false, true) + _test_live_migration(false, true); } #[test] @@ -11218,37 +11218,37 @@ mod live_migration { #[test] fn test_live_migration_watchdog() { - _test_live_migration_watchdog(false, false) + _test_live_migration_watchdog(false, false); } #[test] fn test_live_migration_watchdog_local() { - _test_live_migration_watchdog(false, true) + _test_live_migration_watchdog(false, true); } #[test] fn test_live_upgrade_basic() { - _test_live_migration(true, false) + _test_live_migration(true, false); } #[test] fn test_live_upgrade_local() { - _test_live_migration(true, true) + _test_live_migration(true, true); } #[test] fn test_live_upgrade_watchdog() { - _test_live_migration_watchdog(true, false) + _test_live_migration_watchdog(true, false); } #[test] fn test_live_upgrade_watchdog_local() { - _test_live_migration_watchdog(true, true) + _test_live_migration_watchdog(true, true); } #[test] #[cfg(target_arch = "x86_64")] fn test_live_migration_with_landlock() { - _test_live_migration_with_landlock() + _test_live_migration_with_landlock(); } } @@ -11259,42 +11259,42 @@ mod live_migration { #[test] fn test_live_migration_balloon() { - _test_live_migration_balloon(false, false) + _test_live_migration_balloon(false, false); } #[test] fn test_live_migration_balloon_local() { - _test_live_migration_balloon(false, true) + _test_live_migration_balloon(false, true); } #[test] fn test_live_upgrade_balloon() { - _test_live_migration_balloon(true, false) + _test_live_migration_balloon(true, false); } #[test] fn test_live_upgrade_balloon_local() { - _test_live_migration_balloon(true, true) + _test_live_migration_balloon(true, true); } #[test] fn test_live_migration_numa() { - _test_live_migration_numa(false, false) + _test_live_migration_numa(false, false); } #[test] fn test_live_migration_numa_local() { - _test_live_migration_numa(false, true) + _test_live_migration_numa(false, true); } #[test] fn test_live_upgrade_numa() { - _test_live_migration_numa(true, false) + _test_live_migration_numa(true, false); } #[test] fn test_live_upgrade_numa_local() { - _test_live_migration_numa(true, true) + _test_live_migration_numa(true, true); } // Require to run ovs-dpdk tests sequentially because they rely on the same ovs-dpdk setup @@ -11395,7 +11395,7 @@ mod aarch64_acpi { #[test] fn test_virtio_iommu() { - _test_virtio_iommu(true) + _test_virtio_iommu(true); } } @@ -11674,7 +11674,7 @@ mod rate_limiter { #[test] fn test_rate_limiter_block_bandwidth() { _test_rate_limiter_block(true, 1); - _test_rate_limiter_block(true, 2) + _test_rate_limiter_block(true, 2); } #[test] diff --git a/tpm/src/lib.rs b/tpm/src/lib.rs index 80c28fa976..46a7422d73 100644 --- a/tpm/src/lib.rs +++ b/tpm/src/lib.rs @@ -209,7 +209,7 @@ impl Ptm for PtmEst { fn set_member_type(&mut self, _mem: MemberType) {} fn set_result_code(&mut self, res: u32) { - self.result_code = res + self.result_code = res; } fn get_result_code(&self) -> u32 { @@ -269,11 +269,11 @@ impl Ptm for PtmInit { } fn set_member_type(&mut self, mem: MemberType) { - self.member = mem + self.member = mem; } fn set_result_code(&mut self, res: u32) { - self.result_code = res + self.result_code = res; } fn get_result_code(&self) -> u32 { @@ -368,11 +368,11 @@ impl Ptm for PtmSetBufferSize { } fn set_member_type(&mut self, mem: MemberType) { - self.mem = mem + self.mem = mem; } fn set_result_code(&mut self, res: u32) { - self.result_code = res + self.result_code = res; } fn get_result_code(&self) -> u32 { diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index b3549243e0..0123ac2042 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -541,7 +541,7 @@ pub fn start_block_backend(backend_command: &str) { for thread in blk_backend.read().unwrap().threads.iter() { if let Err(e) = thread.lock().unwrap().kill_evt.write(1) { - error!("Error shutting down worker thread: {e:?}") + error!("Error shutting down worker thread: {e:?}"); } } } diff --git a/vhost_user_net/src/lib.rs b/vhost_user_net/src/lib.rs index 9979cc9f58..c625fbd906 100644 --- a/vhost_user_net/src/lib.rs +++ b/vhost_user_net/src/lib.rs @@ -228,7 +228,7 @@ impl VhostUserBackendMut for VhostUserNetBackend { { vring .signal_used_queue() - .map_err(Error::FailedSignalingUsedQueue)? + .map_err(Error::FailedSignalingUsedQueue)?; } } 3 => { @@ -240,7 +240,7 @@ impl VhostUserBackendMut for VhostUserNetBackend { { vring .signal_used_queue() - .map_err(Error::FailedSignalingUsedQueue)? + .map_err(Error::FailedSignalingUsedQueue)?; } } _ => return Err(Error::HandleEventUnknownEvent.into()), @@ -406,7 +406,7 @@ pub fn start_net_backend(backend_command: &str) { for thread in net_backend.read().unwrap().threads.iter() { if let Err(e) = thread.lock().unwrap().kill_evt.write(1) { - error!("Error shutting down worker thread: {e:?}") + error!("Error shutting down worker thread: {e:?}"); } } } diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index d28d1d117e..d7ce192622 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -546,7 +546,7 @@ impl VirtioDevice for Balloon { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 38eeaf938b..cf1b1c65e9 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -522,7 +522,7 @@ impl BlockEpollHandler { "Failed scheduling the virtqueue thread {} on the expected CPU set: {}", self.queue_index, io::Error::last_os_error() - ) + ); } } } @@ -562,7 +562,7 @@ impl EpollHelperHandler for BlockEpollHandler { // Process the queue only when the rate limit is not reached if !rate_limit_reached { - self.process_queue_submit_and_signal()? + self.process_queue_submit_and_signal()?; } } COMPLETION_EVENT => { @@ -598,7 +598,7 @@ impl EpollHelperHandler for BlockEpollHandler { )) })?; - self.process_queue_submit_and_signal()? + self.process_queue_submit_and_signal()?; } else { return Err(EpollHelperError::HandleEvent(anyhow!( "Unexpected 'RATE_LIMITER_EVENT' when rate_limiter is not enabled." @@ -874,7 +874,7 @@ impl VirtioDevice for Block { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -1027,7 +1027,7 @@ impl VirtioDevice for Block { } fn set_access_platform(&mut self, access_platform: Arc) { - self.common.set_access_platform(access_platform) + self.common.set_access_platform(access_platform); } } diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index 5756c92737..eeeeb50537 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -694,7 +694,7 @@ impl VirtioDevice for Console { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -766,7 +766,7 @@ impl VirtioDevice for Console { } fn set_access_platform(&mut self, access_platform: Arc) { - self.common.set_access_platform(access_platform) + self.common.set_access_platform(access_platform); } } diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index f2acbcec97..e89665e513 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -1048,7 +1048,7 @@ impl VirtioDevice for Iommu { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 51bb41ba00..06fd160d39 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -901,7 +901,7 @@ impl VirtioDevice for Mem { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 17ca855fc6..f0e98da3bd 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -684,7 +684,7 @@ impl VirtioDevice for Net { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -852,7 +852,7 @@ impl VirtioDevice for Net { } fn set_access_platform(&mut self, access_platform: Arc) { - self.common.set_access_platform(access_platform) + self.common.set_access_platform(access_platform); } } diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 2916b6efc0..d766558472 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -374,7 +374,7 @@ impl VirtioDevice for Pmem { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -440,7 +440,7 @@ impl VirtioDevice for Pmem { } fn set_access_platform(&mut self, access_platform: Arc) { - self.common.set_access_platform(access_platform) + self.common.set_access_platform(access_platform); } } diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index f87c523a34..c1d5ce9e1e 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -239,7 +239,7 @@ impl VirtioDevice for Rng { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn activate( @@ -297,7 +297,7 @@ impl VirtioDevice for Rng { } fn set_access_platform(&mut self, access_platform: Arc) { - self.common.set_access_platform(access_platform) + self.common.set_access_platform(access_platform); } } diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 448935cde1..363c621eb4 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -203,7 +203,12 @@ impl VirtioPciCommonConfig { 1 => self.write_common_config_byte(offset, data[0]), 2 => self.write_common_config_word(offset, LittleEndian::read_u16(data), queues), 4 => { - self.write_common_config_dword(offset, LittleEndian::read_u32(data), queues, device) + self.write_common_config_dword( + offset, + LittleEndian::read_u32(data), + queues, + device, + ); } 8 => self.write_common_config_qword(offset, LittleEndian::read_u64(data), queues), _ => error!("invalid data length for virtio write: len {}", data.len()), diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 1a21c7a403..da54ef64da 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -395,7 +395,7 @@ impl VirtioPciDevice { for _ in locked_device.queue_max_sizes().iter() { queue_evts.push(EventFd::new(EFD_NONBLOCK).map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!("Failed creating eventfd: {e}")) - })?) + })?); } let num_queues = locked_device.queue_max_sizes().len(); @@ -748,7 +748,7 @@ impl VirtioPciDevice { let bar_offset: u32 = // SAFETY: we know self.cap_pci_cfg_info.cap.cap.offset is 32bits long. unsafe { std::mem::transmute(self.cap_pci_cfg_info.cap.cap.offset) }; - self.read_bar(0, bar_offset as u64, data) + self.read_bar(0, bar_offset as u64, data); } } @@ -1240,7 +1240,7 @@ impl PciDevice for VirtioPciDevice { impl BusDevice for VirtioPciDevice { fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) { - self.read_bar(base, offset, data) + self.read_bar(base, offset, data); } fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index d4739e4999..e1f7dd0d63 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -398,7 +398,7 @@ impl VirtioDevice for Vdpa { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -444,7 +444,7 @@ impl VirtioDevice for Vdpa { } fn set_access_platform(&mut self, access_platform: Arc) { - self.common.set_access_platform(access_platform) + self.common.set_access_platform(access_platform); } } diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 5dab8442af..2c32e0b854 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -244,7 +244,7 @@ impl VirtioDevice for Blk { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -346,7 +346,7 @@ impl VirtioDevice for Blk { } fn shutdown(&mut self) { - self.vu_common.shutdown() + self.vu_common.shutdown(); } fn add_memory_region( diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index ab07aea365..2374a1cc71 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -252,7 +252,7 @@ impl VirtioDevice for Fs { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -326,7 +326,7 @@ impl VirtioDevice for Fs { } fn shutdown(&mut self) { - self.vu_common.shutdown() + self.vu_common.shutdown(); } fn get_shm_regions(&self) -> Option { @@ -361,7 +361,7 @@ impl VirtioDevice for Fs { addr: cache.0.addr, len: cache.0.len, mergeable: false, - }) + }); } mappings diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 2a5d05d7e4..fef399e899 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -282,7 +282,7 @@ impl VirtioDevice for Net { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 364b78a258..38b834a432 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -406,7 +406,7 @@ where } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -414,7 +414,7 @@ where 0 if data.len() == 8 => LittleEndian::write_u64(data, self.cid), 0 if data.len() == 4 => LittleEndian::write_u32(data, (self.cid & 0xffff_ffff) as u32), 4 if data.len() == 4 => { - LittleEndian::write_u32(data, ((self.cid >> 32) & 0xffff_ffff) as u32) + LittleEndian::write_u32(data, ((self.cid >> 32) & 0xffff_ffff) as u32); } _ => warn!( "vsock: virtio-vsock received invalid read request of {} bytes at offset {}", @@ -481,7 +481,7 @@ where } fn set_access_platform(&mut self, access_platform: Arc) { - self.common.set_access_platform(access_platform) + self.common.set_access_platform(access_platform); } } diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index e6957efd63..6d91c6657b 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -476,7 +476,7 @@ impl VsockMuxer { }) .unwrap_or_else(|err| { info!("vsock: error adding local-init connection: {err:?}"); - }) + }); } } diff --git a/virtio-devices/src/watchdog.rs b/virtio-devices/src/watchdog.rs index 25978f252d..124d586f82 100644 --- a/virtio-devices/src/watchdog.rs +++ b/virtio-devices/src/watchdog.rs @@ -321,7 +321,7 @@ impl VirtioDevice for Watchdog { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value) + self.common.ack_features(value); } fn activate( diff --git a/vm-allocator/src/memory_slot.rs b/vm-allocator/src/memory_slot.rs index 63a41d9c53..7bfb55549c 100644 --- a/vm-allocator/src/memory_slot.rs +++ b/vm-allocator/src/memory_slot.rs @@ -23,7 +23,7 @@ impl MemorySlotAllocator { /// Release memory slot for reuse pub fn free_memory_slot(&mut self, slot: u32) { - self.memory_slot_free_list.lock().unwrap().push(slot) + self.memory_slot_free_list.lock().unwrap().push(slot); } /// Instantiate struct diff --git a/vm-allocator/src/system.rs b/vm-allocator/src/system.rs index 0fd159617c..bad0272c37 100644 --- a/vm-allocator/src/system.rs +++ b/vm-allocator/src/system.rs @@ -119,12 +119,12 @@ impl SystemAllocator { /// Free an IO address range. /// We can only free a range if it matches exactly an already allocated range. pub fn free_io_addresses(&mut self, address: GuestAddress, size: GuestUsize) { - self.io_address_space.free(address, size) + self.io_address_space.free(address, size); } /// Free a platform MMIO address range. /// We can only free a range if it matches exactly an already allocated range. pub fn free_platform_mmio_addresses(&mut self, address: GuestAddress, size: GuestUsize) { - self.platform_mmio_address_space.free(address, size) + self.platform_mmio_address_space.free(address, size); } } diff --git a/vm-device/src/bus.rs b/vm-device/src/bus.rs index fc3bbeb464..92916a65b1 100644 --- a/vm-device/src/bus.rs +++ b/vm-device/src/bus.rs @@ -43,7 +43,7 @@ impl BusDeviceSync for Mutex { fn read(&self, base: u64, offset: u64, data: &mut [u8]) { self.lock() .expect("Failed to acquire device lock") - .read(base, offset, data) + .read(base, offset, data); } /// Writes at `offset` into this device fn write(&self, base: u64, offset: u64, data: &[u8]) -> Option> { @@ -275,7 +275,7 @@ mod unit_tests { fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option> { for (i, v) in data.iter().enumerate() { - assert_eq!(*v, (offset as u8) + (i as u8)) + assert_eq!(*v, (offset as u8) + (i as u8)); } None diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index eb2bcb0e6e..bc0d60e965 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -267,7 +267,7 @@ impl MemoryRangeTable { } pub fn push(&mut self, range: MemoryRange) { - self.data.push(range) + self.data.push(range); } pub fn read_from(fd: &mut dyn Read, length: u64) -> Result { @@ -307,7 +307,7 @@ impl MemoryRangeTable { } pub fn extend(&mut self, table: Self) { - self.data.extend(table.data) + self.data.extend(table.data); } pub fn new_from_tables(tables: Vec) -> Self { diff --git a/vm-virtio/src/queue.rs b/vm-virtio/src/queue.rs index c33f6e5996..f33a11f4cc 100644 --- a/vm-virtio/src/queue.rs +++ b/vm-virtio/src/queue.rs @@ -46,7 +46,7 @@ pub mod testing { // Writes to the actual memory location. pub fn set(&self, val: T) { - self.mem.write_obj(val, self.location).unwrap() + self.mem.write_obj(val, self.location).unwrap(); } // This function returns a place in memory which holds a value of type U, and starts @@ -143,7 +143,7 @@ pub mod testing { for _ in 1..qsize as usize { let x = ring.last().unwrap().next_place(); - ring.push(x) + ring.push(x); } let event = ring.last().unwrap().next_place(); diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 5a48782907..f748c87c59 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -309,7 +309,7 @@ fn create_srat_table( region, proximity_domain, MemAffinityFlags::ENABLE, - )) + )); } for region in &node.hotplug_regions { @@ -317,7 +317,7 @@ fn create_srat_table( region, proximity_domain, MemAffinityFlags::ENABLE | MemAffinityFlags::HOTPLUGGABLE, - )) + )); } for cpu in &node.cpus { diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 1ad3cfb30e..8b1f940b0b 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1835,11 +1835,11 @@ impl ConsoleConfig { if parser.is_set("off") { } else if parser.is_set("pty") { - mode = ConsoleOutputMode::Pty + mode = ConsoleOutputMode::Pty; } else if parser.is_set("tty") { - mode = ConsoleOutputMode::Tty + mode = ConsoleOutputMode::Tty; } else if parser.is_set("null") { - mode = ConsoleOutputMode::Null + mode = ConsoleOutputMode::Null; } else if parser.is_set("file") { mode = ConsoleOutputMode::File; file = @@ -1890,11 +1890,11 @@ impl DebugConsoleConfig { if parser.is_set("off") { } else if parser.is_set("pty") { - mode = ConsoleOutputMode::Pty + mode = ConsoleOutputMode::Pty; } else if parser.is_set("tty") { - mode = ConsoleOutputMode::Tty + mode = ConsoleOutputMode::Tty; } else if parser.is_set("null") { - mode = ConsoleOutputMode::Null + mode = ConsoleOutputMode::Null; } else if parser.is_set("file") { mode = ConsoleOutputMode::File; file = @@ -2327,7 +2327,7 @@ impl RestoreConfig { } if !restored_net_with_fds.is_empty() { - warn!("Ignoring unused 'net_fds' for VM restore.") + warn!("Ignoring unused 'net_fds' for VM restore."); } Ok(()) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index fd770af59e..9b52fc264c 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -739,7 +739,7 @@ impl VcpuState { fn join_thread(&mut self) -> Result<()> { if let Some(handle) = self.handle.take() { - handle.join().map_err(Error::ThreadCleanup)? + handle.join().map_err(Error::ThreadCleanup)?; } Ok(()) @@ -747,7 +747,7 @@ impl VcpuState { fn unpark_thread(&self) { if let Some(handle) = self.handle.as_ref() { - handle.thread().unpark() + handle.thread().unpark(); } } } @@ -823,7 +823,7 @@ impl CpuManager { let mut cpu_list = Vec::new(); for (proximity_domain, numa_node) in numa_nodes.iter() { for cpu in numa_node.cpus.iter() { - cpu_list.push((*cpu, *proximity_domain)) + cpu_list.push((*cpu, *proximity_domain)); } } cpu_list @@ -1263,7 +1263,7 @@ impl CpuManager { Ok(details) => match details { TdxExitDetails::GetQuote => warn!("TDG_VP_VMCALL_GET_QUOTE not supported"), TdxExitDetails::SetupEventNotifyInterrupt => { - warn!("TDG_VP_VMCALL_SETUP_EVENT_NOTIFY_INTERRUPT not supported") + warn!("TDG_VP_VMCALL_SETUP_EVENT_NOTIFY_INTERRUPT not supported"); } }, Err(e) => error!("Unexpected TDX VMCALL: {e}"), @@ -1424,7 +1424,7 @@ impl CpuManager { cmp::Ordering::Greater => { let vcpus = self.create_vcpus(desired_vcpus, None)?; for vcpu in vcpus { - self.configure_vcpu(vcpu, None)? + self.configure_vcpu(vcpu, None)?; } self.activate_vcpus(desired_vcpus, true, None)?; Ok(true) @@ -2223,7 +2223,7 @@ impl Aml for CpuNotify { &aml::Equal::new(&aml::Arg(0), &self.cpu_id), vec![&aml::Notify::new(&object, &aml::Arg(1))], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } @@ -2338,9 +2338,9 @@ impl Aml for CpuMethods { &aml::Release::new("\\_SB_.PRES.CPLK".into()), ], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } else { - aml::Method::new("CSCN".into(), 0, true, vec![]).to_aml_bytes(sink) + aml::Method::new("CSCN".into(), 0, true, vec![]).to_aml_bytes(sink); } } } @@ -2435,7 +2435,7 @@ impl Aml for CpuManager { cpu_data_inner.push(cpu_device); } - aml::Device::new("_SB_.CPUS".into(), cpu_data_inner).to_aml_bytes(sink) + aml::Device::new("_SB_.CPUS".into(), cpu_data_inner).to_aml_bytes(sink); } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 3aaa72b779..682379a485 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -688,7 +688,7 @@ impl Console { pub fn update_console_size(&self) { if let Some(resizer) = self.console_resizer.as_ref() { - resizer.update_console_size() + resizer.update_console_size(); } } } @@ -1171,7 +1171,7 @@ impl DeviceManager { if let Some(pci_segments) = &config.lock().unwrap().pci_segments { for pci_segment in pci_segments.iter() { mmio32_aperture_weights[pci_segment.pci_segment as usize] = - pci_segment.mmio32_aperture_weight + pci_segment.mmio32_aperture_weight; } } @@ -1191,7 +1191,7 @@ impl DeviceManager { if let Some(pci_segments) = &config.lock().unwrap().pci_segments { for pci_segment in pci_segments.iter() { mmio64_aperture_weights[pci_segment.pci_segment as usize] = - pci_segment.mmio64_aperture_weight + pci_segment.mmio64_aperture_weight; } } @@ -1460,7 +1460,7 @@ impl DeviceManager { if let Some(tpm) = self.config.clone().lock().unwrap().tpm.as_ref() { let tpm_dev = self.add_tpm_device(tpm.socket.clone())?; self.bus_devices - .push(Arc::clone(&tpm_dev) as Arc) + .push(Arc::clone(&tpm_dev) as Arc); } self.legacy_interrupt_manager = Some(legacy_interrupt_manager); @@ -4606,7 +4606,7 @@ impl DeviceManager { self.mmio_regions .lock() .unwrap() - .retain(|x| x.start != mmio_region.start) + .retain(|x| x.start != mmio_region.start); } ( @@ -5078,7 +5078,7 @@ impl Aml for TpmDevice { ), ], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } @@ -5098,7 +5098,7 @@ impl Aml for DeviceManager { } let mut pci_scan_inner: Vec<&dyn Aml> = Vec::new(); for method in &pci_scan_methods { - pci_scan_inner.push(method) + pci_scan_inner.push(method); } // PCI hotplug controller @@ -5171,7 +5171,7 @@ impl Aml for DeviceManager { true, segment.mmio_config_address as u32, layout::PCI_MMIO_CONFIG_SIZE_PER_SEGMENT as u32, - )) + )); } let mut mbrd_memory_refs = Vec::new(); @@ -5281,7 +5281,7 @@ impl Aml for DeviceManager { .unwrap() .lock() .unwrap() - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } @@ -5432,7 +5432,7 @@ impl BusDevice for DeviceManager { _ => error!("Accessing unknown location at base 0x{base:x}, offset 0x{offset:x}"), } - debug!("PCI_HP_REG_R: base 0x{base:x}, offset 0x{offset:x}, data {data:?}") + debug!("PCI_HP_REG_R: base 0x{base:x}, offset 0x{offset:x}, data {data:?}"); } fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 8c3ffc5e54..7e5a2d8d2a 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1466,7 +1466,7 @@ impl Vmm { // Wait for all the threads to finish for thread in self.threads.drain(..) { - thread.join().map_err(Error::ThreadCleanup)? + thread.join().map_err(Error::ThreadCleanup)?; } Ok(()) @@ -2214,7 +2214,7 @@ impl RequestHandler for Vmm { })?; if existing_memory_files.is_none() { - existing_memory_files = Some(HashMap::default()) + existing_memory_files = Some(HashMap::default()); } if let Some(ref mut existing_memory_files) = existing_memory_files { diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 3f7ef1f257..947fd41f3f 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -2131,7 +2131,7 @@ impl Aml for MemoryNotify { &aml::Equal::new(&aml::Arg(0), &self.slot_id), vec![&aml::Notify::new(&object, &aml::Arg(1))], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } @@ -2178,7 +2178,7 @@ impl Aml for MemorySlot { ), ], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } @@ -2361,7 +2361,7 @@ impl Aml for MemoryMethods { &aml::Return::new(&aml::Path::new("MR64")), ], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index 345869c1da..c31a60a226 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -227,7 +227,7 @@ impl Aml for PciDevSlot { ), ], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } @@ -281,7 +281,7 @@ impl Aml for PciDevSlotMethods { &aml::Release::new("\\_SB_.PHPR.BLCK".into()), ], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } @@ -344,7 +344,7 @@ impl Aml for PciDsmMethod { &aml::Return::new(&aml::BufferData::new(vec![0])), ], ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } @@ -470,6 +470,6 @@ impl Aml for PciSegment { format!("_SB_.PC{:02X}", self.id).as_str().into(), pci_dsdt_inner_data, ) - .to_aml_bytes(sink) + .to_aml_bytes(sink); } } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 137528c82b..9837e71ffe 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1627,7 +1627,7 @@ impl Vm { // Wait for all the threads to finish for thread in self.threads.drain(..) { - thread.join().map_err(Error::ThreadCleanup)? + thread.join().map_err(Error::ThreadCleanup)?; } *state = new_state; @@ -2350,7 +2350,7 @@ impl Vm { &self.memory_manager, &self.numa_nodes, tpm_enabled, - )? + )?; } } } From fed010fcd195c7e50ee0cd39d73733b9ea4ed5c6 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 12:27:30 +0100 Subject: [PATCH 0305/1893] misc: clippy: add manual_string_new Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 1 + hypervisor/src/arch/emulator/mod.rs | 4 ++-- performance-metrics/src/main.rs | 6 +++--- tests/integration.rs | 14 +++++++------- vmm/src/config.rs | 4 ++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6953818920..65ff1a30a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -171,6 +171,7 @@ suspicious = "deny" # Individual Lints assertions_on_result_states = "deny" +manual_string_new = "deny" semicolon_if_nothing_returned = "deny" undocumented_unsafe_blocks = "deny" uninlined_format_args = "deny" diff --git a/hypervisor/src/arch/emulator/mod.rs b/hypervisor/src/arch/emulator/mod.rs index a882fc8e82..472b712d54 100644 --- a/hypervisor/src/arch/emulator/mod.rs +++ b/hypervisor/src/arch/emulator/mod.rs @@ -26,10 +26,10 @@ impl Display for Exception { self.ip, self.error .map(|e| format!(": error {e:x}")) - .unwrap_or_else(|| "".to_owned()), + .unwrap_or_default(), self.payload .map(|payload| format!(": payload {payload:x}")) - .unwrap_or_else(|| "".to_owned()) + .unwrap_or_default() ) } } diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index b3f1ae018c..1b374a573f 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -48,7 +48,7 @@ pub struct MetricsReport { impl Default for MetricsReport { fn default() -> Self { - let mut git_human_readable = "".to_string(); + let mut git_human_readable = String::new(); if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() { if git_out.status.success() { git_human_readable = String::from_utf8(git_out.stdout) @@ -63,7 +63,7 @@ impl Default for MetricsReport { } } - let mut git_revision = "".to_string(); + let mut git_revision = String::new(); if let Ok(git_out) = Command::new("git").args(["rev-parse", "HEAD"]).output() { if git_out.status.success() { git_revision = String::from_utf8(git_out.stdout) @@ -78,7 +78,7 @@ impl Default for MetricsReport { } } - let mut git_commit_date = "".to_string(); + let mut git_commit_date = String::new(); if let Ok(git_out) = Command::new("git") .args(["show", "-s", "--format=%cd"]) .output() diff --git a/tests/integration.rs b/tests/integration.rs index 06a0242dd3..4d602bcdb7 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1237,7 +1237,7 @@ fn test_vhost_user_net( if let Some(host_mac) = host_mac { format!(",host_mac={host_mac}") } else { - "".to_owned() + String::new() }, if client_mode_daemon { "server" @@ -1636,7 +1636,7 @@ fn _test_virtio_fs( if let Some(pci_segment) = pci_segment { format!(",pci_segment={pci_segment}") } else { - "".to_owned() + String::new() } ); @@ -1730,7 +1730,7 @@ fn _test_virtio_fs( if let Some(pci_segment) = pci_segment { format!(",pci_segment={pci_segment}") } else { - "".to_owned() + String::new() } ); @@ -2019,7 +2019,7 @@ fn _get_vmm_overhead(pid: u32, guest_memory_size: u32) -> HashMap { let reader = io::BufReader::new(smaps); let mut skip_map: bool = false; - let mut region_name: String = "".to_string(); + let mut region_name: String = String::new(); let mut region_maps = HashMap::new(); for line in reader.lines() { let l = line.unwrap(); @@ -5977,7 +5977,7 @@ mod common_parallel { if let Some(pci_segment) = pci_segment { format!(",pci_segment={pci_segment}") } else { - "".to_owned() + String::new() } )), ); @@ -6109,7 +6109,7 @@ mod common_parallel { if let Some(pci_segment) = pci_segment { format!(",pci_segment={pci_segment}") } else { - "".to_owned() + String::new() } ) .as_str(), @@ -6172,7 +6172,7 @@ mod common_parallel { if let Some(pci_segment) = pci_segment { format!(",pci_segment={pci_segment}") } else { - "".to_owned() + String::new() } ) .as_str(), diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 8b1f940b0b..8378a7a310 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -4681,7 +4681,7 @@ mod unit_tests { // Test empty string serial (should be valid) let mut empty_serial_config = valid_config.clone(); empty_serial_config.disks = Some(vec![DiskConfig { - serial: Some("".to_string()), + serial: Some(String::new()), ..disk_fixture() }]); empty_serial_config.validate().unwrap(); @@ -4755,7 +4755,7 @@ mod unit_tests { #[cfg(feature = "igvm")] igvm: None, #[cfg(feature = "sev_snp")] - host_data: Some("".to_string()), + host_data: Some(String::new()), #[cfg(feature = "fw_cfg")] fw_cfg_config: None, }); From f02745a7ed804738c46404e6e479b69e8a36baeb Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 20 Nov 2025 09:55:05 +0100 Subject: [PATCH 0306/1893] vmm: unrelated small code improvements Unfortunately, there is no lint for that. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 7e5a2d8d2a..7343206830 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1874,9 +1874,8 @@ impl RequestHandler for Vmm { } if let Some(ref mut vm) = self.vm { - let info = vm.add_device(device_cfg).map_err(|e| { + let info = vm.add_device(device_cfg).inspect_err(|e| { error!("Error when adding new device to the VM: {e:?}"); - e })?; serde_json::to_vec(&info) .map(Some) @@ -1903,9 +1902,8 @@ impl RequestHandler for Vmm { } if let Some(ref mut vm) = self.vm { - let info = vm.add_user_device(device_cfg).map_err(|e| { + let info = vm.add_user_device(device_cfg).inspect_err(|e| { error!("Error when adding new user device to the VM: {e:?}"); - e })?; serde_json::to_vec(&info) .map(Some) From 95b8c6afdd6eec9810243f92ec1956dccfe305da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20N=C3=BCrnberger?= Date: Fri, 21 Nov 2025 15:46:59 +0100 Subject: [PATCH 0307/1893] seccomp: allow sendto for vfio_user devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit as of rust 1.90, writes to unix sockets use the sendto syscall. This affects the vcpu threads when vfio_user devices are accessed. Signed-off-by: Stefan Nürnberger --- vmm/src/seccomp_filters.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 2b0016fa58..c78c97f4e0 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -810,6 +810,7 @@ fn vcpu_thread_rules( (libc::SYS_rt_sigreturn, vec![]), (libc::SYS_sched_yield, vec![]), (libc::SYS_sendmsg, vec![]), + (libc::SYS_sendto, vec![]), (libc::SYS_shutdown, vec![]), (libc::SYS_sigaltstack, vec![]), (libc::SYS_tgkill, vec![]), From b6032bc4929168879cc449b5c10111146970d576 Mon Sep 17 00:00:00 2001 From: Thomas Prescher Date: Fri, 21 Nov 2025 14:44:09 +0100 Subject: [PATCH 0308/1893] arch: fix extended topology enumeration subleafs When booting a Linux guest in SMP configuration, on sapphire rapids and granite rapids the following kernel warning can be observed: [Firmware Bug]: CPUID leaf 0x1f subleaf 1 APIC ID mismatch 1 != 0 [Firmware Bug]: CPUID leaf 0x1f subleaf 2 APIC ID mismatch 1 != 0 The reason is that we announce the presence of the extended topology leaf, but fail to announce the x2apic ID in EDX for each subleaf. Signed-off-by: Thomas Prescher On-behalf-of: SAP thomas.prescher@sap.com --- arch/src/x86_64/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index edc3629921..51c2d02f07 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -1347,6 +1347,7 @@ fn update_cpuid_topology( u32::from(threads_per_core), ); CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::ECX, 1 << 8); + CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::EDX, x2apic_id); CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::EAX, core_width); CpuidPatch::set_cpuid_reg( @@ -1357,6 +1358,7 @@ fn update_cpuid_topology( u32::from(cores_per_die * threads_per_core), ); CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::ECX, 2 << 8); + CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::EDX, x2apic_id); CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::EAX, die_width); CpuidPatch::set_cpuid_reg( @@ -1367,6 +1369,7 @@ fn update_cpuid_topology( u32::from(dies_per_package * cores_per_die * threads_per_core), ); CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::ECX, 5 << 8); + CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::EDX, x2apic_id); if matches!(cpu_vendor, CpuVendor::AMD) { CpuidPatch::set_cpuid_reg( From 0ff8d1cb2877c659ca8db630dd6e811e7f1e080d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 23:18:32 +0000 Subject: [PATCH 0309/1893] build: Bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [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/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/audit.yaml | 2 +- .github/workflows/build.yaml | 2 +- .github/workflows/dco.yaml | 2 +- .github/workflows/docker-image.yaml | 2 +- .github/workflows/formatting.yaml | 2 +- .github/workflows/fuzz-build.yaml | 2 +- .github/workflows/gitlint.yaml | 2 +- .github/workflows/hadolint.yaml | 2 +- .github/workflows/integration-arm64.yaml | 2 +- .github/workflows/integration-metrics.yaml | 2 +- .github/workflows/integration-rate-limiter.yaml | 2 +- .github/workflows/integration-vfio.yaml | 2 +- .github/workflows/integration-windows.yaml | 2 +- .github/workflows/integration-x86-64.yaml | 2 +- .github/workflows/lychee.yaml | 2 +- .github/workflows/openapi.yaml | 2 +- .github/workflows/package-consistency.yaml | 2 +- .github/workflows/preview-riscv64-build.yaml | 2 +- .github/workflows/preview-riscv64-modules.yaml | 2 +- .github/workflows/quality.yaml | 4 ++-- .github/workflows/release.yaml | 2 +- .github/workflows/reuse.yaml | 2 +- .github/workflows/shlint.yaml | 2 +- .github/workflows/taplo.yaml | 2 +- 24 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/audit.yaml b/.github/workflows/audit.yaml index cfc21696e1..bab8eaa145 100644 --- a/.github/workflows/audit.yaml +++ b/.github/workflows/audit.yaml @@ -10,7 +10,7 @@ jobs: name: Audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions-rust-lang/audit@v1 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2149f2e881..c7147fc3f4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: - x86_64-unknown-linux-musl steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/dco.yaml b/.github/workflows/dco.yaml index daf21315e0..98412e5e22 100644 --- a/.github/workflows/dco.yaml +++ b/.github/workflows/dco.yaml @@ -6,7 +6,7 @@ jobs: name: DCO Check ("Signed-Off-By") runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python 3.x uses: actions/setup-python@v6 with: diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 3696ae97d6..da2bbf5d3a 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/formatting.yaml b/.github/workflows/formatting.yaml index 75e4492559..00cd322797 100644 --- a/.github/workflows/formatting.yaml +++ b/.github/workflows/formatting.yaml @@ -19,7 +19,7 @@ jobs: RUSTFLAGS: -D warnings steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain (${{ matrix.rust }}) uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/fuzz-build.yaml b/.github/workflows/fuzz-build.yaml index 427189b01e..b97796893d 100644 --- a/.github/workflows/fuzz-build.yaml +++ b/.github/workflows/fuzz-build.yaml @@ -18,7 +18,7 @@ jobs: RUSTFLAGS: -D warnings steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain (${{ matrix.rust }}) uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/gitlint.yaml b/.github/workflows/gitlint.yaml index 7c3c4f7e45..6fd0ec4ab9 100644 --- a/.github/workflows/gitlint.yaml +++ b/.github/workflows/gitlint.yaml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 diff --git a/.github/workflows/hadolint.yaml b/.github/workflows/hadolint.yaml index 641d911c0c..631c50eefd 100644 --- a/.github/workflows/hadolint.yaml +++ b/.github/workflows/hadolint.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Lint Dockerfile uses: hadolint/hadolint-action@master diff --git a/.github/workflows/integration-arm64.yaml b/.github/workflows/integration-arm64.yaml index 41a7bc824a..f48c0ec126 100644 --- a/.github/workflows/integration-arm64.yaml +++ b/.github/workflows/integration-arm64.yaml @@ -13,7 +13,7 @@ jobs: - name: Fix workspace permissions run: sudo chown -R runner:runner ${GITHUB_WORKSPACE} - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Run unit tests (musl) diff --git a/.github/workflows/integration-metrics.yaml b/.github/workflows/integration-metrics.yaml index e8dd72ea84..952e938fdf 100644 --- a/.github/workflows/integration-metrics.yaml +++ b/.github/workflows/integration-metrics.yaml @@ -12,7 +12,7 @@ jobs: METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }} steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Run metrics tests diff --git a/.github/workflows/integration-rate-limiter.yaml b/.github/workflows/integration-rate-limiter.yaml index 91682f77f8..94497f47bd 100644 --- a/.github/workflows/integration-rate-limiter.yaml +++ b/.github/workflows/integration-rate-limiter.yaml @@ -13,7 +13,7 @@ jobs: steps: - name: Code checkout if: ${{ github.event_name != 'pull_request' }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Run rate-limiter integration tests diff --git a/.github/workflows/integration-vfio.yaml b/.github/workflows/integration-vfio.yaml index edd7399b15..416e99ee95 100644 --- a/.github/workflows/integration-vfio.yaml +++ b/.github/workflows/integration-vfio.yaml @@ -16,7 +16,7 @@ jobs: run: sudo chown -R runner:runner ${GITHUB_WORKSPACE} - name: Code checkout if: ${{ github.event_name != 'pull_request' }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Run VFIO integration tests diff --git a/.github/workflows/integration-windows.yaml b/.github/workflows/integration-windows.yaml index 0769789a9d..81ed017550 100644 --- a/.github/workflows/integration-windows.yaml +++ b/.github/workflows/integration-windows.yaml @@ -11,7 +11,7 @@ jobs: steps: - name: Code checkout if: ${{ github.event_name != 'pull_request' }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install Docker diff --git a/.github/workflows/integration-x86-64.yaml b/.github/workflows/integration-x86-64.yaml index 8ed76f16a1..453d7c2d32 100644 --- a/.github/workflows/integration-x86-64.yaml +++ b/.github/workflows/integration-x86-64.yaml @@ -17,7 +17,7 @@ jobs: steps: - name: Code checkout if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install Docker diff --git a/.github/workflows/lychee.yaml b/.github/workflows/lychee.yaml index 8d1d3927c0..e77c595ed3 100644 --- a/.github/workflows/lychee.yaml +++ b/.github/workflows/lychee.yaml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: # Fetch the entire history so git diff can compare against the base branch fetch-depth: 0 diff --git a/.github/workflows/openapi.yaml b/.github/workflows/openapi.yaml index 9c1266e4d7..4c179f7e82 100644 --- a/.github/workflows/openapi.yaml +++ b/.github/workflows/openapi.yaml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest container: openapitools/openapi-generator-cli steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Validate OpenAPI env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package-consistency.yaml b/.github/workflows/package-consistency.yaml index 719aa3d8df..9c5eb5c0e4 100644 --- a/.github/workflows/package-consistency.yaml +++ b/.github/workflows/package-consistency.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/preview-riscv64-build.yaml b/.github/workflows/preview-riscv64-build.yaml index cdcfd9638e..baed1741d6 100644 --- a/.github/workflows/preview-riscv64-build.yaml +++ b/.github/workflows/preview-riscv64-build.yaml @@ -13,7 +13,7 @@ jobs: steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/preview-riscv64-modules.yaml b/.github/workflows/preview-riscv64-modules.yaml index 8c2baa0492..ffb4fd4b67 100644 --- a/.github/workflows/preview-riscv64-modules.yaml +++ b/.github/workflows/preview-riscv64-modules.yaml @@ -19,7 +19,7 @@ jobs: steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index e60f3dfb5f..b38f9205a5 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -29,7 +29,7 @@ jobs: steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -165,6 +165,6 @@ jobs: name: Typos / Spellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # Executes "typos ." - uses: crate-ci/typos@v1.39.2 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2c26d8bb94..411684b758 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install musl-gcc if: contains(matrix.platform.target, 'musl') run: sudo apt install -y musl-tools diff --git a/.github/workflows/reuse.yaml b/.github/workflows/reuse.yaml index 9977759e01..39ae873011 100644 --- a/.github/workflows/reuse.yaml +++ b/.github/workflows/reuse.yaml @@ -7,6 +7,6 @@ jobs: name: REUSE Compliance Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: REUSE Compliance Check uses: fsfe/reuse-action@v6 diff --git a/.github/workflows/shlint.yaml b/.github/workflows/shlint.yaml index b9208f3f20..068b9930ed 100644 --- a/.github/workflows/shlint.yaml +++ b/.github/workflows/shlint.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Run the shell script checkers uses: luizm/action-sh-checker@master env: diff --git a/.github/workflows/taplo.yaml b/.github/workflows/taplo.yaml index 75b61d9236..24577878bd 100644 --- a/.github/workflows/taplo.yaml +++ b/.github/workflows/taplo.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Code checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Install build dependencies From 00f0b9e42c871a3625bfaf2e0e77eae7e28d0dd7 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 22 Nov 2025 01:40:25 -0500 Subject: [PATCH 0310/1893] vmm: Fix clippy lints on RISC-V These caused CI failures in #7129. No functional change. Signed-off-by: Demi Marie Obenour --- vmm/src/cpu.rs | 2 +- vmm/src/device_manager.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 9b52fc264c..5d35cab915 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -1702,7 +1702,7 @@ impl CpuManager { n_supervisor_int_id: arch::riscv64::CLOUDHV_IRQCHIP_NUM_MSIS, n_guest_int_id: arch::riscv64::CLOUDHV_IRQCHIP_NUM_MSIS, geust_index_bits: 1, - hart_index_bits: hart_index_bits, + hart_index_bits, group_index_bits: 1, // IMSIC_MMIO_GROUP_MIN_SHIFT group_index_shift: 24, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 682379a485..c56d5f27cd 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1741,7 +1741,7 @@ impl DeviceManager { ) -> DeviceManagerResult>> { let interrupt_controller: Arc> = Arc::new(Mutex::new( aia::Aia::new( - self.config.lock().unwrap().cpus.boot_vcpus as u32, + self.config.lock().unwrap().cpus.boot_vcpus, Arc::clone(&self.msi_interrupt_manager), self.address_manager.vm.clone(), ) From fdc19ad85e9aac085610b66c2645b8a7050bd2c9 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 13 Jun 2025 14:27:00 -0400 Subject: [PATCH 0311/1893] misc: Mark memory region APIs as unsafe To ensure that struct sizes are the same on 32-bit and 64-bit, various kernel APIs use __u64 (Rust u64) to represent userspace pointers. Userspace is expected to cast pointers to __u64 before passing them to the kernel, and cast kernel-provided __u64 to a pointer before using them. However, various safe APIs in Cloud Hypervisor took caller-provided u64 values and passed them to syscalls that treat them as userspace addresses. Therefore, passing bad u64 values would cause memory disclosure or corruption. The memory region APIs are one example of this, so mark them as unsafe. Signed-off-by: Demi Marie Obenour --- hypervisor/src/kvm/mod.rs | 132 ++++++++++++++----------------- hypervisor/src/lib.rs | 21 ----- hypervisor/src/mshv/mod.rs | 117 ++++++++++++---------------- hypervisor/src/vm.rs | 29 +++++-- pci/src/vfio.rs | 98 +++++++++++------------ pci/src/vfio_user.rs | 101 ++++++++++++------------ vmm/src/api/dbus/mod.rs | 4 +- vmm/src/config.rs | 2 +- vmm/src/device_manager.rs | 156 +++++++++++++++++++++---------------- vmm/src/lib.rs | 4 +- vmm/src/memory_manager.rs | 129 +++++++++++++++++------------- vmm/src/vm.rs | 89 ++++++++++++++++++--- 12 files changed, 482 insertions(+), 400 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 6bbd4839e8..53de9bad36 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -69,11 +69,7 @@ use crate::ClockData; use crate::arch::x86::{ CpuIdEntry, FpuState, LapicState, MsrEntry, NUM_IOAPIC_PINS, SpecialRegisters, XsaveState, }; -use crate::{ - CpuState, IoEventAddress, IrqRoutingEntry, MpState, StandardRegisters, - USER_MEMORY_REGION_LOG_DIRTY, USER_MEMORY_REGION_READ, USER_MEMORY_REGION_WRITE, - UserMemoryRegion, -}; +use crate::{CpuState, IoEventAddress, IrqRoutingEntry, MpState, StandardRegisters}; // aarch64 dependencies #[cfg(target_arch = "aarch64")] pub mod aarch64; @@ -234,51 +230,6 @@ pub struct KvmTdxExitVmcall { pub out_rdx: u64, } -impl From for UserMemoryRegion { - fn from(region: kvm_userspace_memory_region) -> Self { - let mut flags = USER_MEMORY_REGION_READ; - if region.flags & KVM_MEM_READONLY == 0 { - flags |= USER_MEMORY_REGION_WRITE; - } - if region.flags & KVM_MEM_LOG_DIRTY_PAGES != 0 { - flags |= USER_MEMORY_REGION_LOG_DIRTY; - } - - UserMemoryRegion { - slot: region.slot, - guest_phys_addr: region.guest_phys_addr, - memory_size: region.memory_size, - userspace_addr: region.userspace_addr, - flags, - } - } -} - -impl From for kvm_userspace_memory_region { - fn from(region: UserMemoryRegion) -> Self { - assert!( - region.flags & USER_MEMORY_REGION_READ != 0, - "KVM mapped memory is always readable" - ); - - let mut flags = 0; - if region.flags & USER_MEMORY_REGION_WRITE == 0 { - flags |= KVM_MEM_READONLY; - } - if region.flags & USER_MEMORY_REGION_LOG_DIRTY != 0 { - flags |= KVM_MEM_LOG_DIRTY_PAGES; - } - - kvm_userspace_memory_region { - slot: region.slot, - guest_phys_addr: region.guest_phys_addr, - memory_size: region.memory_size, - userspace_addr: region.userspace_addr, - flags, - } - } -} - impl From for MpState { fn from(s: kvm_mp_state) -> Self { MpState::Kvm(s) @@ -758,10 +709,17 @@ impl vm::Vm for KvmVm { .map_err(|e| vm::HypervisorVmError::SetGsiRouting(e.into())) } + /// Creates a guest physical memory region. /// - /// Creates a memory region structure that can be used with {create/remove}_user_memory_region + /// # Safety /// - fn make_user_memory_region( + /// `userspace_addr` must point to `memory_size` bytes of memory + /// that will stay mapped until a successful call to + /// `remove_user_memory_region().` Freeing them with `munmap()` + /// before then will cause undefined guest behavior but at least + /// should not cause undefined behavior in the host. In theory, + /// at least. + unsafe fn create_user_memory_region( &self, slot: u32, guest_phys_addr: u64, @@ -769,27 +727,24 @@ impl vm::Vm for KvmVm { userspace_addr: u64, readonly: bool, log_dirty_pages: bool, - ) -> UserMemoryRegion { - kvm_userspace_memory_region { + ) -> vm::Result<()> { + let mut flags = 0; + if readonly { + flags |= KVM_MEM_READONLY; + } + if log_dirty_pages { + flags |= KVM_MEM_LOG_DIRTY_PAGES; + } + + const _: () = assert!(core::mem::size_of::() <= core::mem::size_of::()); + + let mut region = kvm_userspace_memory_region { slot, guest_phys_addr, memory_size, userspace_addr, - flags: if readonly { KVM_MEM_READONLY } else { 0 } - | if log_dirty_pages { - KVM_MEM_LOG_DIRTY_PAGES - } else { - 0 - }, - } - .into() - } - - /// - /// Creates a guest physical memory region. - /// - fn create_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> vm::Result<()> { - let mut region: kvm_userspace_memory_region = user_memory_region.into(); + flags, + }; if (region.flags & KVM_MEM_LOG_DIRTY_PAGES) != 0 { if (region.flags & KVM_MEM_READONLY) != 0 { @@ -814,7 +769,7 @@ impl vm::Vm for KvmVm { region.flags = 0; } - // SAFETY: Safe because guest regions are guaranteed not to overlap. + // SAFETY: Safe because caller promised this is safe. unsafe { self.fd .set_user_memory_region(region) @@ -822,18 +777,45 @@ impl vm::Vm for KvmVm { } } - /// /// Removes a guest physical memory region. /// - fn remove_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> vm::Result<()> { - let mut region: kvm_userspace_memory_region = user_memory_region.into(); + /// # Safety + /// + /// `userspace_addr` must point to `memory_size` bytes of memory, + /// and `add_user_memory_region()` must have been successfully called. + unsafe fn remove_user_memory_region( + &self, + slot: u32, + guest_phys_addr: u64, + memory_size: u64, + userspace_addr: u64, + readonly: bool, + log_dirty_pages: bool, + ) -> vm::Result<()> { + let mut flags = 0; + if readonly { + flags |= KVM_MEM_READONLY; + } + if log_dirty_pages { + flags |= KVM_MEM_LOG_DIRTY_PAGES; + } + + const _: () = assert!(core::mem::size_of::() <= core::mem::size_of::()); + + let mut region = kvm_userspace_memory_region { + slot, + guest_phys_addr, + memory_size, + userspace_addr, + flags, + }; // Remove the corresponding entry from "self.dirty_log_slots" if needed self.dirty_log_slots.write().unwrap().remove(®ion.slot); // Setting the size to 0 means "remove" region.memory_size = 0; - // SAFETY: Safe because guest regions are guaranteed not to overlap. + // SAFETY: Safe because caller promised this is safe. unsafe { self.fd .set_user_memory_region(region) @@ -1211,7 +1193,7 @@ impl hypervisor::Hypervisor for KvmHypervisor { vm_type = KVM_X86_SW_PROTECTED_VM.into(); } else { vm_type = KVM_X86_DEFAULT_VM.into(); - }; + } loop { match self.kvm.create_vm_with_type(vm_type) { diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index d9448c8c61..607166968e 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -123,27 +123,6 @@ pub fn vec_with_array_field(count: usize) -> Vec { vec_with_size_in_bytes(vec_size_bytes) } -/// -/// User memory region structure -/// -#[derive(Debug, Default, Eq, PartialEq)] -pub struct UserMemoryRegion { - pub slot: u32, - pub guest_phys_addr: u64, - pub memory_size: u64, - pub userspace_addr: u64, - pub flags: u32, -} - -/// -/// Flags for user memory region -/// -pub const USER_MEMORY_REGION_READ: u32 = 1; -pub const USER_MEMORY_REGION_WRITE: u32 = 1 << 1; -pub const USER_MEMORY_REGION_EXECUTE: u32 = 1 << 2; -pub const USER_MEMORY_REGION_LOG_DIRTY: u32 = 1 << 3; -pub const USER_MEMORY_REGION_ADJUSTABLE: u32 = 1 << 4; - #[derive(Debug)] pub enum MpState { #[cfg(feature = "kvm")] diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 92ef648b69..1fe60d3689 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -74,35 +74,10 @@ use crate::arch::aarch64::gic::{Vgic, VgicConfig}; use crate::arch::aarch64::regs; #[cfg(target_arch = "x86_64")] use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry}; -use crate::{ - CpuState, IoEventAddress, IrqRoutingEntry, MpState, USER_MEMORY_REGION_ADJUSTABLE, - USER_MEMORY_REGION_EXECUTE, USER_MEMORY_REGION_READ, USER_MEMORY_REGION_WRITE, - UserMemoryRegion, -}; +use crate::{CpuState, IoEventAddress, IrqRoutingEntry, MpState}; pub const PAGE_SHIFT: usize = 12; -impl From for UserMemoryRegion { - fn from(region: mshv_user_mem_region) -> Self { - let mut flags: u32 = USER_MEMORY_REGION_READ | USER_MEMORY_REGION_ADJUSTABLE; - if region.flags & (1 << MSHV_SET_MEM_BIT_WRITABLE) != 0 { - flags |= USER_MEMORY_REGION_WRITE; - } - if region.flags & (1 << MSHV_SET_MEM_BIT_EXECUTABLE) != 0 { - flags |= USER_MEMORY_REGION_EXECUTE; - } - - UserMemoryRegion { - guest_phys_addr: (region.guest_pfn << PAGE_SHIFT as u64) - + (region.userspace_addr & ((1 << PAGE_SHIFT) - 1)), - memory_size: region.size, - userspace_addr: region.userspace_addr, - flags, - ..Default::default() - } - } -} - #[cfg(target_arch = "x86_64")] impl From for ClockData { fn from(d: MshvClockData) -> Self { @@ -122,26 +97,6 @@ impl From for MshvClockData { } } -impl From for mshv_user_mem_region { - fn from(region: UserMemoryRegion) -> Self { - let mut flags: u8 = 0; - if region.flags & USER_MEMORY_REGION_WRITE != 0 { - flags |= 1 << MSHV_SET_MEM_BIT_WRITABLE; - } - if region.flags & USER_MEMORY_REGION_EXECUTE != 0 { - flags |= 1 << MSHV_SET_MEM_BIT_EXECUTABLE; - } - - mshv_user_mem_region { - guest_pfn: region.guest_phys_addr >> PAGE_SHIFT, - size: region.memory_size, - userspace_addr: region.userspace_addr, - flags, - ..Default::default() - } - } -} - impl From for IoEventAddress { fn from(a: mshv_ioctls::IoEventAddress) -> Self { match a { @@ -1918,8 +1873,36 @@ impl vm::Vm for MshvVm { } /// Creates a guest physical memory region. - fn create_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> vm::Result<()> { - let user_memory_region: mshv_user_mem_region = user_memory_region.into(); + /// + /// # Safety + /// + /// `userspace_addr` must point to `memory_size` bytes of memory + /// that will stay mapped until a successful call to + /// `remove_user_memory_region().` Freeing them with `munmap()` + /// before then will cause undefined guest behavior but at least + /// should not cause undefined behavior in the host. In theory, + /// at least. + unsafe fn create_user_memory_region( + &self, + _slot: u32, + guest_phys_addr: u64, + memory_size: u64, + userspace_addr: u64, + readonly: bool, + _log_dirty_pages: bool, + ) -> vm::Result<()> { + let mut flags = 1 << MSHV_SET_MEM_BIT_EXECUTABLE; + if !readonly { + flags |= 1 << MSHV_SET_MEM_BIT_WRITABLE; + } + + let user_memory_region = mshv_user_mem_region { + flags, + guest_pfn: guest_phys_addr >> PAGE_SHIFT, + size: memory_size, + userspace_addr, + ..Default::default() + }; // No matter read only or not we keep track the slots. // For readonly hypervisor can enable the dirty bits, // but a VM exit happens before setting the dirty bits @@ -1938,21 +1921,12 @@ impl vm::Vm for MshvVm { } /// Removes a guest physical memory region. - fn remove_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> vm::Result<()> { - let user_memory_region: mshv_user_mem_region = user_memory_region.into(); - // Remove the corresponding entry from "self.dirty_log_slots" if needed - self.dirty_log_slots - .write() - .unwrap() - .remove(&user_memory_region.guest_pfn); - - self.fd - .unmap_user_memory(user_memory_region) - .map_err(|e| vm::HypervisorVmError::RemoveUserMemory(e.into()))?; - Ok(()) - } - - fn make_user_memory_region( + /// + /// # Safety + /// + /// `userspace_addr` must point to `memory_size` bytes of memory, + /// and `add_user_memory_region()` must have been successfully called. + unsafe fn remove_user_memory_region( &self, _slot: u32, guest_phys_addr: u64, @@ -1960,20 +1934,29 @@ impl vm::Vm for MshvVm { userspace_addr: u64, readonly: bool, _log_dirty_pages: bool, - ) -> UserMemoryRegion { + ) -> vm::Result<()> { let mut flags = 1 << MSHV_SET_MEM_BIT_EXECUTABLE; if !readonly { flags |= 1 << MSHV_SET_MEM_BIT_WRITABLE; } - mshv_user_mem_region { + let user_memory_region = mshv_user_mem_region { flags, guest_pfn: guest_phys_addr >> PAGE_SHIFT, size: memory_size, userspace_addr, ..Default::default() - } - .into() + }; + // Remove the corresponding entry from "self.dirty_log_slots" if needed + self.dirty_log_slots + .write() + .unwrap() + .remove(&user_memory_region.guest_pfn); + + self.fd + .unmap_user_memory(user_memory_region) + .map_err(|e| vm::HypervisorVmError::RemoveUserMemory(e.into()))?; + Ok(()) } fn create_passthrough_device(&self) -> vm::Result { diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 7a0d1b6af6..a01e499a77 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -29,7 +29,7 @@ use crate::arch::riscv64::aia::{Vaia, VaiaConfig}; #[cfg(feature = "tdx")] use crate::arch::x86::CpuIdEntry; use crate::cpu::Vcpu; -use crate::{IoEventAddress, IrqRoutingEntry, UserMemoryRegion}; +use crate::{IoEventAddress, IrqRoutingEntry}; /// /// I/O events data matches (32 or 64 bits). @@ -335,8 +335,13 @@ pub trait Vm: Send + Sync + Any { fn make_routing_entry(&self, gsi: u32, config: &InterruptSourceConfig) -> IrqRoutingEntry; /// Sets the GSI routing table entries, overwriting any previously set fn set_gsi_routing(&self, entries: &[IrqRoutingEntry]) -> Result<()>; - /// Creates a memory region structure that can be used with {create/remove}_user_memory_region - fn make_user_memory_region( + /// Creates a guest physical memory slot. + /// + /// # Safety + /// + /// `[userspace_addr, userspace_addr + memory_size)` must be valid memory, + /// and that address range must remain valid until [`Vm::remove_user_memory_region`] is called. + unsafe fn create_user_memory_region( &self, slot: u32, guest_phys_addr: u64, @@ -344,11 +349,21 @@ pub trait Vm: Send + Sync + Any { userspace_addr: u64, readonly: bool, log_dirty_pages: bool, - ) -> UserMemoryRegion; - /// Creates a guest physical memory slot. - fn create_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> Result<()>; + ) -> Result<()>; /// Removes a guest physical memory slot. - fn remove_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> Result<()>; + /// + /// # Safety + /// + /// `[userspace_addr, userspace_addr + memory_size)` must be valid memory, + unsafe fn remove_user_memory_region( + &self, + slot: u32, + guest_phys_addr: u64, + memory_size: u64, + userspace_addr: u64, + readonly: bool, + log_dirty_pages: bool, + ) -> Result<()>; /// Returns the preferred CPU target type which can be emulated by KVM on underlying host. #[cfg(target_arch = "aarch64")] fn get_preferred_target(&self, kvi: &mut crate::VcpuInit) -> Result<()>; diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 2c07f309c0..3d59293ae3 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -1663,20 +1663,21 @@ impl VfioPciDevice { host_addr: host_addr as u64, }; - region.user_memory_regions.push(user_memory_region); - - let mem_region = self.vm.make_user_memory_region( - user_memory_region.slot, - user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, - false, - false, - ); + // SAFETY: host_addr was allocated by mmap() and points to size + // bytes of memory. + unsafe { + self.vm.create_user_memory_region( + user_memory_region.slot, + user_memory_region.start, + user_memory_region.size, + user_memory_region.host_addr, + false, + false, + ) + } + .map_err(VfioPciError::CreateUserMemoryRegion)?; - self.vm - .create_user_memory_region(mem_region) - .map_err(VfioPciError::CreateUserMemoryRegion)?; + region.user_memory_regions.push(user_memory_region); if !self.iommu_attached { self.container @@ -1714,16 +1715,18 @@ impl VfioPciDevice { } // Remove region - let r = self.vm.make_user_memory_region( - user_memory_region.slot, - user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, - false, - false, - ); - - if let Err(e) = self.vm.remove_user_memory_region(r) { + // SAFETY: only valid entries are added to the user_memory_regions field + // of the entries of self.common.mmio_regions. + if let Err(e) = unsafe { + self.vm.remove_user_memory_region( + user_memory_region.slot, + user_memory_region.start, + user_memory_region.size, + user_memory_region.host_addr, + false, + false, + ) + } { error!("Could not remove the userspace memory region: {e}"); } @@ -1898,20 +1901,19 @@ impl PciDevice for VfioPciDevice { user_memory_region.start, user_memory_region.size, e ); } - // Remove old region - let old_mem_region = self.vm.make_user_memory_region( - user_memory_region.slot, - user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, - false, - false, - ); - - self.vm - .remove_user_memory_region(old_mem_region) - .map_err(io::Error::other)?; + // SAFETY: user_memory_regions has valid entries + unsafe { + self.vm.remove_user_memory_region( + user_memory_region.slot, + user_memory_region.start, + user_memory_region.size, + user_memory_region.host_addr, + false, + false, + ) + } + .map_err(io::Error::other)?; // Update the user memory region with the correct start address. if new_base > old_base { @@ -1921,18 +1923,18 @@ impl PciDevice for VfioPciDevice { } // Insert new region - let new_mem_region = self.vm.make_user_memory_region( - user_memory_region.slot, - user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, - false, - false, - ); - - self.vm - .create_user_memory_region(new_mem_region) - .map_err(io::Error::other)?; + // SAFETY: mmio_regions only has valid values + unsafe { + self.vm.create_user_memory_region( + user_memory_region.slot, + user_memory_region.start, + user_memory_region.size, + user_memory_region.host_addr, + false, + false, + ) + } + .map_err(io::Error::other)?; // Map the moved mmio region to vfio container if !self.iommu_attached { diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index 73de9ee96e..79b5638728 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -110,7 +110,10 @@ impl VfioUserPciDevice { }) } - pub fn map_mmio_regions(&mut self) -> Result<(), VfioUserPciDeviceError> { + /// # Safety + /// + /// Not known yet (TODO) + pub unsafe fn map_mmio_regions(&mut self) -> Result<(), VfioUserPciDeviceError> { for mmio_region in &mut self.common.mmio_regions { let region_flags = self .client @@ -184,20 +187,21 @@ impl VfioUserPciDevice { host_addr: host_addr as u64, }; - mmio_region.user_memory_regions.push(user_memory_region); - - let mem_region = self.vm.make_user_memory_region( - user_memory_region.slot, - user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, - false, - false, - ); + // SAFETY: host_addr was just allocated with mmap() + // and points to size bytes of valid address. + unsafe { + self.vm.create_user_memory_region( + user_memory_region.slot, + user_memory_region.start, + user_memory_region.size, + user_memory_region.host_addr, + false, + false, + ) + } + .map_err(VfioUserPciDeviceError::MapRegionGuest)?; - self.vm - .create_user_memory_region(mem_region) - .map_err(VfioUserPciDeviceError::MapRegionGuest)?; + mmio_region.user_memory_regions.push(user_memory_region); } } } @@ -209,16 +213,17 @@ impl VfioUserPciDevice { for mmio_region in self.common.mmio_regions.iter() { for user_memory_region in mmio_region.user_memory_regions.iter() { // Remove region - let r = self.vm.make_user_memory_region( - user_memory_region.slot, - user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, - false, - false, - ); - - if let Err(e) = self.vm.remove_user_memory_region(r) { + // SAFETY: only valid regions are in user_memory_regions + if let Err(e) = unsafe { + self.vm.remove_user_memory_region( + user_memory_region.slot, + user_memory_region.start, + user_memory_region.size, + user_memory_region.host_addr, + false, + false, + ) + } { error!("Could not remove the userspace memory region: {e}"); } @@ -455,18 +460,18 @@ impl PciDevice for VfioUserPciDevice { for user_memory_region in mmio_region.user_memory_regions.iter_mut() { // Remove old region - let old_region = self.vm.make_user_memory_region( - user_memory_region.slot, - user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, - false, - false, - ); - - self.vm - .remove_user_memory_region(old_region) - .map_err(std::io::Error::other)?; + // SAFETY: only valid regions are in user_memory_regions + unsafe { + self.vm.remove_user_memory_region( + user_memory_region.slot, + user_memory_region.start, + user_memory_region.size, + user_memory_region.host_addr, + false, + false, + ) + } + .map_err(std::io::Error::other)?; // Update the user memory region with the correct start address. if new_base > old_base { @@ -476,18 +481,18 @@ impl PciDevice for VfioUserPciDevice { } // Insert new region - let new_region = self.vm.make_user_memory_region( - user_memory_region.slot, - user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, - false, - false, - ); - - self.vm - .create_user_memory_region(new_region) - .map_err(std::io::Error::other)?; + // SAFETY: only valid regions are in user_memory_regions + unsafe { + self.vm.create_user_memory_region( + user_memory_region.slot, + user_memory_region.start, + user_memory_region.size, + user_memory_region.host_addr, + false, + false, + ) + } + .map_err(std::io::Error::other)?; } info!("Moved bar 0x{old_base:x} -> 0x{new_base:x}"); } diff --git a/vmm/src/api/dbus/mod.rs b/vmm/src/api/dbus/mod.rs index f329b86bcd..59c48f8a97 100644 --- a/vmm/src/api/dbus/mod.rs +++ b/vmm/src/api/dbus/mod.rs @@ -356,7 +356,7 @@ pub fn start_dbus_thread( apply_filter(&api_seccomp_filter) .map_err(VmmError::ApplySeccompFilter) .map_err(|e| { - error!("Error applying seccomp filter: {:?}", e); + error!("Error applying seccomp filter: {e:?}"); exit_evt.write(1).ok(); e })?; @@ -383,7 +383,7 @@ pub fn start_dbus_thread( } } } - }) + }); })) .map_err(|_| { error!("dbus-api thread panicked"); diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 8378a7a310..c7ec5a07de 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2521,7 +2521,7 @@ impl VmConfig { #[cfg(target_arch = "x86_64")] if self.debug_console.mode == ConsoleOutputMode::Tty { tty_consoles.push("debug-console"); - }; + } if tty_consoles.len() > 1 { warn!("Using TTY output for multiple consoles: {tty_consoles:?}"); } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index c56d5f27cd..1293a0daab 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -817,32 +817,43 @@ impl DeviceRelocation for AddressManager { if let Some(mut shm_regions) = virtio_dev.get_shm_regions() && shm_regions.addr.raw_value() == old_base { - let mem_region = self.vm.make_user_memory_region( - shm_regions.mem_slot, - old_base, - shm_regions.len, - shm_regions.host_addr, - false, - false, - ); - - self.vm.remove_user_memory_region(mem_region).map_err(|e| { - io::Error::other(format!("failed to remove user memory region: {e:?}")) - })?; - - // Create new mapping by inserting new region to KVM. - let mem_region = self.vm.make_user_memory_region( - shm_regions.mem_slot, - new_base, - shm_regions.len, - shm_regions.host_addr, - false, - false, - ); + // SAFETY: TODO what are the invariants here? + unsafe { + // Remove old mapping + self.vm + .remove_user_memory_region( + shm_regions.mem_slot, + old_base, + shm_regions.len, + shm_regions.host_addr, + false, + false, + ) + .map_err(|e| { + io::Error::other(format!( + "failed to remove user memory region: {e:?}" + )) + })?; + } - self.vm.create_user_memory_region(mem_region).map_err(|e| { - io::Error::other(format!("failed to create user memory regions: {e:?}")) - })?; + // SAFETY: TODO what are the invariants here? + unsafe { + // Create new mapping by inserting new region to KVM. + self.vm + .create_user_memory_region( + shm_regions.mem_slot, + new_base, + shm_regions.len, + shm_regions.host_addr, + false, + false, + ) + .map_err(|e| { + io::Error::other(format!( + "failed to create user memory regions: {e:?}" + )) + })?; + } // Update shared memory regions to reflect the new mapping. shm_regions.addr = GuestAddress(new_base); @@ -3240,12 +3251,14 @@ impl DeviceManager { .map_err(DeviceManagerError::NewMmapRegion)?; let host_addr: u64 = mmap_region.as_ptr() as u64; - let mem_slot = self - .memory_manager - .lock() - .unwrap() - .create_userspace_mapping(region_base, region_size, host_addr, false, false, false) - .map_err(DeviceManagerError::MemoryManager)?; + // SAFETY: host_addr points to region_size bytes of mmap-allocated memory. + let mem_slot = unsafe { + self.memory_manager + .lock() + .unwrap() + .create_userspace_mapping(region_base, region_size, host_addr, false, false, false) + .map_err(DeviceManagerError::MemoryManager) + }?; let mapping = UserspaceMapping { host_addr, @@ -3997,13 +4010,16 @@ impl DeviceManager { resources, )?; + // SAFETY: TODO // Note it is required to call 'add_pci_device()' in advance to have the list of // mmio regions provisioned correctly - vfio_user_pci_device - .lock() - .unwrap() - .map_mmio_regions() - .map_err(DeviceManagerError::VfioUserMapRegion)?; + unsafe { + vfio_user_pci_device + .lock() + .unwrap() + .map_mmio_regions() + .map_err(DeviceManagerError::VfioUserMapRegion) + }?; let mut node = device_node!(vfio_user_name, vfio_user_pci_device); @@ -4722,17 +4738,22 @@ impl DeviceManager { // Shutdown and remove the underlying virtio-device if present if let Some(virtio_device) = virtio_device { for mapping in virtio_device.lock().unwrap().userspace_mappings() { - self.memory_manager - .lock() - .unwrap() - .remove_userspace_mapping( - mapping.addr.raw_value(), - mapping.len, - mapping.host_addr, - mapping.mergeable, - mapping.mem_slot, - ) - .map_err(DeviceManagerError::MemoryManager)?; + // SAFETY: userspace_mappings only has valid mappings. + // TODO: do not rely on the correctness of all the code in this file + // for this to hold. + unsafe { + self.memory_manager + .lock() + .unwrap() + .remove_userspace_mapping( + mapping.addr.raw_value(), + mapping.len, + mapping.host_addr, + mapping.mergeable, + mapping.mem_slot, + ) + .map_err(DeviceManagerError::MemoryManager) + }?; } virtio_device.lock().unwrap().shutdown(); @@ -4984,7 +5005,7 @@ impl IvshmemOps for IvshmemHandler { ) -> Result<(Arc, UserspaceMapping), IvshmemError> { info!("Creating ivshmem mem region at 0x{start_addr:x}"); - let region = MemoryManager::create_ram_region( + let region: Arc = MemoryManager::create_ram_region( &backing_file, 0, GuestAddress(start_addr), @@ -4998,19 +5019,21 @@ impl IvshmemOps for IvshmemHandler { false, ) .map_err(|_| IvshmemError::CreateUserMemoryRegion)?; - let mem_slot = self - .memory_manager - .lock() - .unwrap() - .create_userspace_mapping( - region.start_addr().0, - region.len(), - region.as_ptr() as u64, - false, - false, - false, - ) - .map_err(|_| IvshmemError::CreateUserspaceMapping)?; + let mem_slot = { + let mut manager = self.memory_manager.lock().unwrap(); + // SAFETY: guaranteed by GuestRegionMmap invariants + unsafe { + manager.create_userspace_mapping( + region.start_addr().0, + region.len(), + region.as_ptr() as u64, + false, + false, + false, + ) + } + } + .map_err(|_| IvshmemError::CreateUserspaceMapping)?; let mapping = UserspaceMapping { host_addr: region.as_ptr() as u64, mem_slot, @@ -5022,17 +5045,18 @@ impl IvshmemOps for IvshmemHandler { } fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError> { - self.memory_manager - .lock() - .unwrap() - .remove_userspace_mapping( + let mut manager = self.memory_manager.lock().unwrap(); + // SAFETY: UserspaceMapping is valid due to other code being correct + unsafe { + manager.remove_userspace_mapping( mapping.addr.raw_value(), mapping.len, mapping.host_addr, mapping.mergeable, mapping.mem_slot, ) - .map_err(|_| IvshmemError::RemoveUserspaceMapping)?; + } + .map_err(|_| IvshmemError::RemoveUserspaceMapping)?; Ok(()) } } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 7343206830..84cf123ea7 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1128,7 +1128,7 @@ impl Vmm { return Err(MigratableError::MigrateSend(anyhow!( "Live Migration is not supported when TDX is enabled" ))); - }; + } let amx = vm_config.lock().unwrap().cpus.features.amx; let phys_bits = vm::physical_bits( @@ -1265,7 +1265,7 @@ impl Vmm { return Err(MigratableError::MigrateReceive(anyhow!( "Live Migration is not supported when TDX is enabled" ))); - }; + } // We check the `CPUID` compatibility of between the source vm and destination, which is // mostly about feature compatibility. diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 947fd41f3f..146ea610e4 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -900,14 +900,18 @@ impl MemoryManager { for (zone_id, regions) in list { for (region, virtio_mem) in regions { - let slot = self.create_userspace_mapping( - region.start_addr().raw_value(), - region.len(), - region.as_ptr() as u64, - self.mergeable, - false, - self.log_dirty, - )?; + // SAFETY: regions only holds valid addresses. + // TODO: encapsulate this unsafety in a small part of the file. + let slot = unsafe { + self.create_userspace_mapping( + region.start_addr().raw_value(), + region.len(), + region.as_ptr() as u64, + self.mergeable, + false, + self.log_dirty, + ) + }?; let file_offset = if let Some(file_offset) = region.file_offset() { file_offset.start() @@ -958,18 +962,18 @@ impl MemoryManager { arch::layout::UEFI_START, ) .unwrap(); - let uefi_mem_region = self.vm.make_user_memory_region( - uefi_mem_slot, - uefi_region.start_addr().raw_value(), - uefi_region.len(), - uefi_region.as_ptr() as u64, - false, - false, - ); - self.vm - .create_user_memory_region(uefi_mem_region) - .map_err(Error::CreateUefiFlash)?; - + unsafe { + self.vm + .create_user_memory_region( + uefi_mem_slot, + uefi_region.start_addr().raw_value(), + uefi_region.len(), + uefi_region.as_ptr() as u64, + false, + false, + ) + .map_err(Error::CreateUefiFlash)?; + } let uefi_flash = GuestMemoryAtomic::new(GuestMemoryMmap::from_regions(vec![uefi_region]).unwrap()); @@ -1608,14 +1612,17 @@ impl MemoryManager { )?; // Map it into the guest - let slot = self.create_userspace_mapping( - region.start_addr().0, - region.len(), - region.as_ptr() as u64, - self.mergeable, - false, - self.log_dirty, - )?; + // SAFETY: create_ram_region only produces valid mappings. + let slot = unsafe { + self.create_userspace_mapping( + region.start_addr().0, + region.len(), + region.as_ptr() as u64, + self.mergeable, + false, + self.log_dirty, + ) + }?; self.guest_ram_mappings.push(GuestRamMapping { gpa: region.start_addr().raw_value(), size: region.len(), @@ -1708,7 +1715,11 @@ impl MemoryManager { self.memory_slot_allocator().next_memory_slot() } - pub fn create_userspace_mapping( + /// # Safety + /// + /// `userspace_addr` and `memory_size` must be and remain valid + /// until `remove_userspace_mapping` is called. + pub unsafe fn create_userspace_mapping( &mut self, guest_phys_addr: u64, memory_size: u64, @@ -1718,22 +1729,24 @@ impl MemoryManager { log_dirty: bool, ) -> Result { let slot = self.allocate_memory_slot(); - let mem_region = self.vm.make_user_memory_region( - slot, - guest_phys_addr, - memory_size, - userspace_addr, - readonly, - log_dirty, - ); info!( "Creating userspace mapping: {guest_phys_addr:x} -> {userspace_addr:x} {memory_size:x}, slot {slot}" ); - self.vm - .create_user_memory_region(mem_region) - .map_err(Error::CreateUserMemoryRegion)?; + // SAFETY: promised by caller + unsafe { + self.vm + .create_user_memory_region( + slot, + guest_phys_addr, + memory_size, + userspace_addr, + readonly, + log_dirty, + ) + .map_err(Error::CreateUserMemoryRegion)?; + } // SAFETY: the address and size are valid since the // mmap succeeded. @@ -1781,7 +1794,16 @@ impl MemoryManager { Ok(slot) } - pub fn remove_userspace_mapping( + /// # Safety + /// + /// `userspace_addr` and `memory_size` must have previously been passed + /// to `create_userspace_mapping`. + /// + /// # Errors + /// + /// If this function fails there is no way to clean up resources and you + /// should probably crash the process. + pub unsafe fn remove_userspace_mapping( &mut self, guest_phys_addr: u64, memory_size: u64, @@ -1789,18 +1811,19 @@ impl MemoryManager { mergeable: bool, slot: u32, ) -> Result<(), Error> { - let mem_region = self.vm.make_user_memory_region( - slot, - guest_phys_addr, - memory_size, - userspace_addr, - false, /* readonly -- don't care */ - false, /* log dirty */ - ); - - self.vm - .remove_user_memory_region(mem_region) - .map_err(Error::RemoveUserMemoryRegion)?; + // SAFETY: The caller promises that the parameters are correct. + unsafe { + self.vm + .remove_user_memory_region( + slot, + guest_phys_addr, + memory_size, + userspace_addr, + false, /* readonly -- don't care */ + false, /* log dirty */ + ) + .map_err(Error::RemoveUserMemoryRegion)?; + } // Mark the pages as unmergeable if there were previously marked as // mergeable. diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 9837e71ffe..89255e28b4 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -3464,17 +3464,18 @@ mod unit_tests { .expect("new VM creation failed"); for (index, region) in mem.iter().enumerate() { - let mem_region = vm.make_user_memory_region( - index as u32, - region.start_addr().raw_value(), - region.len(), - region.as_ptr() as u64, - false, - false, - ); - - vm.create_user_memory_region(mem_region) + // SAFETY: inputs are valid + unsafe { + vm.create_user_memory_region( + index as u32, + region.start_addr().raw_value(), + region.len(), + region.as_ptr() as u64, + false, + false, + ) .expect("Cannot configure guest memory"); + } } mem.write_slice(&code, load_addr) .expect("Writing code to memory failed"); @@ -3574,3 +3575,71 @@ mod unit_tests { .unwrap(); } } + +#[cfg(all(feature = "kvm", target_arch = "x86_64"))] +#[test] +pub fn test_vm() { + use hypervisor::VmExit; + use vm_memory::{Address, GuestMemory, GuestMemoryRegion}; + // This example based on https://lwn.net/Articles/658511/ + let code = [ + 0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */ + 0x00, 0xd8, /* add %bl, %al */ + 0x04, b'0', /* add $'0', %al */ + 0xee, /* out %al, (%dx) */ + 0xb0, b'\n', /* mov $'\n', %al */ + 0xee, /* out %al, (%dx) */ + 0xf4, /* hlt */ + ]; + + let mem_size = 0x1000; + let load_addr = GuestAddress(0x1000); + let mem = GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap(); + + let hv = hypervisor::new().unwrap(); + let vm = hv + .create_vm(HypervisorVmConfig::default()) + .expect("new VM creation failed"); + + for (index, region) in mem.iter().enumerate() { + // SAFETY: parameters are correct + unsafe { + vm.create_user_memory_region( + index as u32, + region.start_addr().raw_value(), + region.len(), + region.as_ptr() as u64, + false, + false, + ) + .expect("Cannot configure guest memory"); + } + } + mem.write_slice(&code, load_addr) + .expect("Writing code to memory failed"); + + let mut vcpu = vm.create_vcpu(0, None).expect("new Vcpu failed"); + + let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed"); + vcpu_sregs.cs.base = 0; + vcpu_sregs.cs.selector = 0; + vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed"); + + let mut vcpu_regs = vcpu.get_regs().expect("get regs failed"); + vcpu_regs.set_rip(0x1000); + vcpu_regs.set_rax(2); + vcpu_regs.set_rbx(3); + vcpu_regs.set_rflags(2); + vcpu.set_regs(&vcpu_regs).expect("set regs failed"); + + loop { + match vcpu.run().expect("run failed") { + VmExit::Reset => { + println!("HLT"); + break; + } + VmExit::Ignore => {} + r => panic!("unexpected exit reason: {r:?}"), + } + } +} From 42522a88c0226a50b49beaaf22436aec12c61d21 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 13 Jun 2025 14:27:00 -0400 Subject: [PATCH 0312/1893] misc: do not use u64 to represent host pointers To ensure that struct sizes are the same on 32-bit and 64-bit, various kernel APIs use __u64 (Rust u64) to represent userspace pointers. Userspace is expected to cast pointers to __u64 before passing them to the kernel, and cast kernel-provided __u64 to a pointer before using them. However, various safe APIs in Cloud Hypervisor took caller-provided u64 values and passed them to syscalls that interpret them as userspace addresses. Therefore, passing bad u64 values would cause memory disclosure or corruption. Fix the bug by using usize and pointer types as appropriate. To make soundness of the code easier to reason about, the PCI code gains a new MmapRegion abstraction that ensures the validity of pointers. The rest of the code already has an MmapRegion abstraction it can use. To avoid having to reason about whether something is keeping the MmapRegion alive, reference counting is added. MmapRegion cannot hold references to other objects, so the reference counting cannot introduce cycles. Signed-off-by: Demi Marie Obenour --- devices/src/ivshmem.rs | 12 +- fuzz/fuzz_targets/pmem.rs | 4 +- hypervisor/src/kvm/mod.rs | 16 +- hypervisor/src/mshv/mod.rs | 16 +- hypervisor/src/vm.rs | 8 +- pci/src/lib.rs | 1 + pci/src/mmap.rs | 89 +++++++++ pci/src/vfio.rs | 209 ++++++++++++--------- pci/src/vfio_user.rs | 94 ++++----- virtio-devices/src/device.rs | 7 +- virtio-devices/src/pmem.rs | 10 +- virtio-devices/src/transport/pci_device.rs | 2 +- virtio-devices/src/vhost_user/fs.rs | 3 +- vm-device/src/lib.rs | 8 +- vmm/src/device_manager.rs | 62 +++--- vmm/src/memory_manager.rs | 94 ++++++--- vmm/src/vm.rs | 8 +- 17 files changed, 381 insertions(+), 262 deletions(-) create mode 100644 pci/src/mmap.rs diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs index 5997e10a2b..d6f5b5d467 100644 --- a/devices/src/ivshmem.rs +++ b/devices/src/ivshmem.rs @@ -33,7 +33,7 @@ const IVSHMEM_DEVICE_ID: u16 = 0x1110; const IVSHMEM_REG_BAR_SIZE: u64 = 0x100; -type GuestRegionMmap = vm_memory::GuestRegionMmap; +type MmapRegion = vm_memory::MmapRegion; #[derive(Debug, Error)] pub enum IvshmemError { @@ -68,7 +68,7 @@ pub trait IvshmemOps: Send + Sync { start_addr: u64, size: usize, backing_file: Option, - ) -> Result<(Arc, UserspaceMapping), IvshmemError>; + ) -> Result<(Arc, UserspaceMapping), IvshmemError>; fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError>; } @@ -95,7 +95,7 @@ pub struct IvshmemDevice { region_size: u64, ivshmem_ops: Arc>, backend_file: Option, - region: Option>, + region: Option>, userspace_mapping: Option, } @@ -180,11 +180,7 @@ impl IvshmemDevice { Ok(device) } - pub fn set_region( - &mut self, - region: Arc, - userspace_mapping: UserspaceMapping, - ) { + pub fn set_region(&mut self, region: Arc, userspace_mapping: UserspaceMapping) { self.region = Some(region); self.userspace_mapping = Some(userspace_mapping); } diff --git a/fuzz/fuzz_targets/pmem.rs b/fuzz/fuzz_targets/pmem.rs index e9247fb631..a8fcb7a774 100644 --- a/fuzz/fuzz_targets/pmem.rs +++ b/fuzz/fuzz_targets/pmem.rs @@ -115,10 +115,9 @@ fn create_dummy_pmem() -> Pmem { .unwrap(); let guest_addr = GuestAddress(0); let dummy_user_mapping = UserspaceMapping { - host_addr: dummy_mmap_region.as_ptr() as u64, mem_slot: 0, addr: guest_addr, - len: dummy_mapping_size as u64, + mapping: Arc::new(dummy_mmap_region), mergeable: false, }; @@ -127,7 +126,6 @@ fn create_dummy_pmem() -> Pmem { file, guest_addr, dummy_user_mapping, - dummy_mmap_region, false, SeccompAction::Allow, EventFd::new(EFD_NONBLOCK).unwrap(), diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 53de9bad36..d9dc9b7549 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -723,8 +723,8 @@ impl vm::Vm for KvmVm { &self, slot: u32, guest_phys_addr: u64, - memory_size: u64, - userspace_addr: u64, + memory_size: usize, + userspace_addr: *mut u8, readonly: bool, log_dirty_pages: bool, ) -> vm::Result<()> { @@ -741,8 +741,8 @@ impl vm::Vm for KvmVm { let mut region = kvm_userspace_memory_region { slot, guest_phys_addr, - memory_size, - userspace_addr, + memory_size: memory_size as u64, + userspace_addr: userspace_addr as usize as u64, flags, }; @@ -787,8 +787,8 @@ impl vm::Vm for KvmVm { &self, slot: u32, guest_phys_addr: u64, - memory_size: u64, - userspace_addr: u64, + memory_size: usize, + userspace_addr: *mut u8, readonly: bool, log_dirty_pages: bool, ) -> vm::Result<()> { @@ -805,8 +805,8 @@ impl vm::Vm for KvmVm { let mut region = kvm_userspace_memory_region { slot, guest_phys_addr, - memory_size, - userspace_addr, + memory_size: memory_size as u64, + userspace_addr: userspace_addr as usize as u64, flags, }; diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 1fe60d3689..3ca3d846f9 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -1886,8 +1886,8 @@ impl vm::Vm for MshvVm { &self, _slot: u32, guest_phys_addr: u64, - memory_size: u64, - userspace_addr: u64, + memory_size: usize, + userspace_addr: *mut u8, readonly: bool, _log_dirty_pages: bool, ) -> vm::Result<()> { @@ -1899,8 +1899,8 @@ impl vm::Vm for MshvVm { let user_memory_region = mshv_user_mem_region { flags, guest_pfn: guest_phys_addr >> PAGE_SHIFT, - size: memory_size, - userspace_addr, + size: memory_size.try_into().unwrap(), + userspace_addr: (userspace_addr as usize).try_into().unwrap(), ..Default::default() }; // No matter read only or not we keep track the slots. @@ -1930,8 +1930,8 @@ impl vm::Vm for MshvVm { &self, _slot: u32, guest_phys_addr: u64, - memory_size: u64, - userspace_addr: u64, + memory_size: usize, + userspace_addr: *mut u8, readonly: bool, _log_dirty_pages: bool, ) -> vm::Result<()> { @@ -1943,8 +1943,8 @@ impl vm::Vm for MshvVm { let user_memory_region = mshv_user_mem_region { flags, guest_pfn: guest_phys_addr >> PAGE_SHIFT, - size: memory_size, - userspace_addr, + size: memory_size.try_into().unwrap(), + userspace_addr: (userspace_addr as usize).try_into().unwrap(), ..Default::default() }; // Remove the corresponding entry from "self.dirty_log_slots" if needed diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index a01e499a77..9d4bf5c3d6 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -345,8 +345,8 @@ pub trait Vm: Send + Sync + Any { &self, slot: u32, guest_phys_addr: u64, - memory_size: u64, - userspace_addr: u64, + memory_size: usize, + userspace_addr: *mut u8, readonly: bool, log_dirty_pages: bool, ) -> Result<()>; @@ -359,8 +359,8 @@ pub trait Vm: Send + Sync + Any { &self, slot: u32, guest_phys_addr: u64, - memory_size: u64, - userspace_addr: u64, + memory_size: usize, + userspace_addr: *mut u8, readonly: bool, log_dirty_pages: bool, ) -> Result<()>; diff --git a/pci/src/lib.rs b/pci/src/lib.rs index c95a38b339..8c1532491b 100644 --- a/pci/src/lib.rs +++ b/pci/src/lib.rs @@ -11,6 +11,7 @@ extern crate log; mod bus; mod configuration; mod device; +mod mmap; mod msi; mod msix; mod vfio; diff --git a/pci/src/mmap.rs b/pci/src/mmap.rs new file mode 100644 index 0000000000..79d3ab20ba --- /dev/null +++ b/pci/src/mmap.rs @@ -0,0 +1,89 @@ +// Copyright © 2025 Demi Marie Obenour +// +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause + +//! Helpers for `mmap()` + +use core::ffi::c_int; +use core::ptr::null_mut; +use std::io::{Error, ErrorKind}; +use std::os::fd::{AsRawFd as _, BorrowedFd}; + +use libc::size_t; + +/// A region of `mmap()`-allocated memory that calls `munmap()` when dropped. +/// This guarantees that the buffer is valid and that its address space +/// will be reserved. The address space is not guaranteed to be accessible. +/// Atomic access to the data will not cause undefined behavior but might +/// cause SIGSEGV or SIGBUS. Non-atomic access will generally cause data +/// races and thus Undefined Behavior. +#[derive(Debug)] +pub struct MmapRegion { + addr: *mut u8, + len: size_t, +} + +impl Drop for MmapRegion { + fn drop(&mut self) { + // SAFETY: guaranteed by type validity invariant + unsafe { assert_eq!(libc::munmap(self.addr as *mut _, self.len), 0) } + } +} +// SAFETY: the caller is responsible for avoiding data races +unsafe impl Send for MmapRegion {} +// SAFETY: the caller is responsible for avoiding data races +unsafe impl Sync for MmapRegion {} + +impl MmapRegion { + #[inline] + pub fn addr(&self) -> *mut u8 { + self.addr + } + + /// Return the length of the region. + /// This function promises that the return value fits in [`libc::size_t`] + /// and in [`isize`] and `unsafe` code can rely on this. + #[inline] + pub fn len(&self) -> usize { + self.len + } + + /// Create an [`MmapRegion`] using `mmap` of a file descriptor. + pub fn mmap( + len: u64, + prot: c_int, + fd: BorrowedFd, + offset1: u64, + offset2: u64, + ) -> std::io::Result { + const BAD_LENGTH: &str = "Offsets must fit in libc::off_t"; + const BAD_OFFSET: &str = "Mapping length must fit \ +in both isize and libc::size_t"; + let Some(offset) = offset1.checked_add(offset2) else { + return Err(Error::new(ErrorKind::InvalidInput, BAD_OFFSET)); + }; + let Ok(offset) = libc::off_t::try_from(offset) else { + return Err(Error::new(ErrorKind::InvalidInput, BAD_OFFSET)); + }; + if isize::try_from(len).is_err() { + return Err(Error::new(ErrorKind::InvalidInput, BAD_LENGTH)); + } + let Ok(len) = libc::size_t::try_from(len) else { + return Err(Error::new(ErrorKind::InvalidInput, BAD_LENGTH)); + }; + + assert!( + (prot & !(libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC)) == 0, + "bad protection" + ); + let flags = libc::MAP_SHARED; + // SAFETY: FFI call with correct parameters. + let addr = unsafe { libc::mmap(null_mut(), len, prot, flags, fd.as_raw_fd(), offset) }; + if addr == libc::MAP_FAILED { + Err(Error::last_os_error()) + } else { + let addr = addr as _; + Ok(Self { addr, len }) + } + } +} diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 3d59293ae3..7c85f557c8 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -6,9 +6,9 @@ use std::any::Any; use std::collections::{BTreeMap, HashMap}; use std::io; +use std::os::fd::BorrowedFd; use std::os::unix::io::AsRawFd; use std::path::PathBuf; -use std::ptr::null_mut; use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; @@ -34,6 +34,7 @@ use vm_memory::{Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestUsiz use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vmm_sys_util::eventfd::EventFd; +use crate::mmap::MmapRegion; use crate::msi::{MSI_CONFIG_ID, MsiConfigState}; use crate::msix::MsixConfigState; use crate::{ @@ -258,12 +259,11 @@ impl Interrupt { } } -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct UserMemoryRegion { pub slot: u32, pub start: u64, - pub size: u64, - pub host_addr: u64, + pub mapping: Arc, } #[derive(Clone)] @@ -275,12 +275,17 @@ pub struct MmioRegion { pub(crate) user_memory_regions: Vec, } -trait MmioRegionRange { +/// # Safety +/// +/// [`Self::find_user_address`] must always either return `Err` +/// or a pointer to `size` bytes of valid memory. +unsafe trait MmioRegionRange { fn check_range(&self, guest_addr: u64, size: u64) -> bool; - fn find_user_address(&self, guest_addr: u64) -> Result; + fn find_user_address(&self, guest_addr: u64, size: u64) -> Result<*mut u8, io::Error>; } -impl MmioRegionRange for Vec { +// SAFETY: See the comment in `find_user_address`. +unsafe impl MmioRegionRange for Vec { // Check if a guest address is within the range of mmio regions fn check_range(&self, guest_addr: u64, size: u64) -> bool { for region in self.iter() { @@ -298,14 +303,33 @@ impl MmioRegionRange for Vec { } // Locate the user region address for a guest address within all mmio regions - fn find_user_address(&self, guest_addr: u64) -> Result { + fn find_user_address(&self, guest_addr: u64, size: u64) -> Result<*mut u8, io::Error> { for region in self.iter() { for user_region in region.user_memory_regions.iter() { - if guest_addr >= user_region.start - && guest_addr < user_region.start + user_region.size - { - return Ok(user_region.host_addr + (guest_addr - user_region.start)); + let mapping: &MmapRegion = &user_region.mapping; + let start: u64 = user_region.start; + let len: u64 = mapping.len().try_into().unwrap(); + // See if the guest address is inside the region. + let Some(offset_from_start) = guest_addr.checked_sub(start) else { + continue; + }; + if offset_from_start >= len { + continue; } + // Check that the size is in bounds. + // This enforces the invariant promised by implementing MmioRegionRange. + assert!( + size <= len - offset_from_start, + "Attempt to read {size} bytes at offset {offset_from_start} into \ +a region of size {len}" + ); + // SAFETY: MmapRegion guarantees that mapping.addr points to at least + // mapping.len() bytes of valid memory. offset_from_start is equal + // to guest_addr - start, which was checked to be less than mapping.len(). + // Therefore, the returned pointer is still in the range of valid memory. + // Also, since mapping.len() fit in usize, offset_from_start must as well, + // so the cast is safe. + return Ok(unsafe { mapping.addr().add(offset_from_start as usize) }); } } @@ -1585,7 +1609,8 @@ impl VfioPciDevice { /// * `mem_slot` - The closure to return a memory slot. pub fn map_mmio_regions(&mut self) -> Result<(), VfioPciError> { let fd = self.device.as_raw_fd(); - + // SAFETY: fd is guaranteed valid + let fd = unsafe { BorrowedFd::borrow_raw(fd) }; for region in self.common.mmio_regions.iter_mut() { let region_flags = self.device.get_region_flags(region.index); if region_flags & VFIO_REGION_INFO_FLAG_MMAP != 0 { @@ -1625,71 +1650,72 @@ impl VfioPciDevice { self.common.interrupt.msix.as_ref(), )?; - for area in sparse_areas.iter() { - // SAFETY: FFI call with correct arguments - let host_addr = unsafe { - libc::mmap( - null_mut(), - area.size as usize, - prot, - libc::MAP_SHARED, - fd, - mmap_offset as libc::off_t + area.offset as libc::off_t, - ) - }; - - if std::ptr::eq(host_addr, libc::MAP_FAILED) { + for area in &sparse_areas { + if !is_page_size_aligned(area.size) || !is_page_size_aligned(area.offset) { error!( - "Could not mmap sparse area (offset = 0x{:x}, size = 0x{:x}): {}", - area.offset, - area.size, - std::io::Error::last_os_error() + "Could not mmap sparse area that is not page size aligned \ +(offset = 0x{:x}, size = 0x{:x})", + area.offset, area.size ); return Err(VfioPciError::MmapArea); } + } - if !is_page_size_aligned(area.size) || !is_page_size_aligned(area.offset) { - warn!( - "Could not mmap sparse area that is not page size aligned (offset = 0x{:x}, size = 0x{:x})", - area.offset, area.size, - ); - return Ok(()); - } + for area in sparse_areas.iter() { + let mapping = match MmapRegion::mmap( + area.size, + prot, + fd, + mmap_offset, + area.offset, + ) { + Ok(mapping) => mapping, + Err(_) => { + error!( + "Could not mmap sparse area (offset = 0x{:x}, size = 0x{:x}): {}", + mmap_offset, + area.size, + std::io::Error::last_os_error() + ); + return Err(VfioPciError::MmapArea); + } + }; let user_memory_region = UserMemoryRegion { slot: self.memory_slot_allocator.next_memory_slot(), start: region.start.0 + area.offset, - size: area.size, - host_addr: host_addr as u64, + mapping: Arc::new(mapping), }; - - // SAFETY: host_addr was allocated by mmap() and points to size - // bytes of memory. + // SAFETY: MmapRegion invariants guarantee that + // user_memory_region.mapping.addr() points to + // user_memory_region.mapping.len() bytes of + // valid memory that will only be unmapped with munmap(). unsafe { self.vm.create_user_memory_region( user_memory_region.slot, user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + user_memory_region.mapping.len(), + user_memory_region.mapping.addr(), false, false, ) } .map_err(VfioPciError::CreateUserMemoryRegion)?; - region.user_memory_regions.push(user_memory_region); - if !self.iommu_attached { self.container .vfio_dma_map( user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + user_memory_region.mapping.len().try_into().unwrap(), + (user_memory_region.mapping.addr() as usize) + .try_into() + .unwrap(), ) .map_err(|e| { VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf) })?; } + region.user_memory_regions.push(user_memory_region); } } } @@ -1698,19 +1724,21 @@ impl VfioPciDevice { } pub fn unmap_mmio_regions(&mut self) { - for region in self.common.mmio_regions.iter() { - for user_memory_region in region.user_memory_regions.iter() { + for region in self.common.mmio_regions.iter_mut() { + for user_memory_region in region.user_memory_regions.drain(..) { + let len = user_memory_region.mapping.len(); + let host_addr = user_memory_region.mapping.addr(); // Unmap from vfio container if !self.iommu_attached && let Err(e) = self .container - .vfio_dma_unmap(user_memory_region.start, user_memory_region.size) + .vfio_dma_unmap(user_memory_region.start, len.try_into().unwrap()) .map_err(|e| VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf)) { error!( "Could not unmap mmio region from vfio container: \ iova 0x{:x}, size 0x{:x}: {}, ", - user_memory_region.start, user_memory_region.size, e + user_memory_region.start, len, e ); } @@ -1721,8 +1749,8 @@ impl VfioPciDevice { self.vm.remove_user_memory_region( user_memory_region.slot, user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + len, + host_addr, false, false, ) @@ -1732,21 +1760,6 @@ impl VfioPciDevice { self.memory_slot_allocator .free_memory_slot(user_memory_region.slot); - - // SAFETY: FFI call with correct arguments - let ret = unsafe { - libc::munmap( - user_memory_region.host_addr as *mut libc::c_void, - user_memory_region.size as usize, - ) - }; - if ret != 0 { - error!( - "Could not unmap region {}, error:{}", - region.index, - io::Error::last_os_error() - ); - } } } } @@ -1886,29 +1899,31 @@ impl PciDevice for VfioPciDevice { region.start = GuestAddress(new_base); for user_memory_region in region.user_memory_regions.iter_mut() { + let len = user_memory_region.mapping.len(); + let host_addr = user_memory_region.mapping.addr(); // Unmap the old MMIO region from vfio container if !self.iommu_attached && let Err(e) = self .container - .vfio_dma_unmap(user_memory_region.start, user_memory_region.size) + .vfio_dma_unmap(user_memory_region.start, len.try_into().unwrap()) .map_err(|e| { VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf) }) { error!( "Could not unmap mmio region from vfio container: \ - iova 0x{:x}, size 0x{:x}: {}, ", - user_memory_region.start, user_memory_region.size, e +iova 0x{:x}, size 0x{:x}: {}, ", + user_memory_region.start, len, e ); } // Remove old region - // SAFETY: user_memory_regions has valid entries + // SAFETY: validity of len and host_addr guaranteed by hypervisor::mmap::MmapRegion unsafe { self.vm.remove_user_memory_region( user_memory_region.slot, user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + len, + host_addr, false, false, ) @@ -1923,13 +1938,13 @@ impl PciDevice for VfioPciDevice { } // Insert new region - // SAFETY: mmio_regions only has valid values + // SAFETY: validity of len and host_addr guaranteed by hypervisor::mmap::MmapRegion unsafe { self.vm.create_user_memory_region( user_memory_region.slot, user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + len, + host_addr, false, false, ) @@ -1941,8 +1956,8 @@ impl PciDevice for VfioPciDevice { self.container .vfio_dma_map( user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + len.try_into().unwrap(), + (host_addr as usize).try_into().unwrap(), ) .map_err(|e| { VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf) @@ -1950,8 +1965,8 @@ impl PciDevice for VfioPciDevice { .map_err(|e| { io::Error::other(format!( "Could not map mmio region to vfio container: \ - iova 0x{:x}, size 0x{:x}: {}, ", - user_memory_region.start, user_memory_region.size, e +iova 0x{:x}, size 0x{:x}: {}, ", + user_memory_region.start, len, e )) })?; } @@ -1987,6 +2002,7 @@ impl Snapshottable for VfioPciDevice { Ok(vfio_pci_dev_snapshot) } } + impl Transportable for VfioPciDevice {} impl Migratable for VfioPciDevice {} @@ -2020,11 +2036,17 @@ impl VfioDmaMapping { impl ExternalDmaMapping for VfioDmaMapping { fn map(&self, iova: u64, gpa: u64, size: u64) -> std::result::Result<(), io::Error> { + let Ok(usize_size): Result = size.try_into() else { + return Err(io::Error::other(format!("size {size} overflows usize"))); + }; let mem = self.memory.memory(); let guest_addr = GuestAddress(gpa); - let user_addr = if mem.check_range(guest_addr, size as usize) { - match mem.get_host_address(guest_addr) { - Ok(t) => t as u64, + let user_addr = if mem.check_range(guest_addr, usize_size) { + match mem.get_slice(guest_addr, usize_size) { + Ok(t) => { + assert!(t.len() >= usize_size); + Ok(t.ptr_guard_mut()) + } Err(e) => { return Err(io::Error::other(format!( "unable to retrieve user address for gpa 0x{gpa:x} from guest memory region: {e}" @@ -2032,15 +2054,26 @@ impl ExternalDmaMapping for VfioDmaMapping p.as_ptr(), + Err(p) => p, + }; + // SAFETY: find_user_address and GuestMemory::get_slice() guarantee that + // the returned pointer is valid for up to `usize_size` bytes. + // `usize_size` is always equal to `size` due to the above `try_into()` call. self.container - .vfio_dma_map(iova, size, user_addr) + .vfio_dma_map(iova, size, (user_addr as usize).try_into().unwrap()) .map_err(|e| { io::Error::other(format!( "failed to map memory for VFIO container, \ diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index 79b5638728..3ce9ebdb04 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -4,8 +4,8 @@ // use std::any::Any; +use std::os::fd::AsFd; use std::os::unix::prelude::AsRawFd; -use std::ptr::null_mut; use std::sync::{Arc, Barrier, Mutex}; use hypervisor::HypervisorVmError; @@ -24,6 +24,7 @@ use vm_memory::{ use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vmm_sys_util::eventfd::EventFd; +use crate::mmap::MmapRegion; use crate::vfio::{UserMemoryRegion, VFIO_COMMON_ID, Vfio, VfioCommon, VfioError}; use crate::{ BarReprogrammingParams, PciBarConfiguration, PciBdf, PciDevice, PciDeviceError, PciSubclass, @@ -52,6 +53,8 @@ pub enum VfioUserPciDeviceError { InitializeLegacyInterrupts(#[source] VfioPciError), #[error("Failed to create VfioCommon")] CreateVfioCommon(#[source] VfioPciError), + #[error("Other OS error")] + Other(#[source] std::io::Error), } #[derive(Copy, Clone)] @@ -110,10 +113,8 @@ impl VfioUserPciDevice { }) } - /// # Safety - /// - /// Not known yet (TODO) - pub unsafe fn map_mmio_regions(&mut self) -> Result<(), VfioUserPciDeviceError> { + /// Map all of the MMIO regions. + pub fn map_mmio_regions(&mut self) -> Result<(), VfioUserPciDeviceError> { for mmio_region in &mut self.common.mmio_regions { let region_flags = self .client @@ -158,43 +159,39 @@ impl VfioUserPciDevice { sparse_areas }; + let file_offset = file_offset.as_ref().unwrap(); + for s in mmaps.iter() { - // SAFETY: FFI call with correct arguments - let host_addr = unsafe { - libc::mmap( - null_mut(), - s.size as usize, - prot, - libc::MAP_SHARED, - file_offset.as_ref().unwrap().file().as_raw_fd(), - file_offset.as_ref().unwrap().start() as libc::off_t - + s.offset as libc::off_t, - ) + let mapping = match MmapRegion::mmap( + s.size, + prot, + file_offset.file().as_fd(), + file_offset.start(), + s.offset, + ) { + Ok(mapping) => Arc::new(mapping), + Err(e) => { + error!( + "Could not mmap sparse area (offset = 0x{:x}, size = 0x{:x}): {}", + s.offset, s.size, e + ); + return Err(VfioUserPciDeviceError::Other(e)); + } }; - if std::ptr::eq(host_addr, libc::MAP_FAILED) { - error!( - "Could not mmap regions, error:{}", - std::io::Error::last_os_error() - ); - continue; - } - let user_memory_region = UserMemoryRegion { slot: self.memory_slot_allocator.next_memory_slot(), start: mmio_region.start.0 + s.offset, - size: s.size, - host_addr: host_addr as u64, + mapping, }; - // SAFETY: host_addr was just allocated with mmap() - // and points to size bytes of valid address. + // SAFETY: validity of len and host_addr guaranteed by hypervisor::mmap::MmapRegion unsafe { self.vm.create_user_memory_region( user_memory_region.slot, user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + user_memory_region.mapping.len(), + user_memory_region.mapping.addr(), false, false, ) @@ -209,17 +206,17 @@ impl VfioUserPciDevice { Ok(()) } - pub fn unmap_mmio_regions(&mut self) { - for mmio_region in self.common.mmio_regions.iter() { - for user_memory_region in mmio_region.user_memory_regions.iter() { + fn unmap_mmio_regions(&mut self) { + for mmio_region in self.common.mmio_regions.iter_mut() { + for user_memory_region in mmio_region.user_memory_regions.drain(..) { // Remove region - // SAFETY: only valid regions are in user_memory_regions + // SAFETY: guaranteed by hypervisor::mmap::MmapRegion invariants if let Err(e) = unsafe { self.vm.remove_user_memory_region( user_memory_region.slot, user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + user_memory_region.mapping.len(), + user_memory_region.mapping.addr(), false, false, ) @@ -229,22 +226,7 @@ impl VfioUserPciDevice { self.memory_slot_allocator .free_memory_slot(user_memory_region.slot); - - // Remove mmaps - // SAFETY: FFI call with correct arguments - let ret = unsafe { - libc::munmap( - user_memory_region.host_addr as *mut libc::c_void, - user_memory_region.size as usize, - ) - }; - if ret != 0 { - error!( - "Could not unmap region {}, error:{}", - mmio_region.index, - std::io::Error::last_os_error() - ); - } + // memory will be unmapped on drop } } } @@ -465,8 +447,8 @@ impl PciDevice for VfioUserPciDevice { self.vm.remove_user_memory_region( user_memory_region.slot, user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + user_memory_region.mapping.len(), + user_memory_region.mapping.addr(), false, false, ) @@ -486,8 +468,8 @@ impl PciDevice for VfioUserPciDevice { self.vm.create_user_memory_region( user_memory_region.slot, user_memory_region.start, - user_memory_region.size, - user_memory_region.host_addr, + user_memory_region.mapping.len(), + user_memory_region.mapping.addr(), false, false, ) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index a30ad60649..cd9a8fea75 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -16,13 +16,13 @@ use std::thread; use libc::EFD_NONBLOCK; use virtio_queue::Queue; use vm_device::UserspaceMapping; -use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestUsize}; +use vm_memory::{GuestAddress, GuestMemoryAtomic}; use vm_migration::{MigratableError, Pausable}; use vm_virtio::{AccessPlatform, VirtioDeviceType}; use vmm_sys_util::eventfd::EventFd; use crate::{ - ActivateError, ActivateResult, Error, GuestMemoryMmap, GuestRegionMmap, + ActivateError, ActivateResult, Error, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_RING_INDIRECT_DESC, }; @@ -46,10 +46,9 @@ pub struct VirtioSharedMemory { #[derive(Clone)] pub struct VirtioSharedMemoryList { - pub host_addr: u64, pub mem_slot: u32, pub addr: GuestAddress, - pub len: GuestUsize, + pub mapping: Arc, pub region_list: Vec, } diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index d766558472..ff8ad296b1 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -34,7 +34,7 @@ use super::{ }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; -use crate::{GuestMemoryMmap, MmapRegion, VirtioInterrupt, VirtioInterruptType}; +use crate::{GuestMemoryMmap, VirtioInterrupt, VirtioInterruptType}; const QUEUE_SIZE: u16 = 256; const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE]; @@ -267,10 +267,6 @@ pub struct Pmem { mapping: UserspaceMapping, seccomp_action: SeccompAction, exit_evt: EventFd, - - // Hold ownership of the memory that is allocated for the device - // which will be automatically dropped when the device is dropped - _region: MmapRegion, } #[derive(Serialize, Deserialize)] @@ -287,7 +283,6 @@ impl Pmem { disk: File, addr: GuestAddress, mapping: UserspaceMapping, - _region: MmapRegion, iommu: bool, seccomp_action: SeccompAction, exit_evt: EventFd, @@ -304,7 +299,7 @@ impl Pmem { } else { let config = VirtioPmemConfig { start: addr.raw_value().to_le(), - size: (_region.size() as u64).to_le(), + size: (mapping.mapping.size() as u64).to_le(), }; let mut avail_features = 1u64 << VIRTIO_F_VERSION_1; @@ -331,7 +326,6 @@ impl Pmem { config, mapping, seccomp_action, - _region, exit_evt, }) } diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index da54ef64da..ca60c2c063 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -1028,7 +1028,7 @@ impl PciDevice for VirtioPciDevice { let bar = PciBarConfiguration::default() .set_index(VIRTIO_SHM_BAR_INDEX) .set_address(shm_list.addr.raw_value()) - .set_size(shm_list.len); + .set_size(shm_list.mapping.size() as _); // The creation of the PCI BAR and its associated capabilities must // happen only during the creation of a brand new VM. When a VM is diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 2374a1cc71..b8d7289206 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -356,10 +356,9 @@ impl VirtioDevice for Fs { let mut mappings = Vec::new(); if let Some(cache) = self.cache.as_ref() { mappings.push(UserspaceMapping { - host_addr: cache.0.host_addr, mem_slot: cache.0.mem_slot, addr: cache.0.addr, - len: cache.0.len, + mapping: cache.0.mapping.clone(), mergeable: false, }); } diff --git a/vm-device/src/lib.rs b/vm-device/src/lib.rs index f484e9f14e..61e765950e 100644 --- a/vm-device/src/lib.rs +++ b/vm-device/src/lib.rs @@ -3,8 +3,11 @@ // SPDX-License-Identifier: Apache-2.0 // +use std::sync::Arc; + use serde::{Deserialize, Serialize}; -use vm_memory::{GuestAddress, GuestUsize}; +use vm_memory::bitmap::AtomicBitmap; +use vm_memory::{GuestAddress, MmapRegion}; mod bus; pub mod dma_mapping; @@ -62,9 +65,8 @@ pub enum Resource { #[derive(Clone)] pub struct UserspaceMapping { - pub host_addr: u64, pub mem_slot: u32, pub addr: GuestAddress, - pub len: GuestUsize, + pub mapping: Arc>, pub mergeable: bool, } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 1293a0daab..622c183f0b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -97,8 +97,10 @@ use vm_device::interrupt::{ InterruptIndex, InterruptManager, LegacyIrqGroupConfig, MsiIrqGroupConfig, }; use vm_device::{Bus, BusDevice, BusDeviceSync, Resource, UserspaceMapping}; +#[cfg(feature = "ivshmem")] +use vm_memory::bitmap::AtomicBitmap; use vm_memory::guest_memory::FileOffset; -use vm_memory::{Address, GuestAddress, GuestMemoryRegion, GuestUsize, MmapRegion}; +use vm_memory::{Address, GuestAddress, GuestMemoryRegion, GuestUsize, MmapRegion, VolatileMemory}; #[cfg(target_arch = "x86_64")] use vm_memory::{GuestAddressSpace, GuestMemory}; use vm_migration::protocol::MemoryRangeTable; @@ -817,15 +819,15 @@ impl DeviceRelocation for AddressManager { if let Some(mut shm_regions) = virtio_dev.get_shm_regions() && shm_regions.addr.raw_value() == old_base { - // SAFETY: TODO what are the invariants here? + // SAFETY: guaranteed by MmapRegion invariants unsafe { // Remove old mapping self.vm .remove_user_memory_region( shm_regions.mem_slot, old_base, - shm_regions.len, - shm_regions.host_addr, + shm_regions.mapping.len(), + shm_regions.mapping.as_ptr(), false, false, ) @@ -834,17 +836,14 @@ impl DeviceRelocation for AddressManager { "failed to remove user memory region: {e:?}" )) })?; - } - // SAFETY: TODO what are the invariants here? - unsafe { // Create new mapping by inserting new region to KVM. self.vm .create_user_memory_region( shm_regions.mem_slot, new_base, - shm_regions.len, - shm_regions.host_addr, + shm_regions.mapping.len(), + shm_regions.mapping.as_ptr(), false, false, ) @@ -3249,10 +3248,11 @@ impl DeviceManager { }, ) .map_err(DeviceManagerError::NewMmapRegion)?; - let host_addr: u64 = mmap_region.as_ptr() as u64; + let host_addr = mmap_region.as_ptr(); // SAFETY: host_addr points to region_size bytes of mmap-allocated memory. let mem_slot = unsafe { + let region_size = region_size.try_into().unwrap(); self.memory_manager .lock() .unwrap() @@ -3261,10 +3261,9 @@ impl DeviceManager { }?; let mapping = UserspaceMapping { - host_addr, mem_slot, addr: GuestAddress(region_base), - len: region_size, + mapping: Arc::new(mmap_region), mergeable: false, }; @@ -3274,7 +3273,6 @@ impl DeviceManager { file, GuestAddress(region_base), mapping, - mmap_region, self.force_iommu | pmem_cfg.iommu, self.seccomp_action.clone(), self.exit_evt @@ -4010,16 +4008,13 @@ impl DeviceManager { resources, )?; - // SAFETY: TODO // Note it is required to call 'add_pci_device()' in advance to have the list of // mmio regions provisioned correctly - unsafe { - vfio_user_pci_device - .lock() - .unwrap() - .map_mmio_regions() - .map_err(DeviceManagerError::VfioUserMapRegion) - }?; + vfio_user_pci_device + .lock() + .unwrap() + .map_mmio_regions() + .map_err(DeviceManagerError::VfioUserMapRegion)?; let mut node = device_node!(vfio_user_name, vfio_user_pci_device); @@ -4747,8 +4742,8 @@ impl DeviceManager { .unwrap() .remove_userspace_mapping( mapping.addr.raw_value(), - mapping.len, - mapping.host_addr, + mapping.mapping.size(), + mapping.mapping.as_ptr() as _, mapping.mergeable, mapping.mem_slot, ) @@ -5002,13 +4997,12 @@ impl IvshmemOps for IvshmemHandler { start_addr: u64, size: usize, backing_file: Option, - ) -> Result<(Arc, UserspaceMapping), IvshmemError> { + ) -> Result<(Arc>, UserspaceMapping), IvshmemError> { info!("Creating ivshmem mem region at 0x{start_addr:x}"); - let region: Arc = MemoryManager::create_ram_region( + let region = MemoryManager::create_ram_region_raw( &backing_file, 0, - GuestAddress(start_addr), size, false, true, @@ -5021,12 +5015,12 @@ impl IvshmemOps for IvshmemHandler { .map_err(|_| IvshmemError::CreateUserMemoryRegion)?; let mem_slot = { let mut manager = self.memory_manager.lock().unwrap(); - // SAFETY: guaranteed by GuestRegionMmap invariants + // SAFETY: guaranteed by MmapRegion invariants unsafe { manager.create_userspace_mapping( - region.start_addr().0, + start_addr, region.len(), - region.as_ptr() as u64, + region.as_ptr(), false, false, false, @@ -5034,11 +5028,11 @@ impl IvshmemOps for IvshmemHandler { } } .map_err(|_| IvshmemError::CreateUserspaceMapping)?; + let region = Arc::new(region); let mapping = UserspaceMapping { - host_addr: region.as_ptr() as u64, + mapping: region.clone(), mem_slot, - addr: GuestAddress(region.start_addr().0), - len: region.len(), + addr: GuestAddress(start_addr), mergeable: false, }; Ok((region, mapping)) @@ -5050,8 +5044,8 @@ impl IvshmemOps for IvshmemHandler { unsafe { manager.remove_userspace_mapping( mapping.addr.raw_value(), - mapping.len, - mapping.host_addr, + mapping.mapping.len(), + mapping.mapping.as_ptr(), mapping.mergeable, mapping.mem_slot, ) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 146ea610e4..a9efbe99a6 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -8,7 +8,7 @@ use std::collections::BTreeMap; use std::collections::HashMap; use std::fs::{File, OpenOptions}; use std::io::{self}; -use std::ops::{BitAnd, Deref, Not, Sub}; +use std::ops::{BitAnd, Not, Sub}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use std::os::fd::AsFd; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; @@ -900,13 +900,12 @@ impl MemoryManager { for (zone_id, regions) in list { for (region, virtio_mem) in regions { - // SAFETY: regions only holds valid addresses. - // TODO: encapsulate this unsafety in a small part of the file. + // SAFETY: guaranteed by GuestRegionMmap invariants let slot = unsafe { self.create_userspace_mapping( region.start_addr().raw_value(), - region.len(), - region.as_ptr() as u64, + region.len().try_into().unwrap(), + region.as_ptr(), self.mergeable, false, self.log_dirty, @@ -962,13 +961,16 @@ impl MemoryManager { arch::layout::UEFI_START, ) .unwrap(); + const _: () = assert!(core::mem::size_of::() == core::mem::size_of::()); + + // SAFETY: guaranteed by GuestRegionMmap unsafe { self.vm .create_user_memory_region( uefi_mem_slot, uefi_region.start_addr().raw_value(), - uefi_region.len(), - uefi_region.as_ptr() as u64, + uefi_region.len() as usize, + uefi_region.as_ptr(), false, false, ) @@ -1364,10 +1366,9 @@ impl MemoryManager { } #[allow(clippy::too_many_arguments)] - pub fn create_ram_region( + pub fn create_ram_region_raw( backing_file: &Option, file_offset: u64, - start_addr: GuestAddress, size: usize, prefault: bool, shared: bool, @@ -1376,7 +1377,7 @@ impl MemoryManager { host_numa_node: Option, existing_memory_file: Option, thp: bool, - ) -> Result, Error> { + ) -> Result, Error> { let mut mmap_flags = libc::MAP_NORESERVE; // The duplication of mmap_flags ORing here is unfortunate but it also makes @@ -1403,17 +1404,13 @@ impl MemoryManager { None }; - let region = GuestRegionMmap::new( - MmapRegion::build(fo, size, libc::PROT_READ | libc::PROT_WRITE, mmap_flags) - .map_err(Error::GuestMemoryRegion)?, - start_addr, - ) - .map_err(Error::GuestMemory)?; + let region = MmapRegion::build(fo, size, libc::PROT_READ | libc::PROT_WRITE, mmap_flags) + .map_err(Error::GuestMemoryRegion)?; // Apply NUMA policy if needed. if let Some(node) = host_numa_node { - let addr = region.deref().as_ptr(); - let len = region.deref().size() as u64; + let addr = region.as_ptr(); + let len = region.size() as u64; let mode = MPOL_BIND; let mut nodemask: Vec = Vec::new(); let flags = MPOL_MF_STRICT | MPOL_MF_MOVE; @@ -1498,7 +1495,39 @@ impl MemoryManager { } } - Ok(Arc::new(region)) + Ok(region) + } + + #[allow(clippy::too_many_arguments)] + pub fn create_ram_region( + backing_file: &Option, + file_offset: u64, + start_addr: GuestAddress, + size: usize, + prefault: bool, + shared: bool, + hugepages: bool, + hugepage_size: Option, + host_numa_node: Option, + existing_memory_file: Option, + thp: bool, + ) -> Result, Error> { + let r = Self::create_ram_region_raw( + backing_file, + file_offset, + size, + prefault, + shared, + hugepages, + hugepage_size, + host_numa_node, + existing_memory_file, + thp, + )?; + + Ok(Arc::new( + GuestRegionMmap::new(r, start_addr).map_err(Error::GuestMemory)?, + )) } // Duplicate of `memory_zone_get_align_size` that does not require a `zone` @@ -1612,12 +1641,12 @@ impl MemoryManager { )?; // Map it into the guest - // SAFETY: create_ram_region only produces valid mappings. + // SAFETY: guaranteed by GuestMmapRegion invariants let slot = unsafe { self.create_userspace_mapping( region.start_addr().0, - region.len(), - region.as_ptr() as u64, + region.len().try_into().unwrap(), + region.as_ptr(), self.mergeable, false, self.log_dirty, @@ -1722,8 +1751,8 @@ impl MemoryManager { pub unsafe fn create_userspace_mapping( &mut self, guest_phys_addr: u64, - memory_size: u64, - userspace_addr: u64, + memory_size: usize, + userspace_addr: *mut u8, mergeable: bool, readonly: bool, log_dirty: bool, @@ -1731,10 +1760,11 @@ impl MemoryManager { let slot = self.allocate_memory_slot(); info!( - "Creating userspace mapping: {guest_phys_addr:x} -> {userspace_addr:x} {memory_size:x}, slot {slot}" + "Creating userspace mapping: {guest_phys_addr:x} -> {userspace_addr_:x} {memory_size:x}, slot {slot}", + userspace_addr_ = userspace_addr as u64 ); - // SAFETY: promised by caller + // SAFETY: caller promises parameters are correct. unsafe { self.vm .create_user_memory_region( @@ -1788,7 +1818,8 @@ impl MemoryManager { } info!( - "Created userspace mapping: {guest_phys_addr:x} -> {userspace_addr:x} {memory_size:x}" + "Created userspace mapping: {guest_phys_addr:x} -> {userspace_addr_:x} {memory_size:x}", + userspace_addr_ = userspace_addr as u64 ); Ok(slot) @@ -1806,12 +1837,12 @@ impl MemoryManager { pub unsafe fn remove_userspace_mapping( &mut self, guest_phys_addr: u64, - memory_size: u64, - userspace_addr: u64, + memory_size: usize, + userspace_addr: *mut u8, mergeable: bool, slot: u32, ) -> Result<(), Error> { - // SAFETY: The caller promises that the parameters are correct. + // SAFETY: Caller promises parameters are correct. unsafe { self.vm .remove_user_memory_region( @@ -1852,7 +1883,8 @@ impl MemoryManager { } info!( - "Removed userspace mapping: {guest_phys_addr:x} -> {userspace_addr:x} {memory_size:x}" + "Removed userspace mapping: {guest_phys_addr:x} -> {userspace_addr_:x} {memory_size:x}", + userspace_addr_ = userspace_addr as u64 ); Ok(()) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 89255e28b4..8803f68aa0 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -3469,8 +3469,8 @@ mod unit_tests { vm.create_user_memory_region( index as u32, region.start_addr().raw_value(), - region.len(), - region.as_ptr() as u64, + region.len().try_into().unwrap(), + region.as_ptr(), false, false, ) @@ -3607,8 +3607,8 @@ pub fn test_vm() { vm.create_user_memory_region( index as u32, region.start_addr().raw_value(), - region.len(), - region.as_ptr() as u64, + region.len().try_into().unwrap(), + region.as_ptr() as _, false, false, ) From 199d2d05d830fc07d90e48a4e7ed8d9335bfba24 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 26 Jun 2025 20:03:30 -0400 Subject: [PATCH 0313/1893] hypervisor: tdx: do not use u64 to represent pointers Also drop support for building the TDX code for 32-bit targets. All CPUs with TDX support are 64-bit so supporting 32-bit targets is not needed. Signed-off-by: Demi Marie Obenour --- hypervisor/src/kvm/mod.rs | 32 +++++++++++++++++++++----------- hypervisor/src/vm.rs | 4 ++-- vmm/src/vm.rs | 4 ++-- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index d9dc9b7549..cb21c28e2d 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -1001,7 +1001,7 @@ impl vm::Vm for KvmVm { &self.fd.as_raw_fd(), TdxCommand::InitVm, 0, - &data as *const _ as u64, + &data as *const _ as *const _, ) .map_err(vm::HypervisorVmError::InitializeTdx) } @@ -1011,8 +1011,13 @@ impl vm::Vm for KvmVm { /// #[cfg(feature = "tdx")] fn tdx_finalize(&self) -> vm::Result<()> { - tdx_command(&self.fd.as_raw_fd(), TdxCommand::Finalize, 0, 0) - .map_err(vm::HypervisorVmError::FinalizeTdx) + tdx_command( + &self.fd.as_raw_fd(), + TdxCommand::Finalize, + 0, + std::ptr::null(), + ) + .map_err(vm::HypervisorVmError::FinalizeTdx) } /// @@ -1021,9 +1026,9 @@ impl vm::Vm for KvmVm { #[cfg(feature = "tdx")] fn tdx_init_memory_region( &self, - host_address: u64, + host_address: *mut u8, guest_address: u64, - size: u64, + size: usize, measure: bool, ) -> vm::Result<()> { #[repr(C)] @@ -1033,16 +1038,16 @@ impl vm::Vm for KvmVm { pages: u64, } let data = TdxInitMemRegion { - host_address, + host_address: host_address as _, guest_address, - pages: size / 4096, + pages: (size / 4096).try_into().unwrap(), }; tdx_command( &self.fd.as_raw_fd(), TdxCommand::InitMemRegion, u32::from(measure), - &data as *const _ as u64, + &data as *const _ as *const _, ) .map_err(vm::HypervisorVmError::InitMemRegionTdx) } @@ -1058,7 +1063,7 @@ fn tdx_command( fd: &RawFd, command: TdxCommand, flags: u32, - data: u64, + data: *const libc::c_void, ) -> std::result::Result<(), std::io::Error> { #[repr(C)] struct TdxIoctlCmd { @@ -1071,7 +1076,7 @@ fn tdx_command( let cmd = TdxIoctlCmd { command, flags, - data, + data: data as _, error: 0, unused: 0, }; @@ -1287,7 +1292,7 @@ impl hypervisor::Hypervisor for KvmHypervisor { &self.kvm.as_raw_fd(), TdxCommand::Capabilities, 0, - &data as *const _ as u64, + &data as *const _ as *const _, ) .map_err(|e| hypervisor::HypervisorError::TdxCapabilities(e.into()))?; @@ -2648,6 +2653,11 @@ impl cpu::Vcpu for KvmVcpu { /// #[cfg(feature = "tdx")] fn tdx_init(&self, hob_address: u64) -> cpu::Result<()> { + // On 32-bit, the next cast would clobber the high 32 bits. + #[cfg(not(target_pointer_width = "64"))] + compile_error!("32-bit TDX not supported"); + let hob_address = hob_address as *const _; + tdx_command(&self.fd.as_raw_fd(), TdxCommand::InitVcpu, 0, hob_address) .map_err(cpu::HypervisorCpuError::InitializeTdx) } diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 9d4bf5c3d6..5e1cf7ea8d 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -403,9 +403,9 @@ pub trait Vm: Send + Sync + Any { /// Initialize a TDX memory region for this VM fn tdx_init_memory_region( &self, - _host_address: u64, + _host_address: *mut u8, _guest_address: u64, - _size: u64, + _size: usize, _measure: bool, ) -> Result<()> { unimplemented!() diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 8803f68aa0..24d859b14a 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2251,9 +2251,9 @@ impl Vm { for section in sections { self.vm .tdx_init_memory_region( - mem.get_host_address(GuestAddress(section.address)).unwrap() as u64, + mem.get_host_address(GuestAddress(section.address)).unwrap(), section.address, - section.size, + section.size.try_into().unwrap(), /* TDVF_SECTION_ATTRIBUTES_EXTENDMR */ section.attributes == 1, ) From 06b76972e2ae1207fb8eea83157ed1f2724240c7 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 27 Jun 2025 18:00:27 -0400 Subject: [PATCH 0314/1893] pci: move operation out of loop No functional change intended. Signed-off-by: Demi Marie Obenour --- pci/src/vfio.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 7c85f557c8..dcca015ab3 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -288,10 +288,10 @@ unsafe trait MmioRegionRange { unsafe impl MmioRegionRange for Vec { // Check if a guest address is within the range of mmio regions fn check_range(&self, guest_addr: u64, size: u64) -> bool { + let Some(guest_addr_end) = guest_addr.checked_add(size) else { + return false; + }; for region in self.iter() { - let Some(guest_addr_end) = guest_addr.checked_add(size) else { - return false; - }; let Some(region_end) = region.start.raw_value().checked_add(region.length) else { return false; }; From 8f6a6a85e0ac3c9ad64e98256a3955f330372104 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 27 Jun 2025 18:07:56 -0400 Subject: [PATCH 0315/1893] virtio-devices: mark Vdpa::dma_map as unsafe I believe that its only caller used it safely, but it is still better to mark the code as unsafe. Also add additional validity checks. Signed-off-by: Demi Marie Obenour --- virtio-devices/src/vdpa.rs | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index e1f7dd0d63..7d4bcb96a9 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -326,17 +326,28 @@ impl Vdpa { .map_err(Error::SetStatus) } - fn dma_map( + /// # SAFETY + /// + /// `host_vaddr` must point to `size` bytes of valid memory. + unsafe fn dma_map( &mut self, iova: u64, size: u64, host_vaddr: *const u8, readonly: bool, ) -> Result<()> { - let iova_last = iova + size - 1; + let Some(iova_last) = iova.checked_add(size) else { + return Err(Error::InvalidIovaRange(iova, u64::MAX)); + }; + let Some(iova_last) = iova_last.checked_sub(1) else { + return Err(Error::InvalidIovaRange(0, 0)); + }; if iova < self.iova_range.first || iova_last > self.iova_range.last { return Err(Error::InvalidIovaRange(iova, iova_last)); } + if isize::try_from(size).is_err() { + return Err(Error::InvalidIovaRange(iova, iova_last)); + } assert!(self.vhost.is_some()); self.vhost @@ -552,16 +563,20 @@ impl ExternalDmaMapping for VdpaDmaMapping std::result::Result<(), std::io::Error> { From 12c7cc5e4f1329bc066478a23c2bab0dbbed4a67 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Tue, 8 Jul 2025 14:04:32 -0400 Subject: [PATCH 0316/1893] pci: Remove dma_map() and dma_unmap() These APIs had no users, were not documented, and were unsound. Signed-off-by: Demi Marie Obenour --- pci/src/vfio.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index dcca015ab3..30bf9fbfea 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -1764,26 +1764,6 @@ impl VfioPciDevice { } } - pub fn dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<(), VfioPciError> { - if !self.iommu_attached { - self.container - .vfio_dma_map(iova, size, user_addr) - .map_err(|e| VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf))?; - } - - Ok(()) - } - - pub fn dma_unmap(&self, iova: u64, size: u64) -> Result<(), VfioPciError> { - if !self.iommu_attached { - self.container - .vfio_dma_unmap(iova, size) - .map_err(|e| VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf))?; - } - - Ok(()) - } - pub fn mmio_regions(&self) -> Vec { self.common.mmio_regions.clone() } From 8be28f843866dd1b4da5c3b7d19a2f737aaaaefe Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 27 Jun 2025 19:17:00 -0400 Subject: [PATCH 0317/1893] misc: Work around vfio_dma_map being unsound This API passes a u64 to a kernel API that treats the u64 as a userspace address. Therefore, it should be marked unsafe, but it currently is not [1]. Wrap the call in an unsafe block to document that invariants must be upheld to avoid undefined behavior. This causes a compiler warning, so suppress the warning with #[allow(unused_unsafe)]. [1]: https://github.com/rust-vmm/vfio/issues/100 Signed-off-by: Demi Marie Obenour --- pci/src/vfio.rs | 68 +++++++++++++++++++++++++-------------- virtio-devices/src/mem.rs | 52 ++++++++++++++++++++++++++---- vmm/src/device_manager.rs | 24 ++++++++++---- 3 files changed, 107 insertions(+), 37 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 30bf9fbfea..a1c28c9114 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -1703,17 +1703,22 @@ impl VfioPciDevice { .map_err(VfioPciError::CreateUserMemoryRegion)?; if !self.iommu_attached { - self.container - .vfio_dma_map( + // vfio_dma_map should be unsafe but isn't. + #[allow(unused_unsafe)] + // SAFETY: MmapRegion invariants guarantee that + // user_memory_region.mapping.addr() points to + // user_memory_region.mapping.len() bytes of + // valid memory that will only be unmapped with munmap(). + unsafe { + self.container.vfio_dma_map( user_memory_region.start, user_memory_region.mapping.len().try_into().unwrap(), (user_memory_region.mapping.addr() as usize) .try_into() .unwrap(), ) - .map_err(|e| { - VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf) - })?; + } + .map_err(|e| VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf))?; } region.user_memory_regions.push(user_memory_region); } @@ -1745,6 +1750,7 @@ impl VfioPciDevice { // Remove region // SAFETY: only valid entries are added to the user_memory_regions field // of the entries of self.common.mmio_regions. + // Also, host_addr..host_addr + len is valid by the MmapRegion invariants. if let Err(e) = unsafe { self.vm.remove_user_memory_region( user_memory_region.slot, @@ -1897,7 +1903,9 @@ iova 0x{:x}, size 0x{:x}: {}, ", ); } // Remove old region - // SAFETY: validity of len and host_addr guaranteed by hypervisor::mmap::MmapRegion + // SAFETY: MmapRegion invariants guarantee that + // host_addr points to len bytes of + // valid memory that will only be unmapped with munmap(). unsafe { self.vm.remove_user_memory_region( user_memory_region.slot, @@ -1918,7 +1926,9 @@ iova 0x{:x}, size 0x{:x}: {}, ", } // Insert new region - // SAFETY: validity of len and host_addr guaranteed by hypervisor::mmap::MmapRegion + // SAFETY: MmapRegion invariants guarantee that + // host_addr points to len bytes of + // valid memory that will only be unmapped with munmap(). unsafe { self.vm.create_user_memory_region( user_memory_region.slot, @@ -1933,22 +1943,26 @@ iova 0x{:x}, size 0x{:x}: {}, ", // Map the moved mmio region to vfio container if !self.iommu_attached { - self.container - .vfio_dma_map( + // vfio_dma_map is unsound and ought to be marked as unsafe + #[allow(unused_unsafe)] + // SAFETY: MmapRegion invariants guarantee that + // host_addr points to len bytes of + // valid memory that will only be unmapped with munmap(). + unsafe { + self.container.vfio_dma_map( user_memory_region.start, len.try_into().unwrap(), (host_addr as usize).try_into().unwrap(), ) - .map_err(|e| { - VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf) - }) - .map_err(|e| { - io::Error::other(format!( - "Could not map mmio region to vfio container: \ + } + .map_err(|e| VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf)) + .map_err(|e| { + io::Error::other(format!( + "Could not map mmio region to vfio container: \ iova 0x{:x}, size 0x{:x}: {}, ", - user_memory_region.start, len, e - )) - })?; + user_memory_region.start, len, e + )) + })?; } } } @@ -2049,17 +2063,21 @@ impl ExternalDmaMapping for VfioDmaMapping p, }; + // vfio_dma_map is unsound and ought to be marked as unsafe + #[allow(unused_unsafe)] // SAFETY: find_user_address and GuestMemory::get_slice() guarantee that // the returned pointer is valid for up to `usize_size` bytes. // `usize_size` is always equal to `size` due to the above `try_into()` call. - self.container - .vfio_dma_map(iova, size, (user_addr as usize).try_into().unwrap()) - .map_err(|e| { - io::Error::other(format!( - "failed to map memory for VFIO container, \ + unsafe { + self.container + .vfio_dma_map(iova, size, (user_addr as usize).try_into().unwrap()) + } + .map_err(|e| { + io::Error::other(format!( + "failed to map memory for VFIO container, \ iova 0x{iova:x}, gpa 0x{gpa:x}, size 0x{size:x}: {e:?}" - )) - }) + )) + }) } fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), io::Error> { diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 06fd160d39..eda263c0f6 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -399,7 +399,7 @@ impl BlocksState { struct MemEpollHandler { mem: GuestMemoryAtomic, - host_addr: u64, + region: Arc, host_fd: Option, blocks_state: Arc>, config: Arc>, @@ -412,8 +412,45 @@ struct MemEpollHandler { dma_mapping_handlers: Arc>>>, } +fn usize_to_u64(i: usize) -> u64 { + const _: () = assert!(size_of::() <= size_of::()); + i as _ +} + impl MemEpollHandler { + /// # Panics + /// + /// Panics if any of the following hold: + /// - region size exceeds [`libc::off64_t::MAX`], [`libc::size_t::MAX`], + /// or [`isize::MAX`] + /// - `size + offset` exceeds the size of the region (including overflow). fn discard_memory_range(&self, offset: u64, size: u64) -> Result<(), Error> { + let max_size = usize_to_u64(self.region.size()); + + // Validate the region size to ensure the below casts + // are lossless. + libc::size_t::try_from(max_size).unwrap(); + libc::off64_t::try_from(max_size).unwrap(); + isize::try_from(max_size).unwrap(); + + // Check that offset is in bounds. + assert!(max_size >= offset); + + if size == 0 { + // Do not try to deallocate a zero size. + return Ok(()); + } + + // Check that offset + size is in bounds and does not overflow. + // Since size is checked to be nonzero above, this also means that + // offset is not past the end. + assert!(max_size - offset >= size); + + // Since offset and size are each bounded above by max_size, + // and max_size came from usize and was checked to be able to be + // losslessly cast to size_t and off64_t, this also checks that offset + // and size can each be losslessly cast to all of these types. + // Use fallocate if the memory region is backed by a file. if let Some(fd) = self.host_fd { // SAFETY: FFI call with valid arguments @@ -435,10 +472,13 @@ impl MemEpollHandler { // Only use madvise if the memory region is not allocated with // hugepages. if !self.hugepages { - // SAFETY: FFI call with valid arguments + // SAFETY: FFI call with valid arguments. + // offset + madvize_size was checked in bounds above, + // and madvise_size is checked to not be zero so ptr_offset + // alone is not past the end. let res = unsafe { libc::madvise( - (self.host_addr + offset) as *mut libc::c_void, + self.region.as_ptr().offset(offset as isize) as *mut libc::c_void, size as libc::size_t, libc::MADV_DONTNEED, ) @@ -687,7 +727,7 @@ pub struct MemState { pub struct Mem { common: VirtioCommon, id: String, - host_addr: u64, + region: Arc, host_fd: Option, config: Arc>, seccomp_action: SeccompAction, @@ -780,7 +820,7 @@ impl Mem { ..Default::default() }, id, - host_addr: region.as_ptr() as u64, + region: region.clone(), host_fd, config: Arc::new(Mutex::new(config)), seccomp_action, @@ -923,7 +963,7 @@ impl VirtioDevice for Mem { let mut handler = MemEpollHandler { mem, - host_addr: self.host_addr, + region: self.region.clone(), host_fd: self.host_fd, blocks_state: Arc::clone(&self.blocks_state), config: self.config.clone(), diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 622c183f0b..81a061aade 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3754,13 +3754,19 @@ impl DeviceManager { // virtio-mem device itself. for (_, zone) in self.memory_manager.lock().unwrap().memory_zones().iter() { for region in zone.regions() { - vfio_container - .vfio_dma_map( + // vfio_dma_map is unsound and ought to be marked as unsafe + #[allow(unused_unsafe)] + // SAFETY: GuestMemoryMmap guarantees that region points + // to len bytes of valid memory starting at as_ptr() + // that will only be freed with munmap(). + unsafe { + vfio_container.vfio_dma_map( region.start_addr().raw_value(), region.len(), region.as_ptr() as u64, ) - .map_err(DeviceManagerError::VfioDmaMap)?; + } + .map_err(DeviceManagerError::VfioDmaMap)?; } } @@ -4368,13 +4374,19 @@ impl DeviceManager { // Take care of updating the memory for VFIO PCI devices. if let Some(vfio_container) = &self.vfio_container { - vfio_container - .vfio_dma_map( + // vfio_dma_map is unsound and ought to be marked as unsafe + #[allow(unused_unsafe)] + // SAFETY: GuestMemoryMmap guarantees that region points + // to len bytes of valid memory starting at as_ptr() + // that will only be freed with munmap(). + unsafe { + vfio_container.vfio_dma_map( new_region.start_addr().raw_value(), new_region.len(), new_region.as_ptr() as u64, ) - .map_err(DeviceManagerError::UpdateMemoryForVfioPciDevice)?; + } + .map_err(DeviceManagerError::UpdateMemoryForVfioPciDevice)?; } // Take care of updating the memory for vfio-user devices. From 0e21b56aeac10eb4a5facbdcb34ec68ee890b120 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 27 Jun 2025 18:22:09 -0400 Subject: [PATCH 0318/1893] pci: do not check for page-aligned size and offset before calling mmap() The kernel will validate that the size is page-aligned. The file offset is always zero, so the kernel will also validate that the offset is page-aligned. Signed-off-by: Demi Marie Obenour --- pci/src/vfio.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index a1c28c9114..23efa8c3d7 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -1650,17 +1650,6 @@ impl VfioPciDevice { self.common.interrupt.msix.as_ref(), )?; - for area in &sparse_areas { - if !is_page_size_aligned(area.size) || !is_page_size_aligned(area.offset) { - error!( - "Could not mmap sparse area that is not page size aligned \ -(offset = 0x{:x}, size = 0x{:x})", - area.offset, area.size - ); - return Err(VfioPciError::MmapArea); - } - } - for area in sparse_areas.iter() { let mapping = match MmapRegion::mmap( area.size, From 021f450cdb43d3de461829c9f7374c8c974887c8 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 27 Jun 2025 22:06:50 -0400 Subject: [PATCH 0319/1893] virtio-devices: proper bounds checks Callers of get_host_address_range() rely on it returning a pointer to at least size bytes of memory. mem.get_host_address() is an overrideable method of a safe trait, so it is better for safe code to not rely on its correctness for safety. Instead, use mem.get_slice(), which returns a VolatileSlice whose invariants guarantee that it points to a sufficient amount of memory. If mem.check_range() succeeds but mem.get_slice() returns a slice that is too small, this means that there is either a logic error or a situation the code cannot support yet, so panic. Signed-off-by: Demi Marie Obenour --- virtio-devices/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index 86359da657..fad2d987f5 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -170,7 +170,11 @@ pub fn get_host_address_range( size: usize, ) -> Option<*mut u8> { if mem.check_range(addr, size) { - Some(mem.get_host_address(addr).unwrap()) + let slice = mem.get_slice(addr, size).unwrap(); + assert!(slice.len() >= size); + // TODO: return a VolatileSlice and fix all callers. + #[allow(deprecated)] + Some(slice.as_ptr()) } else { None } From 969a3b57a3282b9fd17b5cb7c4d18f22717517d2 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 27 Jun 2025 22:08:26 -0400 Subject: [PATCH 0320/1893] misc: tdx: make tdx_init_memory_region() unsafe It takes a pointer to a userspace address that it accesses, so it should be marked unsafe. This was missed earlier. Signed-off-by: Demi Marie Obenour --- hypervisor/src/kvm/mod.rs | 6 ++++-- hypervisor/src/vm.rs | 6 +++++- vmm/src/vm.rs | 18 +++++++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index cb21c28e2d..e005834b96 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -1020,11 +1020,13 @@ impl vm::Vm for KvmVm { .map_err(vm::HypervisorVmError::FinalizeTdx) } - /// /// Initialize memory regions for the TDX VM /// + /// # Safety + /// + /// `host_address` must be valid for `size` bytes #[cfg(feature = "tdx")] - fn tdx_init_memory_region( + unsafe fn tdx_init_memory_region( &self, host_address: *mut u8, guest_address: u64, diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 5e1cf7ea8d..3b43c453ab 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -401,7 +401,11 @@ pub trait Vm: Send + Sync + Any { } #[cfg(feature = "tdx")] /// Initialize a TDX memory region for this VM - fn tdx_init_memory_region( + /// + /// # Safety + /// + /// `_host_address` must be valid for `_size` bytes + unsafe fn tdx_init_memory_region( &self, _host_address: *mut u8, _guest_address: u64, diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 24d859b14a..b5bb4a70ab 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2249,15 +2249,23 @@ impl Vm { let mem = guest_memory.memory(); for section in sections { - self.vm - .tdx_init_memory_region( - mem.get_host_address(GuestAddress(section.address)).unwrap(), + let size = section.size.try_into().unwrap(); + // SAFETY: get_host_address_range does proper bounds checking + unsafe { + self.vm.tdx_init_memory_region( + virtio_devices::get_host_address_range( + &*mem, + GuestAddress(section.address), + size, + ) + .unwrap(), section.address, - section.size.try_into().unwrap(), + size, /* TDVF_SECTION_ATTRIBUTES_EXTENDMR */ section.attributes == 1, ) - .map_err(Error::InitializeTdxMemoryRegion)?; + } + .map_err(Error::InitializeTdxMemoryRegion)?; } Ok(()) From 2be304b39221ba4dfd7d0e51525e4193dfbc89fd Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 27 Jun 2025 22:10:37 -0400 Subject: [PATCH 0321/1893] misc: Check that get_slice() returned a big enough slice This should be guaranteed by GuestMemory and GuestMemoryRegion, but those traits are currently safe, so add checks to guard against incorrect implementations of them. Signed-off-by: Demi Marie Obenour --- block/src/lib.rs | 5 +++-- devices/src/pvmemcontrol.rs | 12 +++++------- net_util/src/queue_pair.rs | 10 ++++++---- virtio-devices/src/balloon.rs | 11 +++++++---- virtio-devices/src/vdpa.rs | 11 +++++------ 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 9ad36ac832..a6ed32bc66 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -440,8 +440,9 @@ impl Request { let origin_ptr = mem .get_slice(data_addr, data_len) - .map_err(ExecuteError::GetHostAddress)? - .ptr_guard(); + .map_err(ExecuteError::GetHostAddress)?; + assert!(origin_ptr.len() >= data_len); + let origin_ptr = origin_ptr.ptr_guard(); // Verify the buffer alignment. // In case it's not properly aligned, an intermediate buffer is diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index f096be69fb..d078a1db15 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -429,22 +429,20 @@ impl PvmemcontrolBusDevice { /// [`range_base`, `range_base` + `range_len`) is present in the guest fn operate_on_memory_range(&self, addr: u64, length: u64, f: F) -> result::Result<(), Error> where - F: FnOnce(*mut libc::c_void, libc::size_t) -> libc::c_int, + F: FnOnce(*mut libc::c_void, usize) -> libc::c_int, { let memory = self.mem.memory(); let range_base = GuestAddress(addr); let range_len = usize::try_from(length).map_err(|_| Error::InvalidRequest)?; // assume guest memory is not interleaved with vmm memory on the host. - if !memory.check_range(range_base, range_len) { + let Ok(slice) = memory.get_slice(range_base, range_len) else { return Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress( range_base, ))); - } - let hva = memory - .get_host_address(range_base) - .map_err(Error::GuestMemory)?; - let res = f(hva as *mut libc::c_void, range_len as libc::size_t); + }; + assert!(slice.len() >= range_len); + let res = f(slice.ptr_guard_mut().as_ptr() as _, slice.len()); if res != 0 { return Err(Error::LibcFail(io::Error::last_os_error())); } diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index 0ad328ff9f..6202833a16 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -67,8 +67,9 @@ impl TxVirtio { let buf = desc_chain .memory() .get_slice(desc_addr, desc.len() as usize) - .map_err(NetQueuePairError::GuestMemory)? - .ptr_guard_mut(); + .map_err(NetQueuePairError::GuestMemory)?; + assert!(buf.len() >= desc.len() as usize); + let buf = buf.ptr_guard_mut(); let iovec = libc::iovec { iov_base: buf.as_ptr() as *mut libc::c_void, iov_len: desc.len() as libc::size_t, @@ -208,8 +209,9 @@ impl RxVirtio { let buf = desc_chain .memory() .get_slice(desc_addr, desc.len() as usize) - .map_err(NetQueuePairError::GuestMemory)? - .ptr_guard_mut(); + .map_err(NetQueuePairError::GuestMemory)?; + assert!(buf.len() >= desc.len() as usize); + let buf = buf.ptr_guard_mut(); let iovec = libc::iovec { iov_base: buf.as_ptr() as *mut libc::c_void, iov_len: desc.len() as libc::size_t, diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index d7ce192622..dfb81d0a91 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -174,12 +174,15 @@ impl BalloonEpollHandler { range_len: usize, advice: libc::c_int, ) -> result::Result<(), Error> { - let hva = memory - .get_host_address(range_base) + let slice = memory + .get_slice(range_base, range_len) .map_err(Error::GuestMemory)?; + assert!(slice.len() >= range_len); let res = - // SAFETY: Need unsafe to do syscall madvise - unsafe { libc::madvise(hva as *mut libc::c_void, range_len as libc::size_t, advice) }; + // SAFETY: FFI call with valid arguments, guaranteed by VolatileSlice + unsafe { + libc::madvise(slice.ptr_guard_mut().as_ptr() as *mut libc::c_void, + range_len as libc::size_t, advice) }; if res != 0 { return Err(Error::MadviseFail(io::Error::last_os_error())); } diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 7d4bcb96a9..7c38e2f30e 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -19,7 +19,7 @@ use vhost::{VhostBackend, VringConfigData}; use virtio_queue::desc::RawDescriptor; use virtio_queue::{Queue, QueueT}; use vm_device::dma_mapping::ExternalDmaMapping; -use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; +use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemoryAtomic}; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; @@ -27,7 +27,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::{ ActivateError, ActivateResult, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FEATURES_OK, GuestMemoryMmap, VIRTIO_F_IOMMU_PLATFORM, VirtioCommon, VirtioDevice, - VirtioInterrupt, VirtioInterruptType, + VirtioInterrupt, VirtioInterruptType, get_host_address_range, }; #[derive(Error, Debug)] @@ -548,11 +548,10 @@ impl VdpaDmaMapping { impl ExternalDmaMapping for VdpaDmaMapping { fn map(&self, iova: u64, gpa: u64, size: u64) -> result::Result<(), io::Error> { + let usize_size = size.try_into().unwrap(); let mem = self.memory.memory(); let guest_addr = GuestAddress(gpa); - let user_addr = if mem.check_range(guest_addr, size as usize) { - mem.get_host_address(guest_addr).unwrap() as *const u8 - } else { + let Some(user_addr) = get_host_address_range(&*mem, guest_addr, usize_size) else { return Err(io::Error::other(format!( "failed to convert guest address 0x{gpa:x} into \ host user virtual address" @@ -563,7 +562,7 @@ impl ExternalDmaMapping for VdpaDmaMapping Date: Tue, 21 Oct 2025 10:02:09 +0200 Subject: [PATCH 0322/1893] block: advisory locks: use byte-range locks to match QEMU behavior The granularity has significant implications in typical cloud deployments with network storage. The Linux kernel will sync advisory locks to network file systems, but these backends may have different policies and handle locks differently. For example, Netapp speaks a NFS API but will treat advisory OFD locks for the whole file as mandatory locks, whereas byte-range locks for the whole file will remain advisory [0]. As it is a valid use case to prevent multiple CHV instances from accessing the same disk but disk management software (e.g., Cinder in OpenStack) should be able to snapshot disks while VMs are running, we need special control over the lock granularity. Therefore, it is a valid use case to lock the whole byte range of a disk image without technically locking the whole file - to get the best of both worlds. This also brings CHVs behavior in line with QEMU [1]. Whole-file locks remain a valid use case and could be supported later. This patch only provides the necessary groundwork; making it configurable is out of scope for now. [0] https://kb.netapp.com/on-prem/ontap/da/NAS/NAS-KBs/How_is_Mandatory_Locking_supported_for_NFSv4_on_ONTAP_9 [1] /util/osdep.c::qemu_lock_fcntl() Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- block/src/fcntl.rs | 67 ++++++++++++++++++++++++++++++++----- virtio-devices/src/block.rs | 34 ++++++++++++++++--- 2 files changed, 87 insertions(+), 14 deletions(-) diff --git a/block/src/fcntl.rs b/block/src/fcntl.rs index 2e34de1d6a..3687288a6b 100644 --- a/block/src/fcntl.rs +++ b/block/src/fcntl.rs @@ -101,13 +101,52 @@ impl LockState { } } +/// The granularity of the advisory lock. +/// +/// The granularity has significant implications in typical cloud deployments +/// with network storage. The Linux kernel will sync advisory locks to network +/// file systems, but these backends may have different policies and handle +/// locks differently. For example, Netapp speaks a NFS API but will treat +/// advisory OFD locks for the whole file as mandatory locks, whereas byte-range +/// locks for the whole file will remain advisory [0]. +/// +/// As it is a valid use case to prevent multiple CHV instances from accessing +/// the same disk but disk management software (e.g., Cinder in OpenStack) +/// should be able to snapshot disks while VMs are running, we need special +/// control over the lock granularity. Therefore, it is a valid use case to lock +/// the whole byte range of a disk image without technically locking the whole +/// file - to get the best of both worlds. +/// +/// [0] https://kb.netapp.com/on-prem/ontap/da/NAS/NAS-KBs/How_is_Mandatory_Locking_supported_for_NFSv4_on_ONTAP_9 +#[derive(Clone, Copy, Debug)] +pub enum LockGranularity { + WholeFile, + ByteRange(u64 /* from, inclusive */, u64 /* len */), +} + +impl LockGranularity { + const fn l_start(self) -> u64 { + match self { + LockGranularity::WholeFile => 0, + LockGranularity::ByteRange(start, _) => start, + } + } + + const fn l_len(self) -> u64 { + match self { + LockGranularity::WholeFile => 0, /* EOF */ + LockGranularity::ByteRange(_, len) => len, + } + } +} + /// Returns a [`struct@libc::flock`] structure for the whole file. -const fn get_flock(lock_type: LockType) -> libc::flock { +const fn get_flock(lock_type: LockType, granularity: LockGranularity) -> libc::flock { libc::flock { l_type: lock_type.to_libc_val() as libc::c_short, l_whence: libc::SEEK_SET as libc::c_short, - l_start: 0, - l_len: 0, /* EOF */ + l_start: granularity.l_start() as libc::c_long, + l_len: granularity.l_len() as libc::c_long, l_pid: 0, /* filled by callee */ } } @@ -122,8 +161,13 @@ const fn get_flock(lock_type: LockType) -> libc::flock { /// - `file`: The file to acquire a lock for [`LockType`]. The file's state will /// be logically mutated, but not technically. /// - `lock_type`: The [`LockType`] -pub fn try_acquire_lock(file: Fd, lock_type: LockType) -> Result<(), LockError> { - let flock = get_flock(lock_type); +/// - `granularity`: The [`LockGranularity`]. +pub fn try_acquire_lock( + file: Fd, + lock_type: LockType, + granularity: LockGranularity, +) -> Result<(), LockError> { + let flock = get_flock(lock_type, granularity); let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_SETLK(&flock)); match res { @@ -146,8 +190,9 @@ pub fn try_acquire_lock(file: Fd, lock_type: LockType) -> Result<() /// /// # Parameters /// - `file`: The file to clear all locks for [`LockType`]. -pub fn clear_lock(file: Fd) -> Result<(), LockError> { - try_acquire_lock(file, LockType::Unlock) +/// - `granularity`: The [`LockGranularity`]. +pub fn clear_lock(file: Fd, granularity: LockGranularity) -> Result<(), LockError> { + try_acquire_lock(file, LockType::Unlock, granularity) } /// Returns the current lock state using [`fcntl`] with respect to the given @@ -155,8 +200,12 @@ pub fn clear_lock(file: Fd) -> Result<(), LockError> { /// /// # Parameters /// - `file`: The file for which to get the lock state. -pub fn get_lock_state(file: Fd) -> Result { - let mut flock = get_flock(LockType::Write); +/// - `granularity`: The [`LockGranularity`]. +pub fn get_lock_state( + file: Fd, + granularity: LockGranularity, +) -> Result { + let mut flock = get_flock(LockType::Write, granularity); let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_GETLK(&mut flock)); match res { 0 => { diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index cf1b1c65e9..73f87f566c 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -19,7 +19,7 @@ use std::{io, result}; use anyhow::anyhow; use block::async_io::{AsyncIo, AsyncIoError, DiskFile}; -use block::fcntl::{LockError, LockType, get_lock_state}; +use block::fcntl::{LockError, LockGranularity, LockType, get_lock_state}; use block::{ ExecuteAsync, ExecuteError, Request, RequestType, VirtioBlockConfig, build_serial, fcntl, }; @@ -767,20 +767,42 @@ impl Block { has_feature(self.features(), VIRTIO_BLK_F_RO.into()) } + /// Returns the granularity for the advisory lock for this disk. + // TODO In future, we could add a `lock_granularity=` configuration to the CLI. + // For now, we stick to QEMU behavior. + fn lock_granularity(&mut self) -> LockGranularity { + let fallback = LockGranularity::WholeFile; + + self.disk_image + .size() + .map(|size| LockGranularity::ByteRange(0, size)) + // use a safe fallback + .unwrap_or_else(|e| { + log::warn!( + "Can't get disk size for id={},path={}, falling back to {:?}: error: {e}", + self.id, + self.disk_path.display(), + fallback + ); + fallback + }) + } + /// Tries to set an advisory lock for the corresponding disk image. pub fn try_lock_image(&mut self) -> Result<()> { let lock_type = match self.read_only() { true => LockType::Read, false => LockType::Write, }; + let granularity = self.lock_granularity(); log::debug!( - "Attempting to acquire {lock_type:?} lock for disk image id={},path={}", + "Attempting to acquire {lock_type:?} lock for disk image: id={},path={},granularity={granularity:?}", self.id, self.disk_path.display() ); let fd = self.disk_image.fd(); - fcntl::try_acquire_lock(fd, lock_type).map_err(|error| { - let current_lock = get_lock_state(fd); + fcntl::try_acquire_lock(fd, lock_type, granularity).map_err(|error| { + let current_lock = get_lock_state(fd, granularity); // Don't propagate the error to the outside, as it is not useful at all. Instead, // we try to log additional help to the user. if let Ok(current_lock) = current_lock { @@ -804,10 +826,12 @@ impl Block { /// Releases the advisory lock held for the corresponding disk image. pub fn unlock_image(&mut self) -> Result<()> { + let granularity = self.lock_granularity(); + // It is very unlikely that this fails; // Should we remove the Result to simplify the error propagation on // higher levels? - fcntl::clear_lock(self.disk_image.fd()).map_err(|error| Error::LockDiskImage { + fcntl::clear_lock(self.disk_image.fd(), granularity).map_err(|error| Error::LockDiskImage { path: self.disk_path.clone(), error, lock_type: LockType::Unlock, From 16fbab30b15e3d72d73ed74411a88a112262ba42 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 21 Nov 2025 14:24:04 +0100 Subject: [PATCH 0323/1893] docs: add gitlint guidance to CONTRIBUTING.md Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04738e0dc0..db1b4bfed4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,6 +26,9 @@ cargo check --all --all-targets --tests cargo clippy --all --all-targets --tests # Please note that this will not execute integration tests. cargo test --all --all-targets --tests + +# To lint your last three commits +gitlint --commits "HEAD~3..HEAD" ``` ### \[Optional\] Run Integration Tests From e993310c96f5aa92e64e3e67aceaea6a7156d24f Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 21 Nov 2025 09:13:38 +0000 Subject: [PATCH 0324/1893] hypervisor: kvm: Save KVM HyperV SynIC emulation state As well as saving the MSRs as it is currently does ensure that the KVM capability is enabled along with keeping the internal state updated. Signed-off-by: Rob Bradford Co-authored-by: Chengyu Fu --- hypervisor/src/kvm/mod.rs | 8 +++++++- hypervisor/src/kvm/x86_64/mod.rs | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index e005834b96..6a1b31607d 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -2342,7 +2342,8 @@ impl cpu::Vcpu for KvmVcpu { // Save extra MSRs if the Hyper-V synthetic interrupt controller is // emulated. - if self.hyperv_synic.load(Ordering::Acquire) { + let hyperv_synic = self.hyperv_synic.load(Ordering::Acquire); + if hyperv_synic { let hyperv_synic_msrs = vec![ 0x40000020, 0x40000021, 0x40000080, 0x40000081, 0x40000082, 0x40000083, 0x40000084, 0x40000090, 0x40000091, 0x40000092, 0x40000093, 0x40000094, 0x40000095, 0x40000096, @@ -2407,6 +2408,7 @@ impl cpu::Vcpu for KvmVcpu { mp_state, tsc_khz, nested_state, + hyperv_synic, } .into()) } @@ -2574,6 +2576,10 @@ impl cpu::Vcpu for KvmVcpu { self.set_tsc_khz(freq)?; } + if state.hyperv_synic { + self.enable_hyperv_synic()?; + } + // Try to set all MSRs previously stored. // If the number of MSRs set from SET_MSRS is different from the // expected amount, we fallback onto a slower method by setting MSRs diff --git a/hypervisor/src/kvm/x86_64/mod.rs b/hypervisor/src/kvm/x86_64/mod.rs index c1bda9d9be..39db4a9940 100644 --- a/hypervisor/src/kvm/x86_64/mod.rs +++ b/hypervisor/src/kvm/x86_64/mod.rs @@ -79,6 +79,8 @@ pub struct VcpuKvmState { // Option to prevent useless 8K (de)serialization when no nested // state exists. pub nested_state: Option, + #[serde(default)] + pub hyperv_synic: bool, } impl From for kvm_segment { From 4d79709b5ed2f50832e79040b4b8a8b0019d0f9e Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 21 Nov 2025 11:30:33 +0000 Subject: [PATCH 0325/1893] tests: Re-enable test_windows_guest_snapshot_restore Only for x86-64 for now as it's still failing on ARM64. See: #4327 Signed-off-by: Rob Bradford --- tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration.rs b/tests/integration.rs index 4d602bcdb7..3c00200ec8 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -9043,7 +9043,7 @@ mod windows { #[test] #[cfg(not(feature = "mshv"))] - #[ignore = "See #4327"] + #[cfg_attr(target_arch = "aarch64", ignore = "See #4327")] fn test_windows_guest_snapshot_restore() { let windows_guest = WindowsGuest::new(); From aa6725004906a2f165d5cfbe3228df135c4cb9ef Mon Sep 17 00:00:00 2001 From: Eugene Korenevsky Date: Tue, 4 Nov 2025 03:34:56 +0300 Subject: [PATCH 0326/1893] block: qcow: support compressed clusters (zlib, zstd) Add support of reading and writing compressed clusters. Support zlib and zstd compressions. L2 cache: store entire L2 entries, not only standard cluster addresses. Read path. Offsets of compressed clusters cannot be determined, therefore replace QcowFile.file_offset_read() with QcowFile.file_read(). This method reads the cluster, decompresses it if necessary and returns the data to the caller. Write path. QcowFile.file_offset_write(): since writing to compressed clusters is not generally possible, allocate a new standard (non-compressed) cluster if compressed L2 entry is encountered; then decompress compressed cluster into new cluster; then return offset inside new cluster to the caller. Processing of standard clusters is not changed. Signed-off-by: Eugene Korenevsky --- Cargo.lock | 59 ++++++++++ block/Cargo.toml | 2 + block/src/qcow/decoder.rs | 87 ++++++++++++++ block/src/qcow/mod.rs | 235 ++++++++++++++++++++++++++------------ 4 files changed, 311 insertions(+), 72 deletions(-) create mode 100644 block/src/qcow/decoder.rs diff --git a/Cargo.lock b/Cargo.lock index feb65da670..6c85f55023 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,7 @@ version = "0.1.0" dependencies = [ "byteorder", "crc-any", + "flate2", "io-uring", "libc", "log", @@ -334,6 +335,7 @@ dependencies = [ "vm-memory", "vm-virtio", "vmm-sys-util", + "zstd", ] [[package]] @@ -368,6 +370,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -728,6 +732,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.1" @@ -1076,6 +1090,16 @@ dependencies = [ "syn", ] +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -1235,6 +1259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1965,6 +1990,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.11" @@ -2809,6 +2840,34 @@ dependencies = [ "syn", ] +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zvariant" version = "5.7.0" diff --git a/block/Cargo.toml b/block/Cargo.toml index 9823c1f818..e6ca61bd47 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -11,6 +11,7 @@ io_uring = ["dep:io-uring"] [dependencies] byteorder = { workspace = true } crc-any = "2.5.0" +flate2 = "1.0" io-uring = { version = "0.7.10", optional = true } libc = { workspace = true } log = { workspace = true } @@ -28,6 +29,7 @@ vm-memory = { workspace = true, features = [ ] } vm-virtio = { path = "../vm-virtio" } vmm-sys-util = { workspace = true } +zstd = "0.13" [lints] workspace = true diff --git a/block/src/qcow/decoder.rs b/block/src/qcow/decoder.rs new file mode 100644 index 0000000000..f1237aae7d --- /dev/null +++ b/block/src/qcow/decoder.rs @@ -0,0 +1,87 @@ +// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Zlib decompress error")] + ZlibDecompress(#[source] flate2::DecompressError), + #[error("Zlib unexpected status: {0:?}")] + ZlibUnexpectedStatus(flate2::Status), + #[error("Zstd decompress error")] + ZstdDecompress(#[source] std::io::Error), + #[error("Zstd: failed to fill buffer")] + ZstdFillBuffer(#[source] std::io::Error), +} + +pub type Result = std::result::Result; + +/// Generic trait for decoding zlib/zstd formats +pub trait Decoder { + fn decode(&self, input: &[u8], output: &mut [u8]) -> Result; +} + +#[derive(Default)] +pub struct ZlibDecoder {} + +impl Decoder for ZlibDecoder { + fn decode(&self, input: &[u8], output: &mut [u8]) -> Result { + use flate2::{Decompress, FlushDecompress, Status}; + + let mut decompressor = Decompress::new(false); + let status = decompressor + .decompress(input, output, FlushDecompress::Finish) + .map_err(Error::ZlibDecompress)?; + if status == Status::StreamEnd { + Ok(decompressor.total_out() as usize) + } else { + Err(Error::ZlibUnexpectedStatus(status)) + } + } +} + +#[derive(Default)] +pub struct ZstdDecoder {} + +impl Decoder for ZstdDecoder { + fn decode(&self, input: &[u8], output: &mut [u8]) -> Result { + use std::io::Read; + + let mut decoder = zstd::stream::read::Decoder::new(input).map_err(Error::ZstdDecompress)?; + let decoded_size = decoder.read(output).map_err(Error::ZstdFillBuffer)?; + Ok(decoded_size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_zlib_decode() { + let d = ZlibDecoder::default(); + let valid_input = vec![99, 96, 100, 98, 6, 0]; + let mut output1 = vec![0; 4]; + d.decode(&valid_input, &mut output1).unwrap(); + assert_eq!(&output1, b"\x00\x01\x02\x03"); + + let invalid_input = vec![1, 2, 3, 4]; + let mut output2 = vec![0; 1024]; + d.decode(&invalid_input, &mut output2).unwrap_err(); + } + + #[test] + fn test_zstd_decode() { + let d = ZstdDecoder::default(); + let valid_input = vec![40, 181, 47, 253, 32, 2, 17, 0, 0, 1, 254]; + let mut output1 = vec![0; 2]; + d.decode(&valid_input, &mut output1).unwrap(); + assert_eq!(&output1, b"\x01\xfe"); + + let invalid_input = vec![1, 2, 3, 4]; + let mut output2 = vec![0; 1024]; + d.decode(&invalid_input, &mut output2).unwrap_err(); + } +} diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index e4f9790c95..0e0c972ab0 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -4,6 +4,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +mod decoder; mod qcow_raw_file; mod raw_file; mod refcount; @@ -17,7 +18,7 @@ use std::os::fd::{AsRawFd, RawFd}; use std::str; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use libc::{EINVAL, ENOSPC, ENOTSUP}; +use libc::{EINVAL, EIO, ENOSPC}; use remain::sorted; use thiserror::Error; use vmm_sys_util::file_traits::{FileSetLen, FileSync}; @@ -25,6 +26,7 @@ use vmm_sys_util::seek_hole::SeekHole; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::BlockBackend; +use crate::qcow::decoder::{Decoder, ZlibDecoder, ZstdDecoder}; use crate::qcow::qcow_raw_file::QcowRawFile; pub use crate::qcow::raw_file::RawFile; use crate::qcow::refcount::RefCount; @@ -42,8 +44,6 @@ pub enum Error { BackingFileOpen(#[source] Box), #[error("Backing file name is too long: {0} bytes over")] BackingFileTooLong(usize), - #[error("Compressed blocks not supported")] - CompressedBlocksNotSupported, #[error("Failed to evict cache")] EvictingCache(#[source] io::Error), #[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")] @@ -110,6 +110,8 @@ pub enum Error { TooManyL1Entries(u64), #[error("Ref count table too large: {0}")] TooManyRefcounts(u64), + #[error("Unsupported compression type")] + UnsupportedCompressionType, #[error("Unsupported refcount order")] UnsupportedRefcountOrder, #[error("Unsupported version: {0}")] @@ -127,6 +129,12 @@ pub enum ImageType { Qcow2, } +#[derive(Clone, Debug)] +pub enum CompressionType { + Zlib, + Zstd, +} + // Maximum data size supported. const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB. @@ -153,15 +161,50 @@ const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; // Flags const COMPRESSED_FLAG: u64 = 1 << 62; +const COMPRESSED_SECTOR_SIZE: u64 = 512; const CLUSTER_USED_FLAG: u64 = 1 << 63; 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 +const COMPRESSION_TYPE_ZSTD: u64 = 1; // zstd + // 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; +fn l2_entry_is_empty(l2_entry: u64) -> bool { + l2_entry == 0 +} + +fn l2_entry_is_compressed(l2_entry: u64) -> bool { + l2_entry & COMPRESSED_FLAG != 0 +} + +// Get file offset and size of compressed cluster data +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 +fn l2_entry_std_cluster_addr(l2_entry: u64) -> u64 { + l2_entry & L2_TABLE_OFFSET_MASK +} + +// Make L2 entry for standard (non-compressed) cluster +fn l2_entry_make_std(cluster_addr: u64) -> u64 { + (cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG +} + /// Contains the information from the header of a qcow file. #[derive(Clone, Debug)] pub struct QcowHeader { @@ -190,6 +233,7 @@ pub struct QcowHeader { pub autoclear_features: u64, pub refcount_order: u32, pub header_size: u32, + pub compression_type: CompressionType, // Post-header entries pub backing_file_path: Option, @@ -255,8 +299,19 @@ impl QcowHeader { } else { read_u32_from_file(f)? }, + compression_type: CompressionType::Zlib, backing_file_path: None, }; + if version == 3 && header.header_size > V3_BARE_HEADER_SIZE { + let raw_compression_type = read_u64_from_file(f)? >> (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)); } @@ -274,6 +329,13 @@ impl QcowHeader { Ok(header) } + pub fn get_decoder(&self) -> Box { + 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, @@ -337,6 +399,7 @@ impl QcowHeader { autoclear_features: 0, refcount_order: DEFAULT_REFCOUNT_ORDER, header_size, + compression_type: CompressionType::Zlib, backing_file_path: backing_file.map(String::from), }) } @@ -588,17 +651,6 @@ impl QcowFile { let l2_entries = cluster_size / size_of::() as u64; - // Check for compressed blocks - for l2_addr_disk in l1_table.get_values() { - if *l2_addr_disk != 0 - && let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk) - && let Some(os_error) = e.raw_os_error() - && os_error == ENOTSUP - { - return Err(Error::CompressedBlocksNotSupported); - } - } - let mut qcow = QcowFile { raw_file, header, @@ -714,11 +766,7 @@ impl QcowFile { let raw_file = &mut self.raw_file; self.l2_cache .insert(l1_index, table, |index, evicted| { - raw_file.write_pointer_table( - l1_table[index], - evicted.get_values(), - CLUSTER_USED_FLAG, - ) + raw_file.write_pointer_table(l1_table[index], evicted.get_values(), 0) }) .map_err(Error::EvictingCache)?; } @@ -1050,11 +1098,40 @@ impl QcowFile { (address / self.raw_file.cluster_size()) % self.l2_entries } - // Gets the offset of the given guest address in the host file. If L1, L2, or data clusters have - // yet to be allocated, return None. - fn file_offset_read(&mut self, address: u64) -> std::io::Result> { + // Decompress the cluster, return EIO on failure + fn decompress_l2_cluster(&mut self, l2_entry: u64) -> std::io::Result> { + let (compressed_cluster_addr, compressed_cluster_size) = + l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); + // Read compressed cluster from raw file + self.raw_file + .file_mut() + .seek(SeekFrom::Start(compressed_cluster_addr))?; + let mut compressed_cluster = vec![0; compressed_cluster_size]; + self.raw_file + .file_mut() + .read_exact(&mut compressed_cluster)?; + let decoder = self.header.get_decoder(); + // Decompress + let cluster_size = self.raw_file.cluster_size() as usize; + let mut decompressed_cluster = vec![0; cluster_size]; + let decompressed_size = decoder + .decode(&compressed_cluster, &mut decompressed_cluster) + .map_err(|_| std::io::Error::from_raw_os_error(EIO))?; + if decompressed_size as u64 != self.raw_file.cluster_size() { + return Err(std::io::Error::from_raw_os_error(EIO)); + } + Ok(decompressed_cluster) + } + + fn file_read( + &mut self, + address: u64, + count: usize, + buf: &mut [u8], + ) -> std::io::Result> { + let err_inval = std::io::Error::from_raw_os_error(EINVAL); if address >= self.virtual_size() { - return Err(std::io::Error::from_raw_os_error(EINVAL)); + return Err(err_inval); } let l1_index = self.l1_table_index(address) as usize; @@ -1072,11 +1149,28 @@ impl QcowFile { self.cache_l2_cluster(l1_index, l2_addr_disk, false)?; - let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index]; - if cluster_addr == 0 { + let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; + if l2_entry_is_empty(l2_entry) { + // Reading from an unallocated cluster will return zeros. return Ok(None); + } else if l2_entry_is_compressed(l2_entry) { + // Compressed cluster. + // Read it, decompress, then return slice from decompressed data. + let mut decompressed_cluster = self.decompress_l2_cluster(l2_entry)?; + decompressed_cluster.resize(self.raw_file.cluster_size() as usize, 0); + let start = self.raw_file.cluster_offset(address) as usize; + let end = start.checked_add(count); + if end.is_none() || end.unwrap() > decompressed_cluster.len() { + return Err(err_inval); + } + buf[..count].copy_from_slice(&decompressed_cluster[start..end.unwrap()]); + } else { + let start = l2_entry_std_cluster_addr(l2_entry) + self.raw_file.cluster_offset(address); + let raw_file = self.raw_file.file_mut(); + raw_file.seek(SeekFrom::Start(start))?; + raw_file.read_exact(buf)?; } - Ok(Some(cluster_addr + self.raw_file.cluster_offset(address))) + Ok(Some(())) } // Gets the offset of the given guest address in the host file. If L1, L2, or data clusters need @@ -1100,24 +1194,39 @@ impl QcowFile { set_refcounts.push((new_addr, 1)); } - let cluster_addr = match self.l2_cache.get(l1_index).unwrap()[l2_index] { - 0 => { - let initial_data = if let Some(backing) = self.backing_file.as_mut() { - let cluster_size = self.raw_file.cluster_size(); - let cluster_begin = address - (address % cluster_size); - let mut cluster_data = vec![0u8; cluster_size as usize]; - backing.seek(SeekFrom::Start(cluster_begin))?; - backing.read_exact(&mut cluster_data)?; - Some(cluster_data) - } else { - None - }; - // Need to allocate a data cluster - let cluster_addr = self.append_data_cluster(initial_data)?; - self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?; - cluster_addr + let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; + let cluster_addr = if l2_entry_is_compressed(l2_entry) { + // Writing to compressed cluster. + // Allocate new cluster, decompress into new cluster, then use + // offset of new cluster. + let decompressed_cluster = self.decompress_l2_cluster(l2_entry)?; + let cluster_addr = self.append_data_cluster(None)?; + self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?; + self.raw_file + .file_mut() + .seek(SeekFrom::Start(cluster_addr))?; + let nwritten = self.raw_file.file_mut().write(&decompressed_cluster)?; + if nwritten != decompressed_cluster.len() { + return Err(std::io::Error::from_raw_os_error(EIO)); } - a => a, + cluster_addr + } else if l2_entry_is_empty(l2_entry) { + let initial_data = if let Some(backing) = self.backing_file.as_mut() { + let cluster_size = self.raw_file.cluster_size(); + let cluster_begin = address - (address % cluster_size); + let mut cluster_data = vec![0u8; cluster_size as usize]; + backing.seek(SeekFrom::Start(cluster_begin))?; + backing.read_exact(&mut cluster_data)?; + Some(cluster_data) + } else { + None + }; + // Need to allocate a data cluster + let cluster_addr = self.append_data_cluster(initial_data)?; + self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?; + cluster_addr + } else { + l2_entry_std_cluster_addr(l2_entry) }; for (addr, count) in set_refcounts { @@ -1157,7 +1266,7 @@ impl QcowFile { self.l1_table[l1_index] = new_addr; } // 'unwrap' is OK because it was just added. - self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = cluster_addr; + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = l2_entry_make_std(cluster_addr); Ok(()) } @@ -1334,10 +1443,9 @@ impl QcowFile { // Partial cluster - zero out the relevant bytes if it was allocated. // Any space in unallocated clusters can be left alone, since // unallocated clusters already read back as zeroes. - if let Some(offset) = self.file_offset_read(curr_addr)? { - // Partial cluster - zero it out. - self.raw_file.file_mut().write_zeroes_at(offset, count)?; - } + let offset = self.file_offset_write(curr_addr)?; + // Partial cluster - zero it out. + self.raw_file.file_mut().write_zeroes_at(offset, count)?; } nwritten += count; @@ -1348,14 +1456,8 @@ impl QcowFile { // Reads an L2 cluster from the disk, returning an error if the file can't be read or if any // cluster is compressed. fn read_l2_cluster(raw_file: &mut QcowRawFile, cluster_addr: u64) -> std::io::Result> { - let file_values = raw_file.read_pointer_cluster(cluster_addr, None)?; - if file_values.iter().any(|entry| entry & COMPRESSED_FLAG != 0) { - return Err(std::io::Error::from_raw_os_error(ENOTSUP)); - } - Ok(file_values - .iter() - .map(|entry| *entry & L2_TABLE_OFFSET_MASK) - .collect()) + let l2_table = raw_file.read_pointer_cluster(cluster_addr, None)?; + Ok(l2_table) } // Put an L2 cluster to the cache with evicting less-used cluster @@ -1383,11 +1485,7 @@ impl QcowFile { let l1_table = &self.l1_table; let raw_file = &mut self.raw_file; self.l2_cache.insert(l1_index, l2_table, |index, evicted| { - raw_file.write_pointer_table( - l1_table[index], - evicted.get_values(), - CLUSTER_USED_FLAG, - ) + raw_file.write_pointer_table(l1_table[index], evicted.get_values(), 0) })?; } Ok(new_cluster) @@ -1456,11 +1554,8 @@ impl QcowFile { // The index must be valid from when we inserted it. let addr = self.l1_table[*l1_index]; if addr != 0 { - self.raw_file.write_pointer_table( - addr, - l2_table.get_values(), - CLUSTER_USED_FLAG, - )?; + self.raw_file + .write_pointer_table(addr, l2_table.get_values(), 0)?; } else { return Err(std::io::Error::from_raw_os_error(EINVAL)); } @@ -1512,14 +1607,10 @@ impl Read for QcowFile { let mut nread: usize = 0; while nread < read_count { let curr_addr = address + nread as u64; - let file_offset = self.file_offset_read(curr_addr)?; let count = self.limit_range_cluster(curr_addr, read_count - nread); - if let Some(offset) = file_offset { - self.raw_file.file_mut().seek(SeekFrom::Start(offset))?; - self.raw_file - .file_mut() - .read_exact(&mut buf[nread..(nread + count)])?; + if (self.file_read(curr_addr, count, &mut buf[nread..(nread + count)])?).is_some() { + // Data is successfully read from the cluster } else if let Some(backing) = self.backing_file.as_mut() { backing.seek(SeekFrom::Start(curr_addr))?; backing.read_exact(&mut buf[nread..(nread + count)])?; From 94ed7c17454b2ade565cdbcc7fffbdd0f0a1f306 Mon Sep 17 00:00:00 2001 From: Eugene Korenevsky Date: Tue, 18 Nov 2025 01:43:17 +0300 Subject: [PATCH 0327/1893] block: qcow: add integration tests for qcow2 compression Add tests: - zlib: test_virtio_block_qcow2_zlib() - zstd: test_virtio_block_qcow2_zstd() Both these tests use zlib- and zstd-compressed images as OS image. Modify test_virtio_block_qcow2_backing_file() test: it is practical to test qcow2 file-backing with compression, so use zlib-compressed image as a backing file. Signed-off-by: Eugene Korenevsky --- scripts/run_integration_tests_aarch64.sh | 20 +++++++++++++++++++- scripts/run_integration_tests_x86_64.sh | 22 +++++++++++++++++++++- tests/integration.rs | 18 ++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index bd1e3954d1..ff3cdc4405 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -46,11 +46,29 @@ update_workloads() { popd || exit fi + FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE_NAME="focal-server-cloudimg-arm64-custom-20210929-0-zlib.qcow2" + FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE_NAME" + if [ ! -f "$FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img convert -c -f raw -O qcow2 -o compression_type=zlib \ + "$FOCAL_OS_RAW_IMAGE" $FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE_NAME + popd || exit + fi + + FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE_NAME="focal-server-cloudimg-arm64-custom-20210929-0-zstd.qcow2" + FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE_NAME" + if [ ! -f "$FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img convert -c -f raw -O qcow2 -o compression_type=zstd \ + "$FOCAL_OS_RAW_IMAGE" $FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE_NAME + popd || exit + fi + FOCAL_OS_QCOW2_IMAGE_BACKING_FILE_NAME="focal-server-cloudimg-arm64-custom-20210929-0-backing.qcow2" FOCAL_OS_QCOW2_BACKING_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW2_IMAGE_BACKING_FILE_NAME" if [ ! -f "$FOCAL_OS_QCOW2_BACKING_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img create -f qcow2 -b "$FOCAL_OS_QCOW2_UNCOMPRESSED_IMAGE" -F qcow2 $FOCAL_OS_QCOW2_IMAGE_BACKING_FILE_NAME + time qemu-img create -f qcow2 -b "$FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE" -F qcow2 $FOCAL_OS_QCOW2_IMAGE_BACKING_FILE_NAME popd || exit fi diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 6f89cf865a..e2848a7425 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -45,11 +45,31 @@ if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then popd || exit fi +FOCAL_OS_QCOW_ZLIB_FILE_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0-zlib.qcow2" +FOCAL_OS_QCOW_ZLIB_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW_ZLIB_FILE_IMAGE_NAME" +if [ ! -f "$FOCAL_OS_QCOW_ZLIB_FILE_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img convert -c -f raw -O qcow2 -o compression_type=zlib \ + "$FOCAL_OS_RAW_IMAGE" $FOCAL_OS_QCOW_ZLIB_FILE_IMAGE_NAME + popd || exit +fi + +FOCAL_OS_QCOW_ZSTD_FILE_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0-zstd.qcow2" +FOCAL_OS_QCOW_ZSTD_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW_ZSTD_FILE_IMAGE_NAME" +if [ ! -f "$FOCAL_OS_QCOW_ZSTD_FILE_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img convert -c -f raw -O qcow2 -o compression_type=zstd \ + "$FOCAL_OS_RAW_IMAGE" $FOCAL_OS_QCOW_ZSTD_FILE_IMAGE_NAME + popd || exit +fi + FOCAL_OS_QCOW_BACKING_FILE_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0-backing.qcow2" FOCAL_OS_QCOW_BACKING_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW_BACKING_FILE_IMAGE_NAME" if [ ! -f "$FOCAL_OS_QCOW_BACKING_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img create -f qcow2 -b "$FOCAL_OS_IMAGE" -F qcow2 $FOCAL_OS_QCOW_BACKING_FILE_IMAGE_NAME + time qemu-img create -f qcow2 \ + -b "$FOCAL_OS_QCOW_ZSTD_FILE_IMAGE" \ + -F qcow2 $FOCAL_OS_QCOW_BACKING_FILE_IMAGE_NAME popd || exit fi diff --git a/tests/integration.rs b/tests/integration.rs index 3c00200ec8..276c4e00fb 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -40,6 +40,10 @@ mod x86_64 { pub const JAMMY_VFIO_IMAGE_NAME: &str = "jammy-server-cloudimg-amd64-custom-vfio-20241012-0.raw"; pub const FOCAL_IMAGE_NAME_QCOW2: &str = "focal-server-cloudimg-amd64-custom-20210609-0.qcow2"; + pub const FOCAL_IMAGE_NAME_QCOW2_ZLIB: &str = + "focal-server-cloudimg-amd64-custom-20210609-0-zlib.qcow2"; + pub const FOCAL_IMAGE_NAME_QCOW2_ZSTD: &str = + "focal-server-cloudimg-amd64-custom-20210609-0-zstd.qcow2"; pub const FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE: &str = "focal-server-cloudimg-amd64-custom-20210609-0-backing.qcow2"; pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-amd64-custom-20210609-0.vhd"; @@ -59,6 +63,10 @@ mod aarch64 { pub const FOCAL_IMAGE_UPDATE_KERNEL_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-0-update-kernel.raw"; pub const FOCAL_IMAGE_NAME_QCOW2: &str = "focal-server-cloudimg-arm64-custom-20210929-0.qcow2"; + pub const FOCAL_IMAGE_NAME_QCOW2_ZLIB: &str = + "focal-server-cloudimg-arm64-custom-20210929-0-zlib.qcow2"; + pub const FOCAL_IMAGE_NAME_QCOW2_ZSTD: &str = + "focal-server-cloudimg-arm64-custom-20210929-0-zstd.qcow2"; pub const FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE: &str = "focal-server-cloudimg-arm64-custom-20210929-0-backing.qcow2"; pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-arm64-custom-20210929-0.vhd"; @@ -3488,6 +3496,16 @@ mod common_parallel { _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2, false, false); } + #[test] + fn test_virtio_block_qcow2_zlib() { + _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2_ZLIB, false, false); + } + + #[test] + fn test_virtio_block_qcow2_zstd() { + _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2_ZSTD, false, false); + } + #[test] fn test_virtio_block_qcow2_backing_file() { _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE, false, false); From e6d31a3d81ad459aecd3501afcf4b0cf28d3cc50 Mon Sep 17 00:00:00 2001 From: Eugene Korenevsky Date: Sun, 23 Nov 2025 17:07:33 +0300 Subject: [PATCH 0328/1893] block: qcow: switch qcow2 tests from `focal` to `jammy` qcow2 images Signed-off-by: Eugene Korenevsky --- scripts/run_integration_tests_aarch64.sh | 48 +++++++++++------------ scripts/run_integration_tests_x86_64.sh | 50 ++++++++++++------------ tests/integration.rs | 36 ++++++++--------- 3 files changed, 67 insertions(+), 67 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index ff3cdc4405..9887013cb7 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -46,47 +46,47 @@ update_workloads() { popd || exit fi - FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE_NAME="focal-server-cloudimg-arm64-custom-20210929-0-zlib.qcow2" - FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE_NAME" - if [ ! -f "$FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE" ]; then + JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0.raw" + JAMMY_OS_RAW_IMAGE_DOWNLOAD_URL="https://ch-images.azureedge.net/$JAMMY_OS_RAW_IMAGE_NAME" + JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME" + if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -c -f raw -O qcow2 -o compression_type=zlib \ - "$FOCAL_OS_RAW_IMAGE" $FOCAL_OS_QCOW2_ZLIB_FILE_IMAGE_NAME + time wget --quiet $JAMMY_OS_RAW_IMAGE_DOWNLOAD_URL || exit 1 popd || exit fi - FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE_NAME="focal-server-cloudimg-arm64-custom-20210929-0-zstd.qcow2" - FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE_NAME" - if [ ! -f "$FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE" ]; then + JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_NAME="jammy-server-cloudimg-arm64-custom-20220329-0.qcow2" + JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_DOWNLOAD_URL="https://ch-images.azureedge.net/$JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_NAME" + JAMMY_OS_QCOW2_UNCOMPRESSED_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_NAME" + if [ ! -f "$JAMMY_OS_QCOW2_UNCOMPRESSED_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -c -f raw -O qcow2 -o compression_type=zstd \ - "$FOCAL_OS_RAW_IMAGE" $FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE_NAME + time wget --quiet $JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_DOWNLOAD_URL || exit 1 popd || exit fi - FOCAL_OS_QCOW2_IMAGE_BACKING_FILE_NAME="focal-server-cloudimg-arm64-custom-20210929-0-backing.qcow2" - FOCAL_OS_QCOW2_BACKING_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW2_IMAGE_BACKING_FILE_NAME" - if [ ! -f "$FOCAL_OS_QCOW2_BACKING_FILE_IMAGE" ]; then + JAMMY_OS_QCOW2_ZLIB_FILE_IMAGE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0-zlib.qcow2" + JAMMY_OS_QCOW2_ZLIB_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW2_ZLIB_FILE_IMAGE_NAME" + if [ ! -f "$JAMMY_OS_QCOW2_ZLIB_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img create -f qcow2 -b "$FOCAL_OS_QCOW2_ZSTD_FILE_IMAGE" -F qcow2 $FOCAL_OS_QCOW2_IMAGE_BACKING_FILE_NAME + time qemu-img convert -c -f raw -O qcow2 -o compression_type=zlib \ + "$JAMMY_OS_RAW_IMAGE" $JAMMY_OS_QCOW2_ZLIB_FILE_IMAGE_NAME popd || exit fi - JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0.raw" - JAMMY_OS_RAW_IMAGE_DOWNLOAD_URL="https://ch-images.azureedge.net/$JAMMY_OS_RAW_IMAGE_NAME" - JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME" - if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then + JAMMY_OS_QCOW2_ZSTD_FILE_IMAGE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0-zstd.qcow2" + JAMMY_OS_QCOW2_ZSTD_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW2_ZSTD_FILE_IMAGE_NAME" + if [ ! -f "$JAMMY_OS_QCOW2_ZSTD_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time wget --quiet $JAMMY_OS_RAW_IMAGE_DOWNLOAD_URL || exit 1 + time qemu-img convert -c -f raw -O qcow2 -o compression_type=zstd \ + "$JAMMY_OS_RAW_IMAGE" $JAMMY_OS_QCOW2_ZSTD_FILE_IMAGE_NAME popd || exit fi - JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_NAME="jammy-server-cloudimg-arm64-custom-20220329-0.qcow2" - JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_DOWNLOAD_URL="https://ch-images.azureedge.net/$JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_NAME" - JAMMY_OS_QCOW2_UNCOMPRESSED_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_NAME" - if [ ! -f "$JAMMY_OS_QCOW2_UNCOMPRESSED_IMAGE" ]; then + JAMMY_OS_QCOW2_IMAGE_BACKING_FILE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0-backing.qcow2" + JAMMY_OS_QCOW2_BACKING_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW2_IMAGE_BACKING_FILE_NAME" + if [ ! -f "$JAMMY_OS_QCOW2_BACKING_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time wget --quiet $JAMMY_OS_QCOW2_IMAGE_UNCOMPRESSED_DOWNLOAD_URL || exit 1 + time qemu-img create -f qcow2 -b "$JAMMY_OS_QCOW2_ZSTD_FILE_IMAGE" -F qcow2 $JAMMY_OS_QCOW2_IMAGE_BACKING_FILE_NAME popd || exit fi diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index e2848a7425..4370c15ace 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -45,48 +45,48 @@ if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then popd || exit fi -FOCAL_OS_QCOW_ZLIB_FILE_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0-zlib.qcow2" -FOCAL_OS_QCOW_ZLIB_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW_ZLIB_FILE_IMAGE_NAME" -if [ ! -f "$FOCAL_OS_QCOW_ZLIB_FILE_IMAGE" ]; then +JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2" +JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME" +JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -c -f raw -O qcow2 -o compression_type=zlib \ - "$FOCAL_OS_RAW_IMAGE" $FOCAL_OS_QCOW_ZLIB_FILE_IMAGE_NAME + time wget --quiet $JAMMY_OS_IMAGE_URL || exit 1 popd || exit fi -FOCAL_OS_QCOW_ZSTD_FILE_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0-zstd.qcow2" -FOCAL_OS_QCOW_ZSTD_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW_ZSTD_FILE_IMAGE_NAME" -if [ ! -f "$FOCAL_OS_QCOW_ZSTD_FILE_IMAGE" ]; then +JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.raw" +JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -c -f raw -O qcow2 -o compression_type=zstd \ - "$FOCAL_OS_RAW_IMAGE" $FOCAL_OS_QCOW_ZSTD_FILE_IMAGE_NAME + time qemu-img convert -p -f qcow2 -O raw $JAMMY_OS_IMAGE_NAME $JAMMY_OS_RAW_IMAGE_NAME || exit 1 popd || exit fi -FOCAL_OS_QCOW_BACKING_FILE_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0-backing.qcow2" -FOCAL_OS_QCOW_BACKING_FILE_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_QCOW_BACKING_FILE_IMAGE_NAME" -if [ ! -f "$FOCAL_OS_QCOW_BACKING_FILE_IMAGE" ]; then +JAMMY_OS_QCOW_ZLIB_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-zlib.qcow2" +JAMMY_OS_QCOW_ZLIB_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_ZLIB_FILE_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_QCOW_ZLIB_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img create -f qcow2 \ - -b "$FOCAL_OS_QCOW_ZSTD_FILE_IMAGE" \ - -F qcow2 $FOCAL_OS_QCOW_BACKING_FILE_IMAGE_NAME + time qemu-img convert -c -f raw -O qcow2 -o compression_type=zlib \ + "$JAMMY_OS_RAW_IMAGE" $JAMMY_OS_QCOW_ZLIB_FILE_IMAGE_NAME popd || exit fi -JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2" -JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME" -JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME" -if [ ! -f "$JAMMY_OS_IMAGE" ]; then +JAMMY_OS_QCOW_ZSTD_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-zstd.qcow2" +JAMMY_OS_QCOW_ZSTD_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_ZSTD_FILE_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_QCOW_ZSTD_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time wget --quiet $JAMMY_OS_IMAGE_URL || exit 1 + time qemu-img convert -c -f raw -O qcow2 -o compression_type=zstd \ + "$JAMMY_OS_RAW_IMAGE" $JAMMY_OS_QCOW_ZSTD_FILE_IMAGE_NAME popd || exit fi -JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.raw" -JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME" -if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then +JAMMY_OS_QCOW_BACKING_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-backing.qcow2" +JAMMY_OS_QCOW_BACKING_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_FILE_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_QCOW_BACKING_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -p -f qcow2 -O raw $JAMMY_OS_IMAGE_NAME $JAMMY_OS_RAW_IMAGE_NAME || exit 1 + time qemu-img create -f qcow2 \ + -b "$JAMMY_OS_QCOW_ZSTD_FILE_IMAGE" \ + -F qcow2 $JAMMY_OS_QCOW_BACKING_FILE_IMAGE_NAME popd || exit fi diff --git a/tests/integration.rs b/tests/integration.rs index 276c4e00fb..4ef1029b71 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -39,16 +39,16 @@ mod x86_64 { pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw"; pub const JAMMY_VFIO_IMAGE_NAME: &str = "jammy-server-cloudimg-amd64-custom-vfio-20241012-0.raw"; - pub const FOCAL_IMAGE_NAME_QCOW2: &str = "focal-server-cloudimg-amd64-custom-20210609-0.qcow2"; - pub const FOCAL_IMAGE_NAME_QCOW2_ZLIB: &str = - "focal-server-cloudimg-amd64-custom-20210609-0-zlib.qcow2"; - pub const FOCAL_IMAGE_NAME_QCOW2_ZSTD: &str = - "focal-server-cloudimg-amd64-custom-20210609-0-zstd.qcow2"; - pub const FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE: &str = - "focal-server-cloudimg-amd64-custom-20210609-0-backing.qcow2"; pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-amd64-custom-20210609-0.vhd"; pub const FOCAL_IMAGE_NAME_VHDX: &str = "focal-server-cloudimg-amd64-custom-20210609-0.vhdx"; pub const JAMMY_IMAGE_NAME: &str = "jammy-server-cloudimg-amd64-custom-20241017-0.raw"; + pub const JAMMY_IMAGE_NAME_QCOW2: &str = "jammy-server-cloudimg-amd64-custom-20241017-0.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_ZLIB: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-zlib.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_ZSTD: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-zstd.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_FILE: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-backing.qcow2"; pub const WINDOWS_IMAGE_NAME: &str = "windows-server-2022-amd64-2.raw"; pub const OVMF_NAME: &str = "CLOUDHV.fd"; pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'IO-APIC.*ttyS0' /proc/interrupts || true"; @@ -62,16 +62,16 @@ mod aarch64 { pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-0.raw"; pub const FOCAL_IMAGE_UPDATE_KERNEL_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-0-update-kernel.raw"; - pub const FOCAL_IMAGE_NAME_QCOW2: &str = "focal-server-cloudimg-arm64-custom-20210929-0.qcow2"; - pub const FOCAL_IMAGE_NAME_QCOW2_ZLIB: &str = - "focal-server-cloudimg-arm64-custom-20210929-0-zlib.qcow2"; - pub const FOCAL_IMAGE_NAME_QCOW2_ZSTD: &str = - "focal-server-cloudimg-arm64-custom-20210929-0-zstd.qcow2"; - pub const FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE: &str = - "focal-server-cloudimg-arm64-custom-20210929-0-backing.qcow2"; pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-arm64-custom-20210929-0.vhd"; pub const FOCAL_IMAGE_NAME_VHDX: &str = "focal-server-cloudimg-arm64-custom-20210929-0.vhdx"; pub const JAMMY_IMAGE_NAME: &str = "jammy-server-cloudimg-arm64-custom-20220329-0.raw"; + pub const JAMMY_IMAGE_NAME_QCOW2: &str = "jammy-server-cloudimg-arm64-custom-20220329-0.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_ZLIB: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-zlib.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_ZSTD: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-zstd.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_FILE: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-backing.qcow2"; pub const WINDOWS_IMAGE_NAME: &str = "windows-11-iot-enterprise-aarch64.raw"; pub const OVMF_NAME: &str = "CLOUDHV_EFI.fd"; pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'GICv3.*uart-pl011' /proc/interrupts || true"; @@ -3493,22 +3493,22 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2() { - _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2, false, false); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2, false, false); } #[test] fn test_virtio_block_qcow2_zlib() { - _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2_ZLIB, false, false); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZLIB, false, false); } #[test] fn test_virtio_block_qcow2_zstd() { - _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2_ZSTD, false, false); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZSTD, false, false); } #[test] fn test_virtio_block_qcow2_backing_file() { - _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE, false, false); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_FILE, false, false); } #[test] From 75cdfb011790bec4b5fe9e01982b99e0b5a1b532 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:19:51 +0200 Subject: [PATCH 0329/1893] misc: arch: drop `extern crate`, use modern rust This commit is the first in a series of similar commits to clean up obsolete `extern crate` statements Since Rust 1.30, normal macros can be imported via `use`, and with Rust 1.31 and edition 2018 this has become the preferred approach. `extern crate` is only needed for `alloc` in `no_std` crates, which does not apply here. By dropping these (often redundant or odd) `extern crate` lines, we expose the actual dependencies more clearly and reduce technical debt. ## Auto-generation of the series Most of this series was produced automatically: 1. Removed all "extern crate" references 2. Run the script [0] to add missing `use` statements 3. Run `cargo +nightly fmt --all` 4. Fix the remaining problems manually The treewide changes were then split into per-folder commits. [0] ```python import os import re # Mapping of macro/function usage to imports MACRO_IMPORTS = { "info!": "use log::info;\n", "debug!": "use log::debug;\n", "error!": "use log::error;\n", "trace!": "use log::trace;\n", "warn!": "use log::warn;\n", "event!": "use event_monitor::event;\n", "anyhow!(": "use anyhow::anyhow;\n", "bitflags!(": "use bitflags::bitflags;\n", "ioctl_ior_nr!": "use vmm_sys_util::{ioctl_ior_nr};\n", "ioctl_iow_nr!": "use vmm_sys_util::{ioctl_iow_nr};\n", } # Regex for finding the first use statement USE_REGEX = re.compile(r"^\s*(use|pub use) .+?;") def process_file(path): with open(path, "r", encoding="utf-8") as f: lines = f.readlines() content = "".join(lines) existing_imports = set(lines) needed_imports = set() # Check macros/functions against mapping, only add if not already present for key, import_stmt in MACRO_IMPORTS.items(): if key in content and import_stmt not in existing_imports: needed_imports.add(import_stmt) if not needed_imports: print(f"Unmodified {path} (no new imports needed)") return # Nothing to do # Find first use or pub use statement for i, line in enumerate(lines): if USE_REGEX.match(line): insertion_index = i + 1 break else: print(f"Unmodified {path} (no use or pub use statement found)") return # No use statement found, skip file # Insert imports lines[insertion_index:insertion_index] = list(needed_imports) # Write back file with open(path, "w", encoding="utf-8") as f: f.writelines(lines) print(f"Modified {path}, added imports: {''.join(needed_imports).strip()}") for root, _, files in os.walk("."): for file in files: if file.endswith(".rs"): process_file(os.path.join(root, file)) ``` Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- arch/src/aarch64/fdt.rs | 1 + arch/src/lib.rs | 3 --- arch/src/riscv64/fdt.rs | 1 + arch/src/x86_64/mod.rs | 15 ++++++++++----- arch/src/x86_64/mptable.rs | 1 + arch/src/x86_64/tdx/mod.rs | 1 + 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index ce3551cc71..ea3f842eb9 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -19,6 +19,7 @@ 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 thiserror::Error; use vm_fdt::{FdtWriter, FdtWriterResult}; use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion}; diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 36fa20f13c..a7c0ffdca9 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -8,9 +8,6 @@ //! Implements platform specific functionality. //! Supported platforms: x86_64, aarch64, riscv64. -#[macro_use] -extern crate log; - use std::collections::BTreeMap; use std::sync::Arc; use std::{fmt, result}; diff --git a/arch/src/riscv64/fdt.rs b/arch/src/riscv64/fdt.rs index b30d2ce197..9ad66d367a 100644 --- a/arch/src/riscv64/fdt.rs +++ b/arch/src/riscv64/fdt.rs @@ -15,6 +15,7 @@ use std::{cmp, result, str}; use byteorder::{BigEndian, ByteOrder}; use hypervisor::arch::riscv64::aia::Vaia; +use log::debug; use thiserror::Error; use vm_fdt::{FdtWriter, FdtWriterResult}; use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion}; diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 51c2d02f07..e2cb1eab28 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -6,11 +6,19 @@ // 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. + pub mod interrupts; pub mod layout; +pub mod regs; + +#[cfg(feature = "tdx")] +pub mod tdx; + mod mpspec; mod mptable; -pub mod regs; +mod smbios; + +use std::arch::x86_64; use std::mem; use hypervisor::arch::x86::{CPUID_FLAG_VALID_INDEX, CpuIdEntry}; @@ -19,6 +27,7 @@ use linux_loader::loader::bootparam::{boot_params, setup_header}; use linux_loader::loader::elf::start_info::{ hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info, }; +use log::{debug, error, info}; use thiserror::Error; use vm_memory::{ Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, @@ -26,10 +35,6 @@ use vm_memory::{ }; use crate::{GuestMemoryMmap, InitramfsConfig, RegionType}; -mod smbios; -use std::arch::x86_64; -#[cfg(feature = "tdx")] -pub mod tdx; // While modern architectures support more than 255 CPUs via x2APIC, // legacy devices such as mptable support at most 254 CPUs. diff --git a/arch/src/x86_64/mptable.rs b/arch/src/x86_64/mptable.rs index a0e6dc47e2..c31c9e4117 100644 --- a/arch/src/x86_64/mptable.rs +++ b/arch/src/x86_64/mptable.rs @@ -8,6 +8,7 @@ use std::{mem, result, slice}; use libc::c_uchar; +use log::{info, warn}; use thiserror::Error; use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError}; diff --git a/arch/src/x86_64/tdx/mod.rs b/arch/src/x86_64/tdx/mod.rs index 4ce1aa9a07..a6ce96aae9 100644 --- a/arch/src/x86_64/tdx/mod.rs +++ b/arch/src/x86_64/tdx/mod.rs @@ -5,6 +5,7 @@ use std::fs::File; use std::io::{Read, Seek, SeekFrom}; use std::str::FromStr; +use log::{debug, info}; use thiserror::Error; use uuid::Uuid; use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError}; From 5f66a26b2e62a174ecfa28b6b3d245d745acfbe1 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:20:14 +0200 Subject: [PATCH 0330/1893] misc: block: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- block/src/lib.rs | 4 +--- block/src/qcow/mod.rs | 1 + block/src/raw_async.rs | 1 + block/src/raw_async_aio.rs | 1 + block/src/raw_sync.rs | 1 + block/src/vhdx/vhdx_header.rs | 2 -- 6 files changed, 5 insertions(+), 5 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index a6ed32bc66..8871cebd01 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -8,9 +8,6 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -#[macro_use] -extern crate log; - pub mod async_io; pub mod fcntl; pub mod fixed_vhd; @@ -45,6 +42,7 @@ use std::{cmp, result}; #[cfg(feature = "io_uring")] use io_uring::{IoUring, Probe, opcode}; use libc::{S_IFBLK, S_IFMT, ioctl}; +use log::{error, info, warn}; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use thiserror::Error; diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 0e0c972ab0..d713d56936 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -19,6 +19,7 @@ use std::str; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use libc::{EINVAL, EIO, ENOSPC}; +use log::error; use remain::sorted; use thiserror::Error; use vmm_sys_util::file_traits::{FileSetLen, FileSync}; diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index cd786033e9..0018b50290 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -7,6 +7,7 @@ use std::io::{Error, Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; use io_uring::{IoUring, opcode, types}; +use log::warn; use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 9a74fa41d7..f2f070b43d 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -9,6 +9,7 @@ use std::fs::File; use std::io::{Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; +use log::warn; use vmm_sys_util::aio; use vmm_sys_util::eventfd::EventFd; diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 6b98147e19..b17018f72b 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -7,6 +7,7 @@ use std::fs::File; use std::io::{Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; +use log::warn; use vmm_sys_util::eventfd::EventFd; use crate::DiskTopology; diff --git a/block/src/vhdx/vhdx_header.rs b/block/src/vhdx/vhdx_header.rs index bed5418d83..73ecbc8f84 100644 --- a/block/src/vhdx/vhdx_header.rs +++ b/block/src/vhdx/vhdx_header.rs @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: Apache-2.0 -extern crate log; - use std::collections::btree_map::BTreeMap; use std::fs::File; use std::io::{self, Read, Seek, SeekFrom, Write}; From e39174ac519dcef1ac644c59bb645015161364dc Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:20:18 +0200 Subject: [PATCH 0331/1893] misc: devices: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- devices/src/acpi.rs | 1 + devices/src/aia.rs | 4 ++-- devices/src/debug_console.rs | 1 + devices/src/gic.rs | 4 ++-- devices/src/ioapic.rs | 1 + devices/src/ivshmem.rs | 1 + devices/src/legacy/cmos.rs | 1 + devices/src/legacy/debug_port.rs | 1 + devices/src/legacy/fw_cfg.rs | 1 + devices/src/legacy/fwdebug.rs | 1 + devices/src/legacy/gpio_pl061.rs | 1 + devices/src/legacy/i8042.rs | 1 + devices/src/legacy/rtc_pl031.rs | 1 + devices/src/legacy/uart_pl011.rs | 1 + devices/src/lib.rs | 9 ++------- devices/src/pvmemcontrol.rs | 1 + devices/src/pvpanic.rs | 2 ++ devices/src/tpm.rs | 1 + 18 files changed, 22 insertions(+), 11 deletions(-) diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs index 9e6790d857..69bcb80d76 100644 --- a/devices/src/acpi.rs +++ b/devices/src/acpi.rs @@ -9,6 +9,7 @@ use std::thread; use std::time::Instant; use acpi_tables::{Aml, AmlSink, aml}; +use log::{error, info, warn}; use vm_device::BusDevice; use vm_device::interrupt::InterruptSourceGroup; use vm_memory::GuestAddress; diff --git a/devices/src/aia.rs b/devices/src/aia.rs index f3956727ac..3e17b982a7 100644 --- a/devices/src/aia.rs +++ b/devices/src/aia.rs @@ -3,8 +3,6 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use super::interrupt_controller::{Error, InterruptController}; -extern crate arch; use std::result; use std::sync::{Arc, Mutex}; @@ -19,6 +17,8 @@ use vm_memory::address::Address; use vm_migration::{Migratable, Pausable, Snapshottable, Transportable}; use vmm_sys_util::eventfd::EventFd; +use super::interrupt_controller::{Error, InterruptController}; + type Result = result::Result; // Reserve 32 IRQs for legacy devices. diff --git a/devices/src/debug_console.rs b/devices/src/debug_console.rs index 8ec63573b7..7945edb000 100644 --- a/devices/src/debug_console.rs +++ b/devices/src/debug_console.rs @@ -9,6 +9,7 @@ use std::io; use std::io::Write; use std::sync::{Arc, Barrier}; +use log::error; use vm_device::BusDevice; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; diff --git a/devices/src/gic.rs b/devices/src/gic.rs index 2c6493d885..fd7199d00b 100644 --- a/devices/src/gic.rs +++ b/devices/src/gic.rs @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use super::interrupt_controller::{Error, InterruptController}; -extern crate arch; use std::result; use std::sync::{Arc, Mutex}; @@ -19,6 +17,8 @@ use vm_memory::address::Address; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vmm_sys_util::eventfd::EventFd; +use super::interrupt_controller::{Error, InterruptController}; + type Result = result::Result; // Reserve 32 IRQs for legacy devices. diff --git a/devices/src/ioapic.rs b/devices/src/ioapic.rs index 3ce827bd18..c43f629d8f 100644 --- a/devices/src/ioapic.rs +++ b/devices/src/ioapic.rs @@ -13,6 +13,7 @@ use std::result; use std::sync::{Arc, Barrier}; use byteorder::{ByteOrder, LittleEndian}; +use log::{debug, error, trace, warn}; use serde::{Deserialize, Serialize}; use vm_device::BusDevice; use vm_device::interrupt::{ diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs index d6f5b5d467..4e1a01bf37 100644 --- a/devices/src/ivshmem.rs +++ b/devices/src/ivshmem.rs @@ -11,6 +11,7 @@ use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; use byteorder::{ByteOrder, LittleEndian}; +use log::{debug, error, warn}; use pci::{ BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, diff --git a/devices/src/legacy/cmos.rs b/devices/src/legacy/cmos.rs index 6076db7a90..7b23d616dc 100644 --- a/devices/src/legacy/cmos.rs +++ b/devices/src/legacy/cmos.rs @@ -13,6 +13,7 @@ use std::{mem, thread}; #[cfg_attr(target_env = "musl", allow(deprecated))] use libc::time_t; use libc::{CLOCK_REALTIME, clock_gettime, gmtime_r, timespec, tm}; +use log::{info, warn}; use vm_device::BusDevice; use vmm_sys_util::eventfd::EventFd; diff --git a/devices/src/legacy/debug_port.rs b/devices/src/legacy/debug_port.rs index 28a58b319d..bd9a31d79b 100644 --- a/devices/src/legacy/debug_port.rs +++ b/devices/src/legacy/debug_port.rs @@ -6,6 +6,7 @@ use std::fmt; use std::time::Instant; +use log::{error, warn}; use vm_device::BusDevice; /// Debug I/O port, see: diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index df31104aff..f33179d831 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -35,6 +35,7 @@ use bitfield_struct::bitfield; use linux_loader::bootparam::boot_params; #[cfg(target_arch = "aarch64")] use linux_loader::loader::pe::arm64_image_header as boot_params; +use log::{debug, error}; use vm_device::BusDevice; use vm_memory::bitmap::AtomicBitmap; use vm_memory::{ diff --git a/devices/src/legacy/fwdebug.rs b/devices/src/legacy/fwdebug.rs index e678f44977..1024d262f5 100644 --- a/devices/src/legacy/fwdebug.rs +++ b/devices/src/legacy/fwdebug.rs @@ -9,6 +9,7 @@ use std::sync::{Arc, Barrier}; +use log::error; use vm_device::BusDevice; /// Provides firmware debug output via I/O port controls diff --git a/devices/src/legacy/gpio_pl061.rs b/devices/src/legacy/gpio_pl061.rs index 6e7b057d81..0f4ec5f90e 100644 --- a/devices/src/legacy/gpio_pl061.rs +++ b/devices/src/legacy/gpio_pl061.rs @@ -10,6 +10,7 @@ use std::sync::{Arc, Barrier}; use std::{io, result}; +use log::warn; use serde::{Deserialize, Serialize}; use thiserror::Error; use vm_device::BusDevice; diff --git a/devices/src/legacy/i8042.rs b/devices/src/legacy/i8042.rs index bbfe94eeb4..0e014ab8bf 100644 --- a/devices/src/legacy/i8042.rs +++ b/devices/src/legacy/i8042.rs @@ -8,6 +8,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Barrier}; use std::thread; +use log::{error, info}; use vm_device::BusDevice; use vmm_sys_util::eventfd::EventFd; diff --git a/devices/src/legacy/rtc_pl031.rs b/devices/src/legacy/rtc_pl031.rs index b365ebef45..ac4509113d 100644 --- a/devices/src/legacy/rtc_pl031.rs +++ b/devices/src/legacy/rtc_pl031.rs @@ -15,6 +15,7 @@ use std::result; use std::sync::{Arc, Barrier}; use std::time::Instant; +use log::warn; use thiserror::Error; use vm_device::BusDevice; diff --git a/devices/src/legacy/uart_pl011.rs b/devices/src/legacy/uart_pl011.rs index c64f071e56..aac8f12ea8 100644 --- a/devices/src/legacy/uart_pl011.rs +++ b/devices/src/legacy/uart_pl011.rs @@ -11,6 +11,7 @@ use std::sync::{Arc, Barrier}; use std::time::Instant; use std::{io, result}; +use log::{debug, warn}; use serde::{Deserialize, Serialize}; use thiserror::Error; use vm_device::BusDevice; diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 0c4bfb8ca4..cdec936e26 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -7,13 +7,6 @@ //! Emulates virtual and hardware devices. -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate event_monitor; -#[macro_use] -extern crate log; - pub mod acpi; #[cfg(target_arch = "riscv64")] pub mod aia; @@ -34,6 +27,8 @@ pub mod pvpanic; #[cfg(not(target_arch = "riscv64"))] pub mod tpm; +use bitflags::bitflags; + pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice}; #[cfg(feature = "ivshmem")] pub use self::ivshmem::IvshmemDevice; diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index d078a1db15..d7fd7ff3b3 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -8,6 +8,7 @@ use std::ffi::CString; use std::sync::{Arc, Barrier, Mutex, RwLock}; use std::{io, result}; +use log::{debug, warn}; use num_enum::TryFromPrimitive; use pci::{ BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, diff --git a/devices/src/pvpanic.rs b/devices/src/pvpanic.rs index 9150e2e986..ef42b03610 100644 --- a/devices/src/pvpanic.rs +++ b/devices/src/pvpanic.rs @@ -8,6 +8,8 @@ use std::result; use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; +use event_monitor::event; +use log::{debug, info}; use pci::{ BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs index 1c6879b1ae..532b2c4a94 100644 --- a/devices/src/tpm.rs +++ b/devices/src/tpm.rs @@ -11,6 +11,7 @@ use anyhow::anyhow; use arch::aarch64::layout::{TPM_SIZE, TPM_START}; #[cfg(target_arch = "x86_64")] use arch::x86_64::layout::{TPM_SIZE, TPM_START}; +use log::{debug, error, warn}; use thiserror::Error; use tpm::TPM_CRB_BUFFER_MAX; use tpm::emulator::{BackendCmd, Emulator}; From b3f929432e83a717bd5b53c003ec8af300a2aee6 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:20:33 +0200 Subject: [PATCH 0332/1893] misc: hypervisor: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- hypervisor/src/arch/x86/emulator/instructions/mod.rs | 1 + hypervisor/src/arch/x86/emulator/instructions/movs.rs | 2 ++ hypervisor/src/arch/x86/emulator/instructions/stos.rs | 2 ++ hypervisor/src/arch/x86/emulator/mod.rs | 3 ++- hypervisor/src/cpu.rs | 2 +- hypervisor/src/kvm/mod.rs | 3 +++ hypervisor/src/lib.rs | 7 +------ hypervisor/src/mshv/aarch64/emulator.rs | 2 ++ hypervisor/src/mshv/mod.rs | 8 ++++---- hypervisor/src/mshv/x86_64/emulator.rs | 2 ++ 10 files changed, 20 insertions(+), 12 deletions(-) diff --git a/hypervisor/src/arch/x86/emulator/instructions/mod.rs b/hypervisor/src/arch/x86/emulator/instructions/mod.rs index 99d797c0b8..1f4665bcec 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/mod.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/mod.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: Apache-2.0 // +use anyhow::anyhow; use iced_x86::*; use crate::arch::emulator::{EmulationError, PlatformEmulator, PlatformError}; diff --git a/hypervisor/src/arch/x86/emulator/instructions/movs.rs b/hypervisor/src/arch/x86/emulator/instructions/movs.rs index 5642e04f4f..a4831481b2 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/movs.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/movs.rs @@ -10,6 +10,8 @@ // MOVS - Move Data from String to String // +use anyhow::anyhow; + use crate::arch::x86::emulator::instructions::*; use crate::arch::x86::regs::DF; diff --git a/hypervisor/src/arch/x86/emulator/instructions/stos.rs b/hypervisor/src/arch/x86/emulator/instructions/stos.rs index 38233eea4f..c830126fb8 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/stos.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/stos.rs @@ -10,6 +10,8 @@ // STOS - Store String // +use anyhow::anyhow; + use crate::arch::x86::emulator::instructions::*; use crate::arch::x86::regs::DF; diff --git a/hypervisor/src/arch/x86/emulator/mod.rs b/hypervisor/src/arch/x86/emulator/mod.rs index 591a5e42b0..3de545e475 100644 --- a/hypervisor/src/arch/x86/emulator/mod.rs +++ b/hypervisor/src/arch/x86/emulator/mod.rs @@ -4,8 +4,9 @@ // SPDX-License-Identifier: Apache-2.0 // -use anyhow::Context; +use anyhow::{Context, anyhow}; use iced_x86::*; +use log::debug; use crate::StandardRegisters; use crate::arch::emulator::{EmulationError, EmulationResult, PlatformEmulator, PlatformError}; diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index 763eaa4558..23a1632c1c 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -12,7 +12,7 @@ use thiserror::Error; #[cfg(not(target_arch = "riscv64"))] -use vm_memory::GuestAddress; +use {anyhow::anyhow, vm_memory::GuestAddress}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use crate::RegList; diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 6a1b31607d..0ecf9a48db 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -25,7 +25,10 @@ use std::sync::Mutex; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; +use anyhow::anyhow; use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd}; +#[cfg(target_arch = "x86_64")] +use log::warn; use vmm_sys_util::eventfd::EventFd; #[cfg(target_arch = "aarch64")] diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 607166968e..1899d282b1 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -21,12 +21,6 @@ //! - riscv64 (experimental) //! -#[macro_use] -extern crate anyhow; -#[allow(unused_imports)] -#[macro_use] -extern crate log; - /// Architecture specific definitions #[macro_use] pub mod arch; @@ -53,6 +47,7 @@ mod device; use std::sync::Arc; +use anyhow::anyhow; use concat_idents::concat_idents; #[cfg(target_arch = "x86_64")] pub use cpu::CpuVendor; diff --git a/hypervisor/src/mshv/aarch64/emulator.rs b/hypervisor/src/mshv/aarch64/emulator.rs index a976598400..d71b50cf87 100644 --- a/hypervisor/src/mshv/aarch64/emulator.rs +++ b/hypervisor/src/mshv/aarch64/emulator.rs @@ -3,6 +3,8 @@ // Copyright © 2025, Microsoft Corporation // +use anyhow::anyhow; + use crate::arch::aarch64::regs::{EsrEl2, ExceptionClass, IssDataAbort}; use crate::arch::emulator::PlatformError; use crate::cpu::Vcpu; diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 3ca3d846f9..7194567203 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -9,8 +9,8 @@ use std::collections::HashMap; use std::num::NonZeroUsize; use std::sync::{Arc, RwLock}; -#[cfg(feature = "sev_snp")] -use arc_swap::ArcSwap; +use anyhow::anyhow; +use log::{debug, warn}; use mshv_bindings::*; #[cfg(target_arch = "x86_64")] use mshv_ioctls::InterruptRequest; @@ -19,6 +19,8 @@ use vfio_ioctls::VfioDeviceFd; use vm::DataMatch; #[cfg(feature = "sev_snp")] use vm_memory::bitmap::AtomicBitmap; +#[cfg(feature = "sev_snp")] +use {arc_swap::ArcSwap, log::info}; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::regs::{ @@ -271,7 +273,6 @@ impl hypervisor::Hypervisor for MshvHypervisor { /// # Examples /// /// ``` - /// # extern crate hypervisor; /// use hypervisor::mshv::MshvHypervisor; /// use hypervisor::mshv::MshvVm; /// use hypervisor::HypervisorVmConfig; @@ -1700,7 +1701,6 @@ impl MshvVm { /// # Examples /// /// ``` -/// extern crate hypervisor; /// use hypervisor::mshv::MshvHypervisor; /// use hypervisor::HypervisorVmConfig; /// use std::sync::Arc; diff --git a/hypervisor/src/mshv/x86_64/emulator.rs b/hypervisor/src/mshv/x86_64/emulator.rs index 80d2a2edab..3000cdf59e 100644 --- a/hypervisor/src/mshv/x86_64/emulator.rs +++ b/hypervisor/src/mshv/x86_64/emulator.rs @@ -3,7 +3,9 @@ // Copyright © 2024, Microsoft Corporation // +use anyhow::anyhow; use iced_x86::Register; +use log::debug; use mshv_bindings::*; use crate::arch::emulator::{PlatformEmulator, PlatformError}; From 47dff4f6bc601bdb627ad906a63b1b8532204c46 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:20:36 +0200 Subject: [PATCH 0333/1893] misc: net_gen: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- net_gen/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net_gen/src/lib.rs b/net_gen/src/lib.rs index bb0148a004..91a5c8c15d 100644 --- a/net_gen/src/lib.rs +++ b/net_gen/src/lib.rs @@ -8,9 +8,6 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] -#[macro_use] -extern crate vmm_sys_util; - // generated with bindgen /usr/include/linux/if.h --no-unstable-rust // --constified-enum '*' --with-derive-default -- -D __UAPI_DEF_IF_IFNAMSIZ -D // __UAPI_DEF_IF_NET_DEVICE_FLAGS -D __UAPI_DEF_IF_IFREQ -D __UAPI_DEF_IF_IFMAP @@ -39,6 +36,7 @@ pub use if_tun::{ pub use iff::{ifreq, net_device_flags_IFF_UP, setsockopt, sockaddr, AF_INET}; pub use inn::sockaddr_in; pub use ipv6::{in6_ifreq, sockaddr_in6}; +use vmm_sys_util::{ioctl_ior_nr, ioctl_iow_nr}; pub const TUNTAP: ::std::os::raw::c_uint = 84; From 492f24c632a02845022cea4d2db76d8992d949c0 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:20:41 +0200 Subject: [PATCH 0334/1893] misc: net_util: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- net_util/src/ctrl_queue.rs | 1 + net_util/src/lib.rs | 3 --- net_util/src/mac.rs | 1 + net_util/src/open_tap.rs | 1 + net_util/src/queue_pair.rs | 1 + 5 files changed, 4 insertions(+), 3 deletions(-) diff --git a/net_util/src/ctrl_queue.rs b/net_util/src/ctrl_queue.rs index 6b668a27e6..8eb8408621 100644 --- a/net_util/src/ctrl_queue.rs +++ b/net_util/src/ctrl_queue.rs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +use log::{error, info, warn}; use thiserror::Error; use virtio_bindings::virtio_net::{ VIRTIO_NET_CTRL_GUEST_OFFLOADS, VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, VIRTIO_NET_CTRL_MQ, diff --git a/net_util/src/lib.rs b/net_util/src/lib.rs index 1cc43d4758..6cf5791507 100644 --- a/net_util/src/lib.rs +++ b/net_util/src/lib.rs @@ -5,9 +5,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the THIRD-PARTY file. -#[macro_use] -extern crate log; - mod ctrl_queue; mod mac; mod open_tap; diff --git a/net_util/src/mac.rs b/net_util/src/mac.rs index d3a3c9575c..dec8027889 100644 --- a/net_util/src/mac.rs +++ b/net_util/src/mac.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use std::{fmt, io}; +use log::error; use serde::de::{Deserialize, Deserializer, Error}; use serde::ser::{Serialize, Serializer}; diff --git a/net_util/src/open_tap.rs b/net_util/src/open_tap.rs index cbe77765c7..bbe1db432e 100644 --- a/net_util/src/open_tap.rs +++ b/net_util/src/open_tap.rs @@ -6,6 +6,7 @@ use std::net::IpAddr; use std::path::Path; use std::{fs, io}; +use log::warn; use thiserror::Error; use super::{MacAddr, Tap, TapError, vnet_hdr_len}; diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index 6202833a16..a6bce4409b 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -8,6 +8,7 @@ use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; +use log::{error, info}; use rate_limiter::{RateLimiter, TokenType}; use thiserror::Error; use virtio_queue::{Queue, QueueOwnedT, QueueT}; From 6f4b5253b4b3234d37b33c64f2dcc13ed8365c55 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:20:46 +0200 Subject: [PATCH 0335/1893] misc: pci: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- pci/src/bus.rs | 1 + pci/src/configuration.rs | 1 + pci/src/lib.rs | 2 -- pci/src/msi.rs | 1 + pci/src/msix.rs | 1 + pci/src/vfio.rs | 1 + pci/src/vfio_user.rs | 1 + 7 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pci/src/bus.rs b/pci/src/bus.rs index fd19321de5..3ba2c056b7 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -10,6 +10,7 @@ use std::ops::DerefMut; use std::sync::{Arc, Barrier, Mutex}; use byteorder::{ByteOrder, LittleEndian}; +use log::error; use thiserror::Error; use vm_device::{Bus, BusDevice, BusDeviceSync}; diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index 0e45b1c4c3..b97c46c0ba 100644 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -7,6 +7,7 @@ use std::sync::{Arc, Mutex}; use byteorder::{ByteOrder, LittleEndian}; +use log::{info, warn}; use serde::{Deserialize, Serialize}; use thiserror::Error; use vm_device::PciBarType; diff --git a/pci/src/lib.rs b/pci/src/lib.rs index 8c1532491b..5ab87cf19d 100644 --- a/pci/src/lib.rs +++ b/pci/src/lib.rs @@ -5,8 +5,6 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause //! Implements pci devices and busses. -#[macro_use] -extern crate log; mod bus; mod configuration; diff --git a/pci/src/msi.rs b/pci/src/msi.rs index e0d07cec27..ace1347520 100644 --- a/pci/src/msi.rs +++ b/pci/src/msi.rs @@ -7,6 +7,7 @@ use std::io; use std::sync::Arc; use byteorder::{ByteOrder, LittleEndian}; +use log::error; use serde::{Deserialize, Serialize}; use thiserror::Error; use vm_device::interrupt::{ diff --git a/pci/src/msix.rs b/pci/src/msix.rs index efd0e7b67e..9bc5e63f3a 100644 --- a/pci/src/msix.rs +++ b/pci/src/msix.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use std::{io, result}; use byteorder::{ByteOrder, LittleEndian}; +use log::{debug, error}; use serde::{Deserialize, Serialize}; use thiserror::Error; use vm_device::interrupt::{ diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 23efa8c3d7..87adf08d82 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -15,6 +15,7 @@ use anyhow::anyhow; use byteorder::{ByteOrder, LittleEndian}; use hypervisor::HypervisorVmError; use libc::{_SC_PAGESIZE, sysconf}; +use log::{error, info}; use serde::{Deserialize, Serialize}; use thiserror::Error; use vfio_bindings::bindings::vfio::*; diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index 3ce9ebdb04..5aa2266cab 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -9,6 +9,7 @@ use std::os::unix::prelude::AsRawFd; use std::sync::{Arc, Barrier, Mutex}; use hypervisor::HypervisorVmError; +use log::{error, info}; use thiserror::Error; use vfio_bindings::bindings::vfio::*; use vfio_ioctls::VfioIrq; From 2c1eccc6bc3be82011c12a9623ef5c5404433dc0 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:20:54 +0200 Subject: [PATCH 0336/1893] misc: performance-metrics: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- performance-metrics/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 1b374a573f..bba89cc4fc 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -4,8 +4,6 @@ // // Custom harness to run performance tests -extern crate test_infra; - mod performance_tests; use std::process::Command; From 37091050432fd56d58fdb5a82078b33e45f4ea14 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:21:01 +0200 Subject: [PATCH 0337/1893] misc: rate_limiter: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- rate_limiter/src/group.rs | 1 + rate_limiter/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rate_limiter/src/group.rs b/rate_limiter/src/group.rs index ce726e40d3..54e1401617 100644 --- a/rate_limiter/src/group.rs +++ b/rate_limiter/src/group.rs @@ -10,6 +10,7 @@ use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::sync::{Arc, Mutex}; use std::{io, result, thread}; +use log::{error, info, warn}; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; diff --git a/rate_limiter/src/lib.rs b/rate_limiter/src/lib.rs index da3a4c78ff..cd73253c59 100644 --- a/rate_limiter/src/lib.rs +++ b/rate_limiter/src/lib.rs @@ -43,8 +43,6 @@ //! It is meant to be used in an external event loop and thus implements the `AsRawFd` //! trait and provides an *event-handler* as part of its API. This *event-handler* //! needs to be called by the user on every event on the rate limiter's `AsRawFd` FD. -#[macro_use] -extern crate log; use std::io; use std::os::unix::io::{AsRawFd, RawFd}; @@ -52,6 +50,7 @@ use std::sync::Mutex; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; +use log::error; use thiserror::Error; use vmm_sys_util::timerfd::TimerFd; From 7de45b3a7594550d2c2c85703644299d70a3788a Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:21:04 +0200 Subject: [PATCH 0338/1893] misc: tests: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- tests/integration.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 4ef1029b71..468ab36b71 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -9,8 +9,6 @@ // related warnings for our quality workflow to pass. #![allow(dead_code)] -extern crate test_infra; - use std::collections::HashMap; use std::ffi::CStr; use std::fs::OpenOptions; From d32b2dc13715ecb25127926afe4297b95e29343a Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:21:07 +0200 Subject: [PATCH 0339/1893] misc: tpm: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- tpm/src/emulator.rs | 1 + tpm/src/lib.rs | 3 --- tpm/src/socket.rs | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tpm/src/emulator.rs b/tpm/src/emulator.rs index 16e88c5af7..a0e9ab6ecf 100644 --- a/tpm/src/emulator.rs +++ b/tpm/src/emulator.rs @@ -9,6 +9,7 @@ use std::{mem, ptr}; use anyhow::anyhow; use libc::{c_void, sockaddr_storage, socklen_t}; +use log::{debug, error}; use thiserror::Error; use crate::socket::SocketDev; diff --git a/tpm/src/lib.rs b/tpm/src/lib.rs index 46a7422d73..ad021b3505 100644 --- a/tpm/src/lib.rs +++ b/tpm/src/lib.rs @@ -3,9 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 // -#[macro_use] -extern crate log; - pub mod emulator; pub mod socket; diff --git a/tpm/src/socket.rs b/tpm/src/socket.rs index 0c248e43eb..ca4ae54358 100644 --- a/tpm/src/socket.rs +++ b/tpm/src/socket.rs @@ -8,6 +8,7 @@ use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::UnixStream; use anyhow::anyhow; +use log::debug; use thiserror::Error; use vmm_sys_util::sock_ctrl_msg::ScmSocket; From 29e8a242b6a79ffaad8d16850806e7863a6122ee Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:21:10 +0200 Subject: [PATCH 0340/1893] misc: tracer: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- tracer/src/lib.rs | 4 ---- tracer/src/tracer.rs | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tracer/src/lib.rs b/tracer/src/lib.rs index 40677cf8bd..4c68f89379 100644 --- a/tracer/src/lib.rs +++ b/tracer/src/lib.rs @@ -3,10 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 // -#[cfg(feature = "tracing")] -#[macro_use] -extern crate log; - #[cfg(not(feature = "tracing"))] mod tracer_noop; #[cfg(not(feature = "tracing"))] diff --git a/tracer/src/tracer.rs b/tracer/src/tracer.rs index 3ff20bebb7..56694ff2b9 100644 --- a/tracer/src/tracer.rs +++ b/tracer/src/tracer.rs @@ -13,6 +13,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; +use log::warn; use serde::Serialize; #[derive(Debug)] From 1f70e20c1767ce38b9d64cc196e09a1fe5925552 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:21:15 +0200 Subject: [PATCH 0341/1893] misc: vhost_user_block: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vhost_user_block/src/lib.rs | 2 +- vhost_user_block/src/main.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 0123ac2042..5ad48608ee 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -21,7 +21,7 @@ use std::{convert, io, process, result}; use block::qcow::{self, ImageType, QcowFile}; use block::{Request, VirtioBlockConfig, build_serial}; use libc::EFD_NONBLOCK; -use log::*; +use log::{debug, error, info}; use option_parser::{OptionParser, OptionParserError, Toggle}; use thiserror::Error; use vhost::vhost_user::Listener; diff --git a/vhost_user_block/src/main.rs b/vhost_user_block/src/main.rs index 101d7131f7..9d962e44b8 100644 --- a/vhost_user_block/src/main.rs +++ b/vhost_user_block/src/main.rs @@ -8,8 +8,6 @@ // // SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause) -extern crate vhost_user_block; - use clap::{Arg, Command}; use vhost_user_block::start_block_backend; From adb7cfd72c0bdbce664d11b1acf82dd2a8b3fcdb Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:21:33 +0200 Subject: [PATCH 0342/1893] misc: vhost_user_net: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vhost_user_net/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vhost_user_net/src/lib.rs b/vhost_user_net/src/lib.rs index c625fbd906..0e89a763a8 100644 --- a/vhost_user_net/src/lib.rs +++ b/vhost_user_net/src/lib.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex, RwLock}; use std::{io, process}; use libc::EFD_NONBLOCK; -use log::*; +use log::error; use net_util::{ MacAddr, NetCounters, NetQueuePair, OpenTapError, RxVirtio, Tap, TxVirtio, open_tap, }; From 2157f1a1f2d0d17933bf1e9151e89ca671a6397f Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:21:37 +0200 Subject: [PATCH 0343/1893] misc: virtio-devices: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- virtio-devices/src/balloon.rs | 2 ++ virtio-devices/src/block.rs | 10 ++++++---- virtio-devices/src/console.rs | 2 ++ virtio-devices/src/device.rs | 1 + virtio-devices/src/epoll_helper.rs | 1 + virtio-devices/src/iommu.rs | 2 ++ virtio-devices/src/lib.rs | 5 ----- virtio-devices/src/mem.rs | 2 ++ virtio-devices/src/net.rs | 2 ++ virtio-devices/src/pmem.rs | 2 ++ virtio-devices/src/rng.rs | 2 ++ virtio-devices/src/thread_helper.rs | 1 + virtio-devices/src/transport/pci_common_config.rs | 1 + virtio-devices/src/transport/pci_device.rs | 1 + virtio-devices/src/vdpa.rs | 2 ++ virtio-devices/src/vhost_user/blk.rs | 2 ++ virtio-devices/src/vhost_user/fs.rs | 2 ++ virtio-devices/src/vhost_user/mod.rs | 1 + virtio-devices/src/vhost_user/net.rs | 2 ++ virtio-devices/src/vhost_user/vu_common_ctrl.rs | 1 + virtio-devices/src/vsock/csm/connection.rs | 2 ++ virtio-devices/src/vsock/device.rs | 2 ++ virtio-devices/src/vsock/unix/muxer.rs | 2 ++ virtio-devices/src/watchdog.rs | 2 ++ 24 files changed, 43 insertions(+), 9 deletions(-) diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index dfb81d0a91..ca690ff3f8 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -22,6 +22,8 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier}; use anyhow::anyhow; +use event_monitor::event; +use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 73f87f566c..37488a82a0 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -23,6 +23,8 @@ use block::fcntl::{LockError, LockGranularity, LockType, get_lock_state}; use block::{ ExecuteAsync, ExecuteError, Request, RequestType, VirtioBlockConfig, build_serial, fcntl, }; +use event_monitor::event; +use log::{debug, error, info, warn}; use rate_limiter::TokenType; use rate_limiter::group::{RateLimiterGroup, RateLimiterGroupHandle}; use seccompiler::SeccompAction; @@ -795,7 +797,7 @@ impl Block { false => LockType::Write, }; let granularity = self.lock_granularity(); - log::debug!( + debug!( "Attempting to acquire {lock_type:?} lock for disk image: id={},path={},granularity={granularity:?}", self.id, self.disk_path.display() @@ -806,9 +808,9 @@ impl Block { // Don't propagate the error to the outside, as it is not useful at all. Instead, // we try to log additional help to the user. if let Ok(current_lock) = current_lock { - log::error!("Can't get {lock_type:?} lock for {} as there is already a {current_lock:?} lock", self.disk_path.display()); + error!("Can't get {lock_type:?} lock for {} as there is already a {current_lock:?} lock", self.disk_path.display()); } else { - log::error!("Can't get {lock_type:?} lock for {}, but also can't determine the current lock state", self.disk_path.display()); + error!("Can't get {lock_type:?} lock for {}, but also can't determine the current lock state", self.disk_path.display()); } Error::LockDiskImage { path: self.disk_path.clone(), @@ -816,7 +818,7 @@ impl Block { lock_type, } })?; - log::info!( + info!( "Acquired {lock_type:?} lock for disk image id={},path={}", self.id, self.disk_path.display() diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index eeeeb50537..a036a54612 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -10,7 +10,9 @@ use std::sync::{Arc, Barrier, Mutex}; use std::{cmp, io, result}; use anyhow::anyhow; +use event_monitor::event; use libc::{EFD_NONBLOCK, TIOCGWINSZ}; +use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use serial_buffer::SerialBuffer; diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index cd9a8fea75..f0ed28f517 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -14,6 +14,7 @@ use std::sync::{Arc, Barrier}; use std::thread; use libc::EFD_NONBLOCK; +use log::{error, info, warn}; use virtio_queue::Queue; use vm_device::UserspaceMapping; use vm_memory::{GuestAddress, GuestMemoryAtomic}; diff --git a/virtio-devices/src/epoll_helper.rs b/virtio-devices/src/epoll_helper.rs index 5c0b3a57c6..c20c1190a5 100644 --- a/virtio-devices/src/epoll_helper.rs +++ b/virtio-devices/src/epoll_helper.rs @@ -14,6 +14,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Barrier}; use std::thread; +use log::info; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index e89665e513..1318e575f1 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -10,6 +10,8 @@ use std::sync::{Arc, Barrier, Mutex, RwLock}; use std::{io, result}; use anyhow::anyhow; +use event_monitor::event; +use log::{debug, error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index fad2d987f5..da4f1c91be 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -10,11 +10,6 @@ //! Implements virtio devices, queues, and transport mechanisms. -#[macro_use] -extern crate event_monitor; -#[macro_use] -extern crate log; - use std::io; use serde::{Deserialize, Serialize}; diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index eda263c0f6..3890928a54 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -22,6 +22,8 @@ use std::sync::{Arc, Barrier, Mutex, mpsc}; use std::{io, result}; use anyhow::anyhow; +use event_monitor::event; +use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index f0e98da3bd..71faf85492 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -15,6 +15,8 @@ use std::sync::{Arc, Barrier}; use std::{result, thread}; use anyhow::anyhow; +use event_monitor::event; +use log::{debug, error, info}; #[cfg(not(fuzzing))] use net_util::virtio_features_to_tap_offload; use net_util::{ diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index ff8ad296b1..da3722e254 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -14,6 +14,8 @@ use std::sync::{Arc, Barrier}; use std::{io, result}; use anyhow::anyhow; +use event_monitor::event; +use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index c1d5ce9e1e..aff511b0a3 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -11,6 +11,8 @@ use std::sync::{Arc, Barrier}; use std::{io, result}; use anyhow::anyhow; +use event_monitor::event; +use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/virtio-devices/src/thread_helper.rs b/virtio-devices/src/thread_helper.rs index 735aebbf55..343e7c1678 100644 --- a/virtio-devices/src/thread_helper.rs +++ b/virtio-devices/src/thread_helper.rs @@ -6,6 +6,7 @@ use std::panic::AssertUnwindSafe; use std::thread::{self, JoinHandle}; +use log::error; use seccompiler::{SeccompAction, apply_filter}; use vmm_sys_util::eventfd::EventFd; diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 363c621eb4..1a576ad5a7 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -10,6 +10,7 @@ use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::{Arc, Mutex}; use byteorder::{ByteOrder, LittleEndian}; +use log::{debug, error, warn}; use serde::{Deserialize, Serialize}; use virtio_queue::{Queue, QueueT}; use vm_migration::{MigratableError, Pausable, Snapshot, Snapshottable}; diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index ca60c2c063..0e1eb2ccef 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -15,6 +15,7 @@ use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; use libc::EFD_NONBLOCK; +use log::{error, info}; use pci::{ BarReprogrammingParams, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, PciCapability, PciCapabilityId, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 7c38e2f30e..867cf21cce 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -9,6 +9,8 @@ use std::sync::{Arc, Mutex}; use std::{io, result}; use anyhow::anyhow; +use event_monitor::event; +use log::{debug, error, info}; use serde::{Deserialize, Serialize}; use thiserror::Error; use vhost::vdpa::{VhostVdpa, VhostVdpaIovaRange}; diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 2c32e0b854..10bc874087 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -6,6 +6,8 @@ use std::sync::{Arc, Barrier, Mutex}; use std::{mem, result, thread}; use block::VirtioBlockConfig; +use event_monitor::event; +use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use vhost::vhost_user::message::{ diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index b8d7289206..726d88367b 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -5,6 +5,8 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier, Mutex}; use std::{result, thread}; +use event_monitor::event; +use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use serde_with::{Bytes, serde_as}; diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 1db305421b..764b0d1c02 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -8,6 +8,7 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; +use log::error; use serde::{Deserialize, Serialize}; use thiserror::Error; use vhost::Error as VhostError; diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index fef399e899..fb79d52d83 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -5,6 +5,8 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier, Mutex}; use std::{result, thread}; +use event_monitor::event; +use log::{error, info}; use net_util::{CtrlQueue, MacAddr, VirtioNetConfig, build_net_config_space}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 5f81bc2471..76a792c875 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -10,6 +10,7 @@ use std::sync::atomic::Ordering; use std::thread::sleep; use std::time::{Duration, Instant}; +use log::{error, info}; use vhost::vhost_kern::vhost_binding::{VHOST_F_LOG_ALL, VHOST_VRING_F_LOG}; use vhost::vhost_user::message::{ VhostUserHeaderFlag, VhostUserInflight, VhostUserProtocolFeatures, VhostUserVirtioFeatures, diff --git a/virtio-devices/src/vsock/csm/connection.rs b/virtio-devices/src/vsock/csm/connection.rs index 3b87194da8..fe89365e34 100644 --- a/virtio-devices/src/vsock/csm/connection.rs +++ b/virtio-devices/src/vsock/csm/connection.rs @@ -85,6 +85,8 @@ use std::num::Wrapping; use std::os::unix::io::{AsRawFd, RawFd}; use std::time::{Duration, Instant}; +use log::{debug, error, info, warn}; + use super::super::defs::uapi; use super::super::packet::VsockPacket; use super::super::{Result as VsockResult, VsockChannel, VsockEpollListener, VsockError}; diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 38b834a432..63cfb3a674 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -16,6 +16,8 @@ use std::{io, result}; use anyhow::anyhow; use byteorder::{ByteOrder, LittleEndian}; +use event_monitor::event; +use log::{debug, error, info, warn}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use virtio_queue::{Queue, QueueOwnedT, QueueT}; diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index 6d91c6657b..ee2264476f 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -45,6 +45,8 @@ use std::io::{self, ErrorKind, Read}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::net::{UnixListener, UnixStream}; +use log::{debug, error, info, warn}; + use super::super::csm::ConnState; use super::super::defs::uapi; use super::super::packet::VsockPacket; diff --git a/virtio-devices/src/watchdog.rs b/virtio-devices/src/watchdog.rs index 124d586f82..2f76cae37b 100644 --- a/virtio-devices/src/watchdog.rs +++ b/virtio-devices/src/watchdog.rs @@ -14,6 +14,8 @@ use std::sync::{Arc, Barrier, Mutex}; use std::time::Instant; use anyhow::anyhow; +use event_monitor::event; +use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; From 67fc9d990ea4547e00694c611e1fd79ed4fd9bb2 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 5 Sep 2025 09:21:43 +0200 Subject: [PATCH 0344/1893] misc: vmm: drop `extern crate`, use modern rust This commit is part of a series of similar commits. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/acpi.rs | 1 + vmm/src/api/dbus/mod.rs | 1 + vmm/src/api/http/http_endpoint.rs | 4 +++- vmm/src/api/http/mod.rs | 1 + vmm/src/api/mod.rs | 1 + vmm/src/config.rs | 1 + vmm/src/cpu.rs | 6 ++++++ vmm/src/device_manager.rs | 6 ++++-- vmm/src/gdb.rs | 1 + vmm/src/igvm/igvm_loader.rs | 3 +++ vmm/src/igvm/loader.rs | 1 + vmm/src/lib.rs | 7 ++----- vmm/src/memory_manager.rs | 3 +++ vmm/src/pci_segment.rs | 1 + vmm/src/serial_manager.rs | 1 + vmm/src/vm.rs | 2 ++ vmm/src/vm_config.rs | 7 +++---- 17 files changed, 35 insertions(+), 12 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index f748c87c59..56513a6d04 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -16,6 +16,7 @@ use arch::NumaNodes; #[cfg(target_arch = "aarch64")] use arch::aarch64::DeviceInfoForFdt; use bitflags::bitflags; +use log::info; use pci::PciBdf; use tracer::trace_scoped; use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryRegion}; diff --git a/vmm/src/api/dbus/mod.rs b/vmm/src/api/dbus/mod.rs index 59c48f8a97..6f75fb5cda 100644 --- a/vmm/src/api/dbus/mod.rs +++ b/vmm/src/api/dbus/mod.rs @@ -10,6 +10,7 @@ use std::thread; use futures::channel::oneshot; use futures::{FutureExt, executor}; use hypervisor::HypervisorType; +use log::{error, warn}; use seccompiler::{SeccompAction, apply_filter}; use vmm_sys_util::eventfd::EventFd; use zbus::connection::Builder; diff --git a/vmm/src/api/http/http_endpoint.rs b/vmm/src/api/http/http_endpoint.rs index b25b7e9ab8..be8118e0e4 100644 --- a/vmm/src/api/http/http_endpoint.rs +++ b/vmm/src/api/http/http_endpoint.rs @@ -73,6 +73,8 @@ mod fds_helper { use std::fs::File; use std::os::fd::{IntoRawFd, RawFd}; + use log::{debug, error, warn}; + use crate::api::http::HttpError; /// Abstraction over configuration types received via the HTTP API that @@ -172,7 +174,7 @@ mod fds_helper { if fds_amount > 0 { let new_fds = fds.drain(..fds_amount).collect::>(); - log::debug!( + debug!( "Attaching network FDs received via UNIX domain socket to device: id={:?}, fds={new_fds:?}", cfg.id() ); diff --git a/vmm/src/api/http/mod.rs b/vmm/src/api/http/mod.rs index 456610ce90..a35c9aca07 100644 --- a/vmm/src/api/http/mod.rs +++ b/vmm/src/api/http/mod.rs @@ -15,6 +15,7 @@ use std::sync::mpsc::Sender; use std::thread; use hypervisor::HypervisorType; +use log::error; use micro_http::{ Body, HttpServer, MediaType, Method, Request, Response, ServerError, StatusCode, Version, }; diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 4bb4d00151..96c99f3fce 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -36,6 +36,7 @@ pub mod http; use std::io; use std::sync::mpsc::{RecvError, SendError, Sender, channel}; +use log::info; use micro_http::Body; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/vmm/src/config.rs b/vmm/src/config.rs index c7ec5a07de..5c04e6083f 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -11,6 +11,7 @@ use std::result; use std::str::FromStr; use clap::ArgMatches; +use log::{debug, warn}; use option_parser::{ ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, Tuple, }; diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 5d35cab915..67cd79bdbd 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -58,6 +58,12 @@ use hypervisor::{CpuState, HypervisorCpuError, VmExit, VmOps}; use libc::{c_void, siginfo_t}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use linux_loader::elf::Elf64_Nhdr; +#[cfg(any( + target_arch = "aarch64", + all(target_arch = "x86_64", feature = "guest_debug") +))] +use log::debug; +use log::{error, info, warn}; use seccompiler::{SeccompAction, apply_filter}; use thiserror::Error; use tracer::trace_scoped; diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 81a061aade..1883e0d7b6 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -68,6 +68,7 @@ use devices::legacy::{ #[cfg(feature = "pvmemcontrol")] use devices::pvmemcontrol::{PvmemcontrolBusDevice, PvmemcontrolPciDevice}; use devices::{AcpiNotificationFlags, interrupt_controller}; +use event_monitor::event; use hypervisor::IoEventAddress; #[cfg(target_arch = "aarch64")] use hypervisor::arch::aarch64::regs::AARCH64_PMU_IRQ; @@ -75,6 +76,7 @@ use libc::{ MAP_NORESERVE, MAP_PRIVATE, MAP_SHARED, O_TMPFILE, PROT_READ, PROT_WRITE, TCSANOW, tcsetattr, termios, }; +use log::{debug, error, info, warn}; use pci::{ DeviceRelocation, MmioRegion, PciBarRegionType, PciBdf, PciDevice, VfioDmaMapping, VfioPciDevice, VfioUserDmaMapping, VfioUserPciDevice, VfioUserPciDeviceError, @@ -2615,7 +2617,7 @@ impl DeviceManager { let (virtio_device, migratable_device) = if disk_cfg.vhost_user { if is_hotplug { - log::debug!("Acquiring image lock for vhost-user block device not supported"); + debug!("Acquiring image lock for vhost-user block device not supported"); } let socket = disk_cfg.vhost_socket.as_ref().unwrap().clone(); let vu_cfg = VhostUserConfig { @@ -2801,7 +2803,7 @@ impl DeviceManager { // state save/resume, and live-migration, locking is part of the outer control flow // to ensure proper order of (un)locking. if is_hotplug { - log::debug!("Acquiring lock for hotplugged image"); + debug!("Acquiring lock for hotplugged image"); virtio_block .try_lock_image() .map_err(DeviceManagerError::DiskLockError)?; diff --git a/vmm/src/gdb.rs b/vmm/src/gdb.rs index f97fb46253..fc24767d9c 100644 --- a/vmm/src/gdb.rs +++ b/vmm/src/gdb.rs @@ -29,6 +29,7 @@ use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs; use gdbstub_arch::x86::X86_64_SSE as GdbArch; #[cfg(target_arch = "x86_64")] use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs; +use log::{error, info}; use thiserror::Error; use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryError}; diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 03dd4d2472..e53dc698f5 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -15,6 +15,9 @@ use igvm_defs::{IGVM_VHS_MEMORY_MAP_ENTRY, MemoryMapEntryType}; use igvm_defs::{ IGVM_VHS_PARAMETER, IGVM_VHS_PARAMETER_INSERT, IgvmPageDataType, IgvmPlatformType, }; +use log::debug; +#[cfg(feature = "sev_snp")] +use log::info; use mshv_bindings::*; use thiserror::Error; use zerocopy::IntoBytes; diff --git a/vmm/src/igvm/loader.rs b/vmm/src/igvm/loader.rs index 316cadb1ac..a27eb94984 100644 --- a/vmm/src/igvm/loader.rs +++ b/vmm/src/igvm/loader.rs @@ -2,6 +2,7 @@ // // Copyright © 2023, Microsoft Corporation // +use log::debug; use range_map_vec::{Entry, RangeMap}; use thiserror::Error; use vm_memory::bitmap::AtomicBitmap; diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 84cf123ea7..e8c07dba55 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -3,11 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 // -#[macro_use] -extern crate event_monitor; -#[macro_use] -extern crate log; - use std::collections::HashMap; use std::fs::File; use std::io::{Read, Write, stdout}; @@ -30,8 +25,10 @@ use api::http::HttpApiHandle; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] use arch::x86_64::MAX_SUPPORTED_CPUS_LEGACY; use console_devices::{ConsoleInfo, pre_create_console_devices}; +use event_monitor::event; use landlock::LandlockError; use libc::{EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW, tcsetattr, termios}; +use log::{error, info, warn}; use memory_manager::MemoryManagerSnapshotData; use pci::PciBdf; use seccompiler::{SeccompAction, apply_filter}; diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index a9efbe99a6..f73790e421 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -25,6 +25,9 @@ use devices::ioapic; #[cfg(target_arch = "aarch64")] use hypervisor::HypervisorVmError; use libc::_SC_NPROCESSORS_ONLN; +#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] +use log::debug; +use log::{error, info, warn}; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracer::trace_scoped; diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index c31a60a226..b334ddb5d6 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex}; use acpi_tables::{Aml, aml}; use arch::layout; +use log::info; use pci::{DeviceRelocation, PciBdf, PciBus, PciConfigMmio, PciRoot}; #[cfg(target_arch = "x86_64")] use pci::{PCI_CONFIG_IO_PORT, PCI_CONFIG_IO_PORT_SIZE, PciConfigIo}; diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 2df1ba3ca6..6fa94a0c22 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -19,6 +19,7 @@ use devices::legacy::Pl011; #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] use devices::legacy::Serial; use libc::EFD_NONBLOCK; +use log::{error, info, warn}; use serial_buffer::SerialBuffer; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index b5bb4a70ab..fdaa778024 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -39,6 +39,7 @@ use devices::AcpiNotificationFlags; use devices::interrupt_controller; #[cfg(feature = "fw_cfg")] use devices::legacy::fw_cfg::FwCfgItem; +use event_monitor::event; #[cfg(all(target_arch = "aarch64", feature = "guest_debug"))] use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] @@ -57,6 +58,7 @@ use linux_loader::loader::bzimage::BzImage; use linux_loader::loader::elf::PvhBootCapability::PvhEntryPresent; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use linux_loader::loader::pe::Error::InvalidImageMagicNumber; +use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 70c3579025..662a9b4099 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -8,6 +8,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fs, result}; +use log::{debug, warn}; use net_util::MacAddr; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -807,13 +808,11 @@ impl PayloadConfig { (Some(_firmware), Some(_kernel)) => Err(PayloadConfigError::FirmwarePlusOtherPayloads), (Some(_firmware), None) => { if self.cmdline.is_some() { - log::warn!("Ignoring cmdline parameter as firmware is provided as the payload"); + warn!("Ignoring cmdline parameter as firmware is provided as the payload"); self.cmdline = None; } if self.initramfs.is_some() { - log::warn!( - "Ignoring initramfs parameter as firmware is provided as the payload" - ); + warn!("Ignoring initramfs parameter as firmware is provided as the payload"); self.initramfs = None; } Ok(()) From bcf23c1bf626c7500aacbad4e4f6f1b463476b0b Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 25 Nov 2025 08:01:18 +0000 Subject: [PATCH 0345/1893] ci: Move micro_http to rust-vmm dependabot group This crate depends upon vmm-sys-util which is in this group so it also needs to be in that group. Signed-off-by: Rob Bradford --- .github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2c6136600e..781912648b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,6 +11,7 @@ updates: - dependency-name: "kvm-bindings" - dependency-name: "kvm-ioctls" - dependency-name: "linux-loader" + - dependency-name: "micro_http" - dependency-name: "mshv-bindings" - dependency-name: "mshv-ioctls" - dependency-name: "seccompiler" @@ -41,6 +42,7 @@ updates: - dependency-name: "kvm-bindings" - dependency-name: "kvm-ioctls" - dependency-name: "linux-loader" + - dependency-name: "micro_http" - dependency-name: "mshv-bindings" - dependency-name: "mshv-ioctls" - dependency-name: "seccompiler" From 4e93f85ab19c43c5e1cb19593646dfa5a75f27d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:22:26 +0000 Subject: [PATCH 0346/1893] build: Bump the non-rust-vmm group across 2 directories with 38 updates Bumps the non-rust-vmm group with 28 updates in the / directory: | Package | From | To | | --- | --- | --- | | [zbus](https://github.com/z-galaxy/zbus) | `5.11.0` | `5.12.0` | | [serde_with](https://github.com/jonasbb/serde_with) | `3.15.0` | `3.16.0` | | [bitflags](https://github.com/bitflags/bitflags) | `2.9.4` | `2.10.0` | | [cfg-if](https://github.com/rust-lang/cfg-if) | `1.0.3` | `1.0.4` | | [clap](https://github.com/clap-rs/clap) | `4.5.49` | `4.5.53` | | [zerocopy](https://github.com/google/zerocopy) | `0.8.27` | `0.8.30` | | [io-uring](https://github.com/tokio-rs/io-uring) | `0.7.10` | `0.7.11` | | [num_enum](https://github.com/illicitonion/num_enum) | `0.7.4` | `0.7.5` | | [getrandom](https://github.com/rust-random/getrandom) | `0.3.3` | `0.3.4` | | [landlock](https://github.com/landlock-lsm/rust-landlock) | `0.4.3` | `0.4.4` | | [aho-corasick](https://github.com/BurntSushi/aho-corasick) | `1.1.3` | `1.1.4` | | [anstyle-query](https://github.com/rust-cli/anstyle) | `1.1.4` | `1.1.5` | | [anstyle-wincon](https://github.com/rust-cli/anstyle) | `3.0.10` | `3.0.11` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.41` | `1.2.47` | | [hashbrown](https://github.com/rust-lang/hashbrown) | `0.16.0` | `0.16.1` | | [indexmap](https://github.com/indexmap-rs/indexmap) | `2.11.4` | `2.12.1` | | [is_terminal_polyfill](https://github.com/polyfill-rs/is_terminal_polyfill) | `1.70.1` | `1.70.2` | | [jiff](https://github.com/BurntSushi/jiff) | `0.2.15` | `0.2.16` | | [libz-sys](https://github.com/rust-lang/libz-sys) | `1.1.22` | `1.1.23` | | [once_cell_polyfill](https://github.com/polyfill-rs/once_cell_polyfill) | `1.70.1` | `1.70.2` | | [openssl-src](https://github.com/alexcrichton/openssl-src-rs) | `300.5.3+3.5.4` | `300.5.4+3.5.4` | | [openssl-sys](https://github.com/rust-openssl/rust-openssl) | `0.9.109` | `0.9.111` | | [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.101` | `1.0.103` | | [quote](https://github.com/dtolnay/quote) | `1.0.41` | `1.0.42` | | [signal-hook-registry](https://github.com/vorner/signal-hook) | `1.4.6` | `1.4.7` | | [syn](https://github.com/dtolnay/syn) | `2.0.106` | `2.0.111` | | [unicode-ident](https://github.com/dtolnay/unicode-ident) | `1.0.19` | `1.0.22` | | [zvariant](https://github.com/dbus2/zbus) | `5.7.0` | `5.8.0` | Bumps the non-rust-vmm group with 20 updates in the /fuzz directory: | Package | From | To | | --- | --- | --- | | [serde_with](https://github.com/jonasbb/serde_with) | `3.15.0` | `3.16.0` | | [bitflags](https://github.com/bitflags/bitflags) | `2.9.4` | `2.10.0` | | [cfg-if](https://github.com/rust-lang/cfg-if) | `1.0.3` | `1.0.4` | | [clap](https://github.com/clap-rs/clap) | `4.5.49` | `4.5.53` | | [zerocopy](https://github.com/google/zerocopy) | `0.8.27` | `0.8.30` | | [num_enum](https://github.com/illicitonion/num_enum) | `0.7.4` | `0.7.5` | | [landlock](https://github.com/landlock-lsm/rust-landlock) | `0.4.3` | `0.4.4` | | [anstyle-query](https://github.com/rust-cli/anstyle) | `1.1.4` | `1.1.5` | | [anstyle-wincon](https://github.com/rust-cli/anstyle) | `3.0.10` | `3.0.11` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.41` | `1.2.47` | | [hashbrown](https://github.com/rust-lang/hashbrown) | `0.16.0` | `0.16.1` | | [indexmap](https://github.com/indexmap-rs/indexmap) | `2.11.4` | `2.12.1` | | [is_terminal_polyfill](https://github.com/polyfill-rs/is_terminal_polyfill) | `1.70.1` | `1.70.2` | | [once_cell_polyfill](https://github.com/polyfill-rs/once_cell_polyfill) | `1.70.1` | `1.70.2` | | [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.101` | `1.0.103` | | [quote](https://github.com/dtolnay/quote) | `1.0.41` | `1.0.42` | | [signal-hook-registry](https://github.com/vorner/signal-hook) | `1.4.6` | `1.4.7` | | [syn](https://github.com/dtolnay/syn) | `2.0.106` | `2.0.111` | | [unicode-ident](https://github.com/dtolnay/unicode-ident) | `1.0.19` | `1.0.22` | | [windows-sys](https://github.com/microsoft/windows-rs) | `0.60.2` | `0.61.2` | Updates `zbus` from 5.11.0 to 5.12.0 - [Release notes](https://github.com/z-galaxy/zbus/releases) - [Commits](https://github.com/z-galaxy/zbus/compare/zbus-5.11.0...zbus-5.12.0) Updates `serde_with` from 3.15.0 to 3.16.0 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.15.0...v3.16.0) Updates `bitflags` from 2.9.4 to 2.10.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.9.4...2.10.0) Updates `cfg-if` from 1.0.3 to 1.0.4 - [Release notes](https://github.com/rust-lang/cfg-if/releases) - [Changelog](https://github.com/rust-lang/cfg-if/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/cfg-if/compare/v1.0.3...v1.0.4) Updates `clap` from 4.5.49 to 4.5.53 - [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.49...clap_complete-v4.5.53) Updates `zerocopy` from 0.8.27 to 0.8.30 - [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.27...v0.8.30) Updates `io-uring` from 0.7.10 to 0.7.11 - [Commits](https://github.com/tokio-rs/io-uring/commits/v0.7.11) Updates `num_enum` from 0.7.4 to 0.7.5 - [Commits](https://github.com/illicitonion/num_enum/compare/0.7.4...0.7.5) Updates `getrandom` from 0.3.3 to 0.3.4 - [Release notes](https://github.com/rust-random/getrandom/releases) - [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-random/getrandom/compare/v0.3.3...v0.3.4) Updates `landlock` from 0.4.3 to 0.4.4 - [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.3...v0.4.4) Updates `aho-corasick` from 1.1.3 to 1.1.4 - [Commits](https://github.com/BurntSushi/aho-corasick/compare/1.1.3...1.1.4) Updates `anstyle-query` from 1.1.4 to 1.1.5 - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-query-v1.1.4...anstyle-query-v1.1.5) Updates `anstyle-wincon` from 3.0.10 to 3.0.11 - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-wincon-v3.0.10...anstyle-wincon-v3.0.11) Updates `cc` from 1.2.41 to 1.2.47 - [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.41...cc-v1.2.47) Updates `clap_builder` from 4.5.49 to 4.5.53 - [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.49...v4.5.53) Updates `find-msvc-tools` from 0.1.4 to 0.1.5 - [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.4...find-msvc-tools-v0.1.5) Updates `hashbrown` from 0.16.0 to 0.16.1 - [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.0...v0.16.1) Updates `indexmap` from 2.11.4 to 2.12.1 - [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.11.4...2.12.1) Updates `is_terminal_polyfill` from 1.70.1 to 1.70.2 - [Changelog](https://github.com/polyfill-rs/is_terminal_polyfill/blob/main-v1.70/CHANGELOG.md) - [Commits](https://github.com/polyfill-rs/is_terminal_polyfill/compare/v1.70.1...v1.70.2) Updates `jiff` from 0.2.15 to 0.2.16 - [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.15...jiff-static-0.2.16) Updates `jiff-static` from 0.2.15 to 0.2.16 - [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.15...jiff-static-0.2.16) Updates `libz-sys` from 1.1.22 to 1.1.23 - [Release notes](https://github.com/rust-lang/libz-sys/releases) - [Commits](https://github.com/rust-lang/libz-sys/compare/1.1.22...1.1.23) Updates `num_enum_derive` from 0.7.4 to 0.7.5 - [Commits](https://github.com/illicitonion/num_enum/compare/0.7.4...0.7.5) Updates `once_cell_polyfill` from 1.70.1 to 1.70.2 - [Changelog](https://github.com/polyfill-rs/once_cell_polyfill/blob/v1.70.2/CHANGELOG.md) - [Commits](https://github.com/polyfill-rs/once_cell_polyfill/compare/v1.70.1...v1.70.2) Updates `openssl-src` from 300.5.3+3.5.4 to 300.5.4+3.5.4 - [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.109 to 0.9.111 - [Release notes](https://github.com/rust-openssl/rust-openssl/releases) - [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.109...openssl-sys-v0.9.111) Updates `proc-macro2` from 1.0.101 to 1.0.103 - [Release notes](https://github.com/dtolnay/proc-macro2/releases) - [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.101...1.0.103) Updates `quote` from 1.0.41 to 1.0.42 - [Release notes](https://github.com/dtolnay/quote/releases) - [Commits](https://github.com/dtolnay/quote/compare/1.0.41...1.0.42) Updates `serde_with_macros` from 3.15.0 to 3.16.0 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.15.0...v3.16.0) Updates `signal-hook-registry` from 1.4.6 to 1.4.7 - [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md) - [Commits](https://github.com/vorner/signal-hook/compare/registry-v1.4.6...registry-v1.4.7) Updates `syn` from 2.0.106 to 2.0.111 - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.106...2.0.111) Updates `unicode-ident` from 1.0.19 to 1.0.22 - [Release notes](https://github.com/dtolnay/unicode-ident/releases) - [Commits](https://github.com/dtolnay/unicode-ident/compare/1.0.19...1.0.22) Updates `zbus_macros` from 5.11.0 to 5.12.0 - [Release notes](https://github.com/z-galaxy/zbus/releases) - [Commits](https://github.com/z-galaxy/zbus/compare/zbus-5.11.0...zbus-5.12.0) Updates `zerocopy-derive` from 0.8.27 to 0.8.30 - [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.27...v0.8.30) Updates `zvariant` from 5.7.0 to 5.8.0 - [Release notes](https://github.com/dbus2/zbus/releases) - [Commits](https://github.com/dbus2/zbus/compare/zvariant-5.7.0...zvariant-5.8.0) Updates `zvariant_derive` from 5.7.0 to 5.8.0 - [Release notes](https://github.com/dbus2/zbus/releases) - [Commits](https://github.com/dbus2/zbus/compare/zbus-5.7.0...zbus-5.8.0) Updates `serde_with` from 3.15.0 to 3.16.0 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.15.0...v3.16.0) Updates `bitflags` from 2.9.4 to 2.10.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.9.4...2.10.0) Updates `cfg-if` from 1.0.3 to 1.0.4 - [Release notes](https://github.com/rust-lang/cfg-if/releases) - [Changelog](https://github.com/rust-lang/cfg-if/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/cfg-if/compare/v1.0.3...v1.0.4) Updates `clap` from 4.5.49 to 4.5.53 - [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.49...clap_complete-v4.5.53) Updates `zerocopy` from 0.8.27 to 0.8.30 - [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.27...v0.8.30) Updates `bitfield-struct` from 0.11.0 to 0.12.1 - [Release notes](https://github.com/wrenger/bitfield-struct-rs/releases) - [Commits](https://github.com/wrenger/bitfield-struct-rs/compare/0.11.0...0.12.1) Updates `num_enum` from 0.7.4 to 0.7.5 - [Commits](https://github.com/illicitonion/num_enum/compare/0.7.4...0.7.5) Updates `landlock` from 0.4.3 to 0.4.4 - [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.3...v0.4.4) Updates `anstyle-query` from 1.1.4 to 1.1.5 - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-query-v1.1.4...anstyle-query-v1.1.5) Updates `anstyle-wincon` from 3.0.10 to 3.0.11 - [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-wincon-v3.0.10...anstyle-wincon-v3.0.11) Updates `cc` from 1.2.41 to 1.2.47 - [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.41...cc-v1.2.47) Updates `clap_builder` from 4.5.49 to 4.5.53 - [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.49...v4.5.53) Updates `find-msvc-tools` from 0.1.4 to 0.1.5 - [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.4...find-msvc-tools-v0.1.5) Updates `hashbrown` from 0.16.0 to 0.16.1 - [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.0...v0.16.1) Updates `indexmap` from 2.11.4 to 2.12.1 - [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.11.4...2.12.1) Updates `is_terminal_polyfill` from 1.70.1 to 1.70.2 - [Changelog](https://github.com/polyfill-rs/is_terminal_polyfill/blob/main-v1.70/CHANGELOG.md) - [Commits](https://github.com/polyfill-rs/is_terminal_polyfill/compare/v1.70.1...v1.70.2) Updates `num_enum_derive` from 0.7.4 to 0.7.5 - [Commits](https://github.com/illicitonion/num_enum/compare/0.7.4...0.7.5) Updates `once_cell_polyfill` from 1.70.1 to 1.70.2 - [Changelog](https://github.com/polyfill-rs/once_cell_polyfill/blob/v1.70.2/CHANGELOG.md) - [Commits](https://github.com/polyfill-rs/once_cell_polyfill/compare/v1.70.1...v1.70.2) Updates `proc-macro2` from 1.0.101 to 1.0.103 - [Release notes](https://github.com/dtolnay/proc-macro2/releases) - [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.101...1.0.103) Updates `quote` from 1.0.41 to 1.0.42 - [Release notes](https://github.com/dtolnay/quote/releases) - [Commits](https://github.com/dtolnay/quote/compare/1.0.41...1.0.42) Updates `serde_with_macros` from 3.15.0 to 3.16.0 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.15.0...v3.16.0) Updates `signal-hook-registry` from 1.4.6 to 1.4.7 - [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md) - [Commits](https://github.com/vorner/signal-hook/compare/registry-v1.4.6...registry-v1.4.7) Updates `syn` from 2.0.106 to 2.0.111 - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.106...2.0.111) Updates `unicode-ident` from 1.0.19 to 1.0.22 - [Release notes](https://github.com/dtolnay/unicode-ident/releases) - [Commits](https://github.com/dtolnay/unicode-ident/compare/1.0.19...1.0.22) 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 `zerocopy-derive` from 0.8.27 to 0.8.30 - [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.27...v0.8.30) --- updated-dependencies: - dependency-name: zbus dependency-version: 5.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: serde_with dependency-version: 3.16.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: bitflags dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: cfg-if dependency-version: 1.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap dependency-version: 4.5.53 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: zerocopy dependency-version: 0.8.30 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: io-uring dependency-version: 0.7.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: num_enum dependency-version: 0.7.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: getrandom dependency-version: 0.3.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: landlock dependency-version: 0.4.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: aho-corasick dependency-version: 1.1.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle-query dependency-version: 1.1.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle-wincon dependency-version: 3.0.11 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.47 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap_builder dependency-version: 4.5.53 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: find-msvc-tools dependency-version: 0.1.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: hashbrown dependency-version: 0.16.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: indexmap dependency-version: 2.12.1 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: is_terminal_polyfill dependency-version: 1.70.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: jiff dependency-version: 0.2.16 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: jiff-static dependency-version: 0.2.16 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: libz-sys dependency-version: 1.1.23 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: num_enum_derive dependency-version: 0.7.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: once_cell_polyfill dependency-version: 1.70.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: openssl-src dependency-version: 300.5.4+3.5.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: openssl-sys dependency-version: 0.9.111 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: proc-macro2 dependency-version: 1.0.103 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: quote dependency-version: 1.0.42 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with_macros dependency-version: 3.16.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: signal-hook-registry dependency-version: 1.4.7 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: syn dependency-version: 2.0.111 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: unicode-ident dependency-version: 1.0.22 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: zbus_macros dependency-version: 5.12.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: zerocopy-derive dependency-version: 0.8.30 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: zvariant dependency-version: 5.8.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: zvariant_derive dependency-version: 5.8.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: serde_with dependency-version: 3.16.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: bitflags dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: cfg-if dependency-version: 1.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap dependency-version: 4.5.53 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: zerocopy dependency-version: 0.8.30 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: bitfield-struct dependency-version: 0.12.1 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: num_enum dependency-version: 0.7.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: landlock dependency-version: 0.4.4 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle-query dependency-version: 1.1.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: anstyle-wincon dependency-version: 3.0.11 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.47 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: clap_builder dependency-version: 4.5.53 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: find-msvc-tools dependency-version: 0.1.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: hashbrown dependency-version: 0.16.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: indexmap dependency-version: 2.12.1 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: is_terminal_polyfill dependency-version: 1.70.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: num_enum_derive dependency-version: 0.7.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: once_cell_polyfill dependency-version: 1.70.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: proc-macro2 dependency-version: 1.0.103 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: quote dependency-version: 1.0.42 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with_macros dependency-version: 3.16.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: signal-hook-registry dependency-version: 1.4.7 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: syn dependency-version: 2.0.111 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: unicode-ident dependency-version: 1.0.22 dependency-type: indirect update-type: version-update:semver-patch 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: zerocopy-derive dependency-version: 0.8.30 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm ... Signed-off-by: dependabot[bot] --- Cargo.lock | 211 +++++++++++++++++---------------- Cargo.toml | 10 +- block/Cargo.toml | 2 +- devices/Cargo.toml | 4 +- fuzz/Cargo.lock | 278 ++++++++++++++++++++++++-------------------- net_util/Cargo.toml | 2 +- vmm/Cargo.toml | 4 +- 7 files changed, 269 insertions(+), 242 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c85f55023..722d1ce6e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,9 +27,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -66,22 +66,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -311,9 +311,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block" @@ -365,9 +365,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.41" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "jobserver", @@ -377,9 +377,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -389,18 +389,18 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.49" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.49" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -544,7 +544,7 @@ dependencies = [ "anyhow", "arch", "bitfield-struct 0.12.1", - "bitflags 2.9.4", + "bitflags 2.10.0", "byteorder", "event_monitor", "hypervisor", @@ -663,7 +663,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", ] @@ -728,9 +728,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" @@ -868,7 +868,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72742d2b395902caf8a5d520d0dd3334ba6d1138938429200e58d5174e275f3f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "log", "managed", @@ -895,20 +895,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] [[package]] @@ -925,9 +925,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hermit-abi" @@ -1017,9 +1017,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -1027,11 +1027,11 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "libc", ] @@ -1047,9 +1047,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -1068,22 +1068,22 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", @@ -1096,7 +1096,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] @@ -1127,7 +1127,7 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "kvm-bindings", "libc", "vmm-sys-util", @@ -1135,9 +1135,9 @@ dependencies = [ [[package]] name = "landlock" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affe8b77dce5b172f8e290bd801b12832a77cd1942d1ea98259916e89d5829d6" +checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088" dependencies = [ "enumflags2", "libc", @@ -1162,7 +1162,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", ] @@ -1182,9 +1182,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.22" +version = "1.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" dependencies = [ "cc", "libc", @@ -1315,7 +1315,7 @@ name = "net_util" version = "0.1.0" dependencies = [ "epoll", - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", "log", "net_gen", @@ -1338,7 +1338,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", @@ -1362,9 +1362,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1372,9 +1372,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1399,9 +1399,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open-enum" @@ -1425,18 +1425,18 @@ dependencies = [ [[package]] name = "openssl-src" -version = "300.5.3+3.5.4" +version = "300.5.4+3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6bad8cd0233b63971e232cc9c5e83039375b8586d2312f31fda85db8f888c2" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -1705,18 +1705,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -1753,7 +1753,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -1779,7 +1779,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -1851,7 +1851,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -1941,9 +1941,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" dependencies = [ "serde_core", "serde_with_macros", @@ -1951,9 +1951,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" dependencies = [ "darling", "proc-macro2", @@ -1983,9 +1983,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] @@ -2023,7 +2023,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f84d13b3b8a0d4e91a2629911e951db1bb8671512f5c09d7d4ba34500ba68c8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", "libssh2-sys", "parking_lot", @@ -2043,9 +2043,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -2059,7 +2059,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.0", @@ -2231,9 +2231,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "utf8parse" @@ -2247,9 +2247,10 @@ version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", "rand", + "serde", "wasm-bindgen", ] @@ -2293,7 +2294,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", "log", "serde", @@ -2311,7 +2312,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", "uuid", "vm-memory", @@ -2486,7 +2487,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.9.4", + "bitflags 2.10.0", "block", "blocking", "cfg-if", @@ -2565,12 +2566,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -2752,19 +2753,16 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.4", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zbus" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" +checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" dependencies = [ "async-broadcast", "async-executor", @@ -2786,7 +2784,8 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "windows-sys 0.60.2", + "uuid", + "windows-sys 0.61.0", "winnow", "zbus_macros", "zbus_names", @@ -2795,9 +2794,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" +checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2822,18 +2821,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", @@ -2870,9 +2869,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.7.0" +version = "5.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" +checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" dependencies = [ "endi", "enumflags2", @@ -2884,9 +2883,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.7.0" +version = "5.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" +checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 65ff1a30a5..de2bcd9bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -135,14 +135,14 @@ igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates serde = "1.0.228" serde_json = "1.0.145" -serde_with = { version = "3.15.0", default-features = false } +serde_with = { version = "3.16.0", default-features = false } # other crates anyhow = "1.0.99" -bitflags = "2.9.4" +bitflags = "2.10.0" byteorder = "1.5.0" -cfg-if = "1.0.3" -clap = "4.5.49" +cfg-if = "1.0.4" +clap = "4.5.53" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" @@ -155,7 +155,7 @@ signal-hook = "0.3.18" thiserror = "2.0.17" uuid = { version = "1.18.1" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.27", default-features = false } +zerocopy = { version = "0.8.30", default-features = false } [workspace.lints.clippy] # Any clippy lint (group) in alphabetical order: diff --git a/block/Cargo.toml b/block/Cargo.toml index e6ca61bd47..91b3394ef7 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -12,7 +12,7 @@ io_uring = ["dep:io-uring"] byteorder = { workspace = true } crc-any = "2.5.0" flate2 = "1.0" -io-uring = { version = "0.7.10", optional = true } +io-uring = { version = "0.7.11", optional = true } libc = { workspace = true } log = { workspace = true } remain = "0.2.15" diff --git a/devices/Cargo.toml b/devices/Cargo.toml index f635e66a49..882824fd2c 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -20,7 +20,7 @@ linux-loader = { workspace = true, features = [ "pe", ], optional = true } log = { workspace = true } -num_enum = "0.7.4" +num_enum = "0.7.5" pci = { path = "../pci" } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } @@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.27", features = [ +zerocopy = { version = "0.8.30", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 8c0a8eae76..ee1692f068 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -10,6 +10,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "anstream" version = "0.6.21" @@ -42,18 +48,18 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", @@ -105,9 +111,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitfield-struct" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8" +checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" dependencies = [ "proc-macro2", "quote", @@ -122,9 +128,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block" @@ -132,6 +138,7 @@ version = "0.1.0" dependencies = [ "byteorder", "crc-any", + "flate2", "libc", "log", "remain", @@ -144,6 +151,7 @@ dependencies = [ "vm-memory", "vm-virtio", "vmm-sys-util", + "zstd", ] [[package]] @@ -160,9 +168,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.41" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "jobserver", @@ -172,24 +180,24 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.49" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.49" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -254,6 +262,15 @@ dependencies = [ "debug-helper", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "darling" version = "0.21.3" @@ -302,7 +319,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.9.4", + "bitflags 2.10.0", "byteorder", "event_monitor", "hypervisor", @@ -320,6 +337,12 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "enumflags2" version = "0.7.12" @@ -346,7 +369,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", ] @@ -374,9 +397,19 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] [[package]] name = "flume" @@ -414,7 +447,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72742d2b395902caf8a5d520d0dd3334ba6d1138938429200e58d5174e275f3f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "log", "managed", @@ -459,9 +492,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hypervisor" @@ -507,9 +540,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -517,9 +550,18 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] [[package]] name = "itoa" @@ -564,7 +606,7 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "kvm-bindings", "libc", "vmm-sys-util", @@ -572,9 +614,9 @@ dependencies = [ [[package]] name = "landlock" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affe8b77dce5b172f8e290bd801b12832a77cd1942d1ea98259916e89d5829d6" +checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088" dependencies = [ "enumflags2", "libc", @@ -648,6 +690,16 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mshv-bindings" version = "0.6.0" @@ -708,9 +760,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -718,9 +770,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -736,9 +788,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open-enum" @@ -794,6 +846,12 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -814,18 +872,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -959,9 +1017,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" dependencies = [ "serde_core", "serde_with_macros", @@ -969,9 +1027,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" dependencies = [ "darling", "proc-macro2", @@ -1001,13 +1059,19 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "smallvec" version = "1.15.1" @@ -1031,9 +1095,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -1134,9 +1198,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "utf8parse" @@ -1188,7 +1252,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", "log", "serde", @@ -1206,7 +1270,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", "uuid", "vm-memory", @@ -1305,6 +1369,7 @@ name = "vm-migration" version = "0.1.0" dependencies = [ "anyhow", + "itertools", "serde", "serde_json", "thiserror 2.0.17", @@ -1326,7 +1391,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.9.4", + "bitflags 2.10.0", "block", "cfg-if", "clap", @@ -1483,78 +1548,13 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.60.2" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.7.13" @@ -1570,25 +1570,53 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml index a55db49f8a..9d800419b0 100644 --- a/net_util/Cargo.toml +++ b/net_util/Cargo.toml @@ -6,7 +6,7 @@ version = "0.1.0" [dependencies] epoll = { workspace = true } -getrandom = "0.3.3" +getrandom = "0.3.4" libc = { workspace = true } log = { workspace = true } net_gen = { path = "../net_gen" } diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 37434f215c..a2fadfbd82 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -54,7 +54,7 @@ hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } igvm = { workspace = true, optional = true } igvm_defs = { workspace = true, optional = true } -landlock = "0.4.3" +landlock = "0.4.4" libc = { workspace = true } linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] } log = { workspace = true } @@ -91,7 +91,7 @@ vm-memory = { workspace = true, features = [ vm-migration = { path = "../vm-migration" } vm-virtio = { path = "../vm-virtio" } vmm-sys-util = { workspace = true, features = ["with-serde"] } -zbus = { version = "5.11.0", optional = true } +zbus = { version = "5.12.0", optional = true } zerocopy = { workspace = true, features = ["alloc", "derive"] } [lints] From 28b54df01d900d9103685f026b77ddd9c9b02d5e Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Nov 2025 13:16:03 +0100 Subject: [PATCH 0347/1893] vmm: cleanup: replace map_err misuse with inspect_err This is continuing the work from [0]. Some places in CHV "misuse" Result::map_err for side-effects. inspect_err is a better suited alternative for exactly that use-case. Unfortunately, there is no clippy lint for this yet. [0] f02745a7ed804738c46404e6e479b69e8a36baeb Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index e8c07dba55..6da66b36db 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -444,10 +444,9 @@ pub fn start_event_monitor_thread( if !seccomp_filter.is_empty() { apply_filter(&seccomp_filter) .map_err(Error::ApplySeccompFilter) - .map_err(|e| { + .inspect_err(|e| { error!("Error applying seccomp filter: {e:?}"); exit_event.write(1).ok(); - e })?; } if landlock_enable { @@ -455,10 +454,9 @@ pub fn start_event_monitor_thread( .map_err(Error::CreateLandlock)? .restrict_self() .map_err(Error::ApplyLandlock) - .map_err(|e| { + .inspect_err(|e| { error!("Error applying landlock to event monitor thread: {e:?}"); exit_event.write(1).ok(); - e })?; } @@ -1941,9 +1939,8 @@ impl RequestHandler for Vmm { } if let Some(ref mut vm) = self.vm { - let info = vm.add_disk(disk_cfg).map_err(|e| { + let info = vm.add_disk(disk_cfg).inspect_err(|e| { error!("Error when adding new disk to the VM: {e:?}"); - e })?; serde_json::to_vec(&info) .map(Some) @@ -1967,9 +1964,8 @@ impl RequestHandler for Vmm { } if let Some(ref mut vm) = self.vm { - let info = vm.add_fs(fs_cfg).map_err(|e| { + let info = vm.add_fs(fs_cfg).inspect_err(|e| { error!("Error when adding new fs to the VM: {e:?}"); - e })?; serde_json::to_vec(&info) .map(Some) @@ -1993,9 +1989,8 @@ impl RequestHandler for Vmm { } if let Some(ref mut vm) = self.vm { - let info = vm.add_pmem(pmem_cfg).map_err(|e| { + let info = vm.add_pmem(pmem_cfg).inspect_err(|e| { error!("Error when adding new pmem device to the VM: {e:?}"); - e })?; serde_json::to_vec(&info) .map(Some) @@ -2019,9 +2014,8 @@ impl RequestHandler for Vmm { } if let Some(ref mut vm) = self.vm { - let info = vm.add_net(net_cfg).map_err(|e| { + let info = vm.add_net(net_cfg).inspect_err(|e| { error!("Error when adding new network device to the VM: {e:?}"); - e })?; serde_json::to_vec(&info) .map(Some) @@ -2045,9 +2039,8 @@ impl RequestHandler for Vmm { } if let Some(ref mut vm) = self.vm { - let info = vm.add_vdpa(vdpa_cfg).map_err(|e| { + let info = vm.add_vdpa(vdpa_cfg).inspect_err(|e| { error!("Error when adding new vDPA device to the VM: {e:?}"); - e })?; serde_json::to_vec(&info) .map(Some) @@ -2076,9 +2069,8 @@ impl RequestHandler for Vmm { } if let Some(ref mut vm) = self.vm { - let info = vm.add_vsock(vsock_cfg).map_err(|e| { + let info = vm.add_vsock(vsock_cfg).inspect_err(|e| { error!("Error when adding new vsock device to the VM: {e:?}"); - e })?; serde_json::to_vec(&info) .map(Some) @@ -2093,9 +2085,8 @@ impl RequestHandler for Vmm { fn vm_counters(&mut self) -> result::Result>, VmError> { if let Some(ref mut vm) = self.vm { - let info = vm.counters().map_err(|e| { + let info = vm.counters().inspect_err(|e| { error!("Error when getting counters from the VM: {e:?}"); - e })?; serde_json::to_vec(&info) .map(Some) From 7443a9f69e39d1655b63195d426f52790b8d110b Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 11 Nov 2025 14:21:11 +0100 Subject: [PATCH 0348/1893] misc: fix typo and wording Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vm-migration/src/lib.rs | 2 +- vmm/src/api/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 7532026743..16c0a6df87 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -50,7 +50,7 @@ pub enum MigratableError { #[error("Failed to complete migration for migratable component")] CompleteMigration(#[source] anyhow::Error), - #[error("Failed to release a disk lock before the migration")] + #[error("Failed to release a disk lock")] UnlockError(#[source] anyhow::Error), } diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 96c99f3fce..694eb961be 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -118,8 +118,8 @@ pub enum ApiError { #[error("The VM could not be snapshotted")] VmSnapshot(#[source] VmError), - /// The VM could not restored. - #[error("The VM could not restored")] + /// The VM could not be restored. + #[error("The VM could not be restored")] VmRestore(#[source] VmError), /// The VM could not be coredumped. From 8c5045000276cfd25a77abbcb3671f07634041c1 Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Wed, 29 Oct 2025 14:32:22 +0100 Subject: [PATCH 0349/1893] vmm: don't needlessly clone strings On-behalf-of: SAP julian.stecklina@sap.com Signed-off-by: Julian Stecklina --- vmm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 6da66b36db..cb1ce9d54e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1623,7 +1623,7 @@ impl RequestHandler for Vmm { for net in restored_nets.iter() { for net_config in vm_net_configs.iter_mut() { // update only if the net dev is backed by FDs - if net_config.id == Some(net.id.clone()) && net_config.fds.is_some() { + if net_config.id.as_ref() == Some(&net.id) && net_config.fds.is_some() { net_config.fds.clone_from(&net.fds); } } From a1a018bd830e3f6835b7c715d1a0bb654c8f43bb Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 25 Nov 2025 05:12:23 +0000 Subject: [PATCH 0350/1893] tests: Do not return a failure when no tests are run It is a common use case to run a subset of tests locally to verify certain functionalities. The default behaviour for nextest is to error out if no tests are run. That causes the test scripts to return a non-zero value (failure). Pass `--no-tests=pass` to nextest to match what `cargo test` does if no tests are run. Signed-off-by: Wei Liu --- scripts/run_integration_tests_aarch64.sh | 16 ++++++++-------- scripts/run_integration_tests_live_migration.sh | 4 ++-- scripts/run_integration_tests_rate_limiter.sh | 2 +- scripts/run_integration_tests_vfio.sh | 2 +- scripts/run_integration_tests_windows_aarch64.sh | 2 +- scripts/run_integration_tests_windows_x86_64.sh | 2 +- scripts/run_integration_tests_x86_64.sh | 10 +++++----- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 9887013cb7..a21a282baf 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -228,13 +228,13 @@ echo "$PAGE_NUM" | sudo tee /proc/sys/vm/nr_hugepages sudo chmod a+rwX /dev/hugepages # Run all direct kernel boot (Device Tree) test cases in mod `parallel` -time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "common_parallel::$test_filter" -- ${test_binary_args[*]} +time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "common_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? # Run some tests in sequence since the result could be affected by other tests # running in parallel. if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=1 "common_sequential::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=1 "common_sequential::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES @@ -242,7 +242,7 @@ fi # Run all ACPI test cases if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "aarch64_acpi::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "aarch64_acpi::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES @@ -250,14 +250,14 @@ fi # Run all test cases related to live migration if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES fi if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES @@ -268,7 +268,7 @@ if [ $RES -eq 0 ]; then cargo build --features "dbus_api" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 # integration tests now do not reply on build feature "dbus_api" - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} RES=$? fi @@ -276,14 +276,14 @@ fi if [ $RES -eq 0 ]; then cargo build --features "fw_cfg" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} RES=$? fi if [ $RES -eq 0 ]; then cargo build --features "ivshmem" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} RES=$? fi diff --git a/scripts/run_integration_tests_live_migration.sh b/scripts/run_integration_tests_live_migration.sh index 9d4af8453d..f88beed65a 100755 --- a/scripts/run_integration_tests_live_migration.sh +++ b/scripts/run_integration_tests_live_migration.sh @@ -87,7 +87,7 @@ sudo chmod a+rwX /dev/hugepages export RUST_BACKTRACE=1 export RUSTFLAGS="$RUSTFLAGS" -time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} +time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? @@ -95,7 +95,7 @@ RES=$? # running in parallel. if [ $RES -eq 0 ]; then export RUST_BACKTRACE=1 - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} RES=$? fi diff --git a/scripts/run_integration_tests_rate_limiter.sh b/scripts/run_integration_tests_rate_limiter.sh index 9582ddcb2d..e4e627426b 100755 --- a/scripts/run_integration_tests_rate_limiter.sh +++ b/scripts/run_integration_tests_rate_limiter.sh @@ -59,7 +59,7 @@ cargo build --features mshv --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 export RUSTFLAGS="$RUSTFLAGS" -time cargo nextest run $test_features --test-threads=1 "rate_limiter::$test_filter" -- ${test_binary_args[*]} +time cargo nextest run --no-tests=pass $test_features --test-threads=1 "rate_limiter::$test_filter" -- ${test_binary_args[*]} RES=$? exit $RES diff --git a/scripts/run_integration_tests_vfio.sh b/scripts/run_integration_tests_vfio.sh index a88f92aad1..b32afe5a23 100755 --- a/scripts/run_integration_tests_vfio.sh +++ b/scripts/run_integration_tests_vfio.sh @@ -30,7 +30,7 @@ cargo build --features mshv --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 export RUSTFLAGS="$RUSTFLAGS" -time cargo nextest run --test-threads=1 "vfio::test_nvidia" -- ${test_binary_args[*]} +time cargo nextest run --no-tests=pass --test-threads=1 "vfio::test_nvidia" -- ${test_binary_args[*]} RES=$? exit $RES diff --git a/scripts/run_integration_tests_windows_aarch64.sh b/scripts/run_integration_tests_windows_aarch64.sh index 7112609e6e..8f12a2740a 100755 --- a/scripts/run_integration_tests_windows_aarch64.sh +++ b/scripts/run_integration_tests_windows_aarch64.sh @@ -44,7 +44,7 @@ cargo build --all --release --target "$BUILD_TARGET" # Only run with 1 thread to avoid tests interfering with one another because # Windows has a static IP configured -time cargo nextest run "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} +time cargo nextest run --no-tests=pass "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} RES=$? dmsetup remove_all -f diff --git a/scripts/run_integration_tests_windows_x86_64.sh b/scripts/run_integration_tests_windows_x86_64.sh index c95e513059..d8f6861497 100755 --- a/scripts/run_integration_tests_windows_x86_64.sh +++ b/scripts/run_integration_tests_windows_x86_64.sh @@ -47,7 +47,7 @@ export RUSTFLAGS="$RUSTFLAGS" # Only run with 1 thread to avoid tests interfering with one another because # Windows has a static IP configured -time cargo nextest run $test_features "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} +time cargo nextest run --no-tests=pass $test_features "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} RES=$? dmsetup remove_all -f diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 4370c15ace..68ab92ee2d 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -201,13 +201,13 @@ ulimit -n 4096 export RUST_BACKTRACE=1 export RUSTFLAGS="$RUSTFLAGS" -time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "common_parallel::$test_filter" -- ${test_binary_args[*]} +time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "common_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? # Run some tests in sequence since the result could be affected by other tests # running in parallel. if [ $RES -eq 0 ]; then - cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=1 "common_sequential::$test_filter" -- ${test_binary_args[*]} + cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=1 "common_sequential::$test_filter" -- ${test_binary_args[*]} RES=$? fi @@ -215,20 +215,20 @@ fi if [ $RES -eq 0 ]; then cargo build --features "mshv,dbus_api" --all --release --target "$BUILD_TARGET" # integration tests now do not reply on build feature "dbus_api" - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} RES=$? fi # Run tests on fw_cfg if [ $RES -eq 0 ]; then cargo build --features "mshv,fw_cfg" --all --release --target "$BUILD_TARGET" - time cargo nextest run $test_features --no-tests=warn --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} RES=$? fi if [ $RES -eq 0 ]; then cargo build --features "mshv,ivshmem" --all --release --target "$BUILD_TARGET" - time cargo nextest run $test_features --retries 3 --no-fail-fast --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} RES=$? fi From dda89d70273470bb71e102c6b912d3cc17f7260f Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 25 Nov 2025 04:27:52 +0000 Subject: [PATCH 0351/1893] tests: Check disk consistency after use We've encountered issues that Cloud Hypervisor corrupts disk images after use. Those issues may not be immediately obvious until the corrupted images are used again. Run consistency checks over the disk images in the test cases to catch issues as early as possible. Signed-off-by: Wei Liu --- tests/integration.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 468ab36b71..845121635c 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3489,24 +3489,59 @@ mod common_parallel { _test_virtio_block(FOCAL_IMAGE_NAME, true, true); } + /// Uses `qemu-img check` to verify disk image consistency. + /// + /// Supported formats are `qcow2` (compressed and uncompressed), + /// `vhdx`, `qed`, `parallels`, `vmdk`, and `vdi`. See man page + /// for more details. + /// + /// It takes either a full path to the image or just the name of + /// the image located in the `workloads` directory. + fn disk_check_consistency(path_or_image_name: impl AsRef) { + let path = if path_or_image_name.as_ref().exists() { + // A full path is provided + path_or_image_name.as_ref().to_path_buf() + } else { + // An image name is provided + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + workload_path.as_path().join(path_or_image_name.as_ref()) + }; + + let output = std::process::Command::new("qemu-img") + .args(["check", path.to_str().unwrap()]) + .output() + .expect("should spawn and run command successfully"); + + assert!( + output.status.success(), + "qemu-img check failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + #[test] fn test_virtio_block_qcow2() { _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2, false, false); + disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2); } #[test] fn test_virtio_block_qcow2_zlib() { _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZLIB, false, false); + disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_ZLIB); } #[test] fn test_virtio_block_qcow2_zstd() { _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZSTD, false, false); + disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_ZSTD); } #[test] fn test_virtio_block_qcow2_backing_file() { _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_FILE, false, false); + disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_BACKING_FILE); } #[test] @@ -3556,6 +3591,7 @@ mod common_parallel { .expect("Expect generating dynamic VHDx image from RAW image"); _test_virtio_block(FOCAL_IMAGE_NAME_VHDX, false, false); + disk_check_consistency(FOCAL_IMAGE_NAME_VHDX); } #[test] @@ -3635,6 +3671,8 @@ mod common_parallel { let output = cloud_child.wait_with_output().unwrap(); handle_child_output(r, &output); + + disk_check_consistency(vhdx_path); } fn vhdx_image_size(disk_name: &str) -> u64 { From d2b19bb969ffa66bdab8f76c2d28211c25b1d128 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 12:28:43 +0100 Subject: [PATCH 0352/1893] misc: clippy: add map_unwrap_or Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 1 + devices/src/pvmemcontrol.rs | 6 ++++-- src/bin/ch-remote.rs | 2 +- virtio-devices/src/block.rs | 19 ++++++++----------- vmm/src/config.rs | 9 ++++----- vmm/src/device_manager.rs | 3 +-- vmm/src/sigwinch_listener.rs | 2 +- vmm/src/vm.rs | 6 ++---- 8 files changed, 22 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de2bcd9bc2..26182ebcaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,6 +172,7 @@ suspicious = "deny" # Individual Lints assertions_on_result_states = "deny" manual_string_new = "deny" +map_unwrap_or = "deny" semicolon_if_nothing_returned = "deny" undocumented_unsafe_blocks = "deny" uninlined_format_args = "deny" diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index d7fd7ff3b3..06e0e24923 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -648,8 +648,10 @@ impl PvmemcontrolBusDevice { .find_connection(conn) .ok_or(Error::InvalidConnection(conn.command)) }) - .map(|gpa| self.handle_pvmemcontrol_request(gpa)) - .unwrap_or_else(|err| warn!("{err:?}")); + .map_or_else( + |err| warn!("{err:?}"), + |gpa| self.handle_pvmemcontrol_request(gpa), + ); } } } diff --git a/src/bin/ch-remote.rs b/src/bin/ch-remote.rs index 13af148bd0..8d89b607b9 100644 --- a/src/bin/ch-remote.rs +++ b/src/bin/ch-remote.rs @@ -1161,7 +1161,7 @@ fn main() { if let Some(api_client::Error::ServerResponse(status_code, body)) = error.downcast_ref::() { - let body = body.as_ref().map(|body| body.as_str()).unwrap_or(""); + let body = body.as_ref().map_or("", |body| body.as_str()); // Retrieve the list of error messages back. let lines: Vec<&str> = match serde_json::from_str(body) { diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 37488a82a0..93c07763e4 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -735,9 +735,7 @@ impl Block { (disk_nsectors, avail_features, 0, config, false) }; - let serial = serial - .map(Vec::from) - .unwrap_or_else(|| build_serial(&disk_path)); + let serial = serial.map_or_else(|| build_serial(&disk_path), Vec::from); Ok(Block { common: VirtioCommon { @@ -773,21 +771,20 @@ impl Block { // TODO In future, we could add a `lock_granularity=` configuration to the CLI. // For now, we stick to QEMU behavior. fn lock_granularity(&mut self) -> LockGranularity { - let fallback = LockGranularity::WholeFile; - - self.disk_image - .size() - .map(|size| LockGranularity::ByteRange(0, size)) + self.disk_image.size().map_or_else( // use a safe fallback - .unwrap_or_else(|e| { - log::warn!( + |e| { + let fallback = LockGranularity::WholeFile; + warn!( "Can't get disk size for id={},path={}, falling back to {:?}: error: {e}", self.id, self.disk_path.display(), fallback ); fallback - }) + }, + |size| LockGranularity::ByteRange(0, size), + ) } /// Tries to set an advisory lock for the corresponding disk image. diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 5c04e6083f..292cd275a0 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1562,8 +1562,7 @@ impl BalloonConfig { let size = parser .convert::("size") .map_err(Error::ParseBalloon)? - .map(|v| v.0) - .unwrap_or(0); + .map_or(0, |v| v.0); let deflate_on_oom = parser .convert::("deflate_on_oom") @@ -2482,7 +2481,7 @@ impl VmConfig { #[cfg(feature = "tdx")] { - let tdx_enabled = self.platform.as_ref().map(|p| p.tdx).unwrap_or(false); + let tdx_enabled = self.platform.as_ref().is_some_and(|p| p.tdx); // At this point we know payload isn't None. if tdx_enabled && self.payload.as_ref().unwrap().firmware.is_none() { return Err(ValidationError::TdxFirmwareMissing); @@ -3110,12 +3109,12 @@ impl VmConfig { #[cfg(feature = "tdx")] pub fn is_tdx_enabled(&self) -> bool { - self.platform.as_ref().map(|p| p.tdx).unwrap_or(false) + self.platform.as_ref().is_some_and(|p| p.tdx) } #[cfg(feature = "sev_snp")] pub fn is_sev_snp_enabled(&self) -> bool { - self.platform.as_ref().map(|p| p.sev_snp).unwrap_or(false) + self.platform.as_ref().is_some_and(|p| p.sev_snp) } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 1883e0d7b6..727c5b504d 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2113,8 +2113,7 @@ impl DeviceManager { .debug_console .clone() .iobase - .map(|port| port as u64) - .unwrap_or(debug_console::DEFAULT_PORT); + .map_or(debug_console::DEFAULT_PORT, |port| port as u64); self.bus_devices .push(Arc::clone(&debug_console) as Arc); diff --git a/vmm/src/sigwinch_listener.rs b/vmm/src/sigwinch_listener.rs index b50e93c986..970e82fcf4 100644 --- a/vmm/src/sigwinch_listener.rs +++ b/vmm/src/sigwinch_listener.rs @@ -104,7 +104,7 @@ unsafe fn close_unused_fds(keep_fds: &mut [RawFd]) { // The next fd is the one at i, because the indexes in the // iterator are offset by one due to the initial 0. let next_keep_fd = keep_fds.get(i); - let last = next_keep_fd.map(|fd| fd - 1).unwrap_or(RawFd::MAX); + let last = next_keep_fd.map_or(RawFd::MAX, |fd| fd - 1); if first > last { continue; diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index fdaa778024..f42fe2406b 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -763,8 +763,7 @@ impl Vm { .unwrap() .payload .as_ref() - .map(|p| p.fw_cfg_config.is_some()) - .unwrap_or(false); + .is_some_and(|p| p.fw_cfg_config.is_some()); if fw_cfg_config { device_manager .lock() @@ -2338,8 +2337,7 @@ impl Vm { .unwrap() .payload .as_ref() - .map(|p| p.fw_cfg_config.is_some()) - .unwrap_or(false); + .is_some_and(|p| p.fw_cfg_config.is_some()); if fw_cfg_enabled { let fw_cfg_config = self .config From a0b72dce2280ae4264c2091388d154e6aceafc42 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 18 Nov 2025 12:30:49 +0100 Subject: [PATCH 0353/1893] misc: clippy: add redundant_else Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 1 + hypervisor/src/kvm/mod.rs | 3 +-- hypervisor/src/mshv/mod.rs | 3 +-- pci/src/configuration.rs | 9 +++---- pci/src/vfio.rs | 17 ++++++------- rate_limiter/src/group.rs | 3 +-- rate_limiter/src/lib.rs | 9 +++---- vmm/src/cpu.rs | 50 ++++++++++++++++++-------------------- vmm/src/serial_manager.rs | 3 +-- vmm/src/vm.rs | 13 +++++----- 10 files changed, 51 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 26182ebcaa..1f6351b244 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,6 +173,7 @@ suspicious = "deny" assertions_on_result_states = "deny" manual_string_new = "deny" map_unwrap_or = "deny" +redundant_else = "deny" semicolon_if_nothing_returned = "deny" undocumented_unsafe_blocks = "deny" uninlined_format_args = "deny" diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 0ecf9a48db..786ab8a82d 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -1214,9 +1214,8 @@ impl hypervisor::Hypervisor for KvmHypervisor { // ioctl has been interrupted, we have to retry as // this can't be considered as a regular error. continue; - } else { - return Err(hypervisor::HypervisorError::VmCreate(e.into())); } + return Err(hypervisor::HypervisorError::VmCreate(e.into())); } } break; diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 7194567203..a7de460806 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -303,9 +303,8 @@ impl hypervisor::Hypervisor for MshvHypervisor { // ioctl has been interrupted, we have to retry as // this can't be considered as a regular error. continue; - } else { - return Err(hypervisor::HypervisorError::VmCreate(e.into())); } + return Err(hypervisor::HypervisorError::VmCreate(e.into())); } } break; diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index b97c46c0ba..4dbd04f122 100644 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -948,12 +948,11 @@ impl PciConfiguration { self.pending_bar_reprogram ); return self.pending_bar_reprogram.drain(..).collect(); - } else { - info!( - "MSE bit is disabled. No BAR reprogramming parameter is returned: {:x?}", - self.pending_bar_reprogram - ); } + info!( + "MSE bit is disabled. No BAR reprogramming parameter is returned: {:x?}", + self.pending_bar_reprogram + ); } Vec::new() diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 87adf08d82..bb45e6c00c 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -910,16 +910,15 @@ impl VfioCommon { let cap_id = self.vfio_wrapper.read_config_byte(cap_next.into()); if PciCapabilityId::from(cap_id) == PciCapabilityId::MsiX { return Some(cap_next as usize); - } else { - let cap_ptr = self.vfio_wrapper.read_config_byte((cap_next + 1).into()) - & PCI_CONFIG_CAPABILITY_PTR_MASK; + } + let cap_ptr = self.vfio_wrapper.read_config_byte((cap_next + 1).into()) + & PCI_CONFIG_CAPABILITY_PTR_MASK; - // See parse_capabilities below for an explanation. - if cap_ptr != cap_next { - cap_next = cap_ptr; - } else { - break; - } + // See parse_capabilities below for an explanation. + if cap_ptr != cap_next { + cap_next = cap_ptr; + } else { + break; } } diff --git a/rate_limiter/src/group.rs b/rate_limiter/src/group.rs index 54e1401617..d0978176bd 100644 --- a/rate_limiter/src/group.rs +++ b/rate_limiter/src/group.rs @@ -234,9 +234,8 @@ impl RateLimiterGroup { Err(e) => { if e.kind() == io::ErrorKind::Interrupted { continue; - } else { - return Err(Error::Epoll(e)); } + return Err(Error::Epoll(e)); } }; diff --git a/rate_limiter/src/lib.rs b/rate_limiter/src/lib.rs index cd73253c59..029288ad2f 100644 --- a/rate_limiter/src/lib.rs +++ b/rate_limiter/src/lib.rs @@ -168,12 +168,11 @@ impl TokenBucket { self.last_update = Instant::now(); // No need to continue to the refill process, we still have burst budget to consume from. return BucketReduction::Success; - } else { - // We still have burst budget for *some* of the tokens requests. - // The tokens left unfulfilled will be consumed from current `self.budget`. - tokens -= self.one_time_burst; - self.one_time_burst = 0; } + // We still have burst budget for *some* of the tokens requests. + // The tokens left unfulfilled will be consumed from current `self.budget`. + tokens -= self.one_time_burst; + self.one_time_burst = 0; } // Compute time passed since last refill/update. diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 67cd79bdbd..ee5186e6d5 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -726,17 +726,16 @@ impl VcpuState { loop { if self.vcpu_run_interrupted.load(Ordering::SeqCst) { return Ok(()); - } else { - // This is more effective than thread::yield_now() at - // avoiding a priority inversion with the vCPU thread - thread::sleep(std::time::Duration::from_millis(1)); - count += 1; - if count >= 1000 { - return Err(Error::SignalAcknowledgeTimeout); - } else if count % 10 == 0 { - warn!("vCPU thread did not respond in {count}ms to signal - retrying"); - self.signal_thread(); - } + } + // This is more effective than thread::yield_now() at + // avoiding a priority inversion with the vCPU thread + thread::sleep(std::time::Duration::from_millis(1)); + count += 1; + if count >= 1000 { + return Err(Error::SignalAcknowledgeTimeout); + } else if count % 10 == 0 { + warn!("vCPU thread did not respond in {count}ms to signal - retrying"); + self.signal_thread(); } } } @@ -807,21 +806,20 @@ impl CpuManager { if amx_tile != 0 { return Err(Error::AmxEnable(anyhow!("Guest AMX usage not supported"))); - } else { - let mut mask: usize = 0; - // SAFETY: Syscall with valid parameters. We use a raw mutable pointer to - // the `mask` place in order to ensure that we do not violate Rust's - // aliasing rules. - let result = unsafe { - libc::syscall( - libc::SYS_arch_prctl, - ARCH_GET_XCOMP_GUEST_PERM, - &raw mut mask, - ) - }; - if result != 0 || (mask & XFEATURE_XTILEDATA_MASK) != XFEATURE_XTILEDATA_MASK { - return Err(Error::AmxEnable(anyhow!("Guest AMX usage not supported"))); - } + } + let mut mask: usize = 0; + // SAFETY: Syscall with valid parameters. We use a raw mutable pointer to + // the `mask` place in order to ensure that we do not violate Rust's + // aliasing rules. + let result = unsafe { + libc::syscall( + libc::SYS_arch_prctl, + ARCH_GET_XCOMP_GUEST_PERM, + &raw mut mask, + ) + }; + if result != 0 || (mask & XFEATURE_XTILEDATA_MASK) != XFEATURE_XTILEDATA_MASK { + return Err(Error::AmxEnable(anyhow!("Guest AMX usage not supported"))); } } diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 6fa94a0c22..efb8d67e82 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -289,9 +289,8 @@ impl SerialManager { // be considered as a regular error. Instead it is more // appropriate to retry, by calling into epoll_wait(). continue; - } else { - return Err(Error::Epoll(e)); } + return Err(Error::Epoll(e)); } }; diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index f42fe2406b..526649c574 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1743,14 +1743,13 @@ impl Vm { zone.hotplugged_size = Some(hotplugged_size); return Ok(()); - } else { - error!( - "Invalid to ask less ({}) than boot RAM ({}) for \ - this memory zone", - desired_memory, zone.size, - ); - return Err(Error::ResizeZone); } + error!( + "Invalid to ask less ({}) than boot RAM ({}) for \ + this memory zone", + desired_memory, zone.size, + ); + return Err(Error::ResizeZone); } } } From 0a07c96d17496971c10a1075f1a05dbfc7180dfa Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 21 Nov 2025 11:08:27 +0100 Subject: [PATCH 0354/1893] misc: clippy: add if_not_else This removes cognitive load when reading if statements. All changes were applied by clippy via `--fix`. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 1 + arch/src/aarch64/fdt.rs | 30 +++++----- block/src/lib.rs | 22 ++++---- block/src/qcow/refcount.rs | 6 +- hypervisor/src/kvm/mod.rs | 6 +- net_util/src/ctrl_queue.rs | 8 +-- net_util/src/open_tap.rs | 10 ++-- net_util/src/queue_pair.rs | 12 ++-- pci/src/bus.rs | 6 +- pci/src/vfio.rs | 17 +++--- test_infra/src/lib.rs | 17 +++--- virtio-devices/src/vdpa.rs | 12 ++-- vm-allocator/src/address.rs | 6 +- vmm/src/config.rs | 12 ++-- vmm/src/device_manager.rs | 8 +-- vmm/src/memory_manager.rs | 110 ++++++++++++++++++------------------ 16 files changed, 139 insertions(+), 144 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f6351b244..4b15a67a0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -171,6 +171,7 @@ suspicious = "deny" # Individual Lints assertions_on_result_states = "deny" +if_not_else = "deny" manual_string_new = "deny" map_unwrap_or = "deny" redundant_else = "deny" diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index ea3f842eb9..f466109e88 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -111,9 +111,7 @@ pub fn get_cache_size(cache_level: CacheLevel) -> u32 { } let file_path = Path::new(&file_directory); - if !file_path.exists() { - 0 - } else { + 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(); @@ -127,6 +125,8 @@ pub fn get_cache_size(cache_level: CacheLevel) -> u32 { "G" => 1024u32.pow(3), _ => 1, } + } else { + 0 } } @@ -142,11 +142,11 @@ pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 { } let file_path = Path::new(&file_directory); - if !file_path.exists() { - 0 - } else { + if file_path.exists() { let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted."); src.trim().parse::().unwrap() + } else { + 0 } } @@ -162,11 +162,11 @@ pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 { } let file_path = Path::new(&file_directory); - if !file_path.exists() { - 0 - } else { + if file_path.exists() { let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted."); src.trim().parse::().unwrap() + } else { + 0 } } @@ -187,9 +187,7 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool { } let file_path = Path::new(&file_directory); - if !file_path.exists() { - result = false; - } else { + 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() { @@ -197,6 +195,8 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool { } else { result = src.contains('-') || src.contains(','); } + } else { + result = false; } result @@ -312,9 +312,7 @@ fn create_cpu_nodes( let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache"); let cache_exist: bool = cache_path.exists(); - if !cache_exist { - warn!("cache sysfs system does not exist."); - } else { + 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); @@ -342,6 +340,8 @@ fn create_cpu_nodes( if l3_cache_size != 0 { l3_cache_shared = get_cache_shared(CacheLevel::L3); } + } else { + warn!("cache sysfs system does not exist."); } for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) { diff --git a/block/src/lib.rs b/block/src/lib.rs index 8871cebd01..997a2f341a 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -293,14 +293,7 @@ impl Request { error!("Only head descriptor present: request = {req:?}"); })?; - if !desc.has_next() { - status_desc = desc; - // Only flush requests are allowed to skip the data descriptor. - if req.request_type != RequestType::Flush { - error!("Need a data descriptor: request = {req:?}"); - return Err(Error::DescriptorChainTooShort); - } - } else { + if desc.has_next() { req.data_descriptors.reserve_exact(1); while desc.has_next() { if desc.is_write_only() && req.request_type == RequestType::Out { @@ -326,6 +319,13 @@ impl Request { })?; } status_desc = desc; + } else { + status_desc = desc; + // Only flush requests are allowed to skip the data descriptor. + if req.request_type != RequestType::Flush { + error!("Need a data descriptor: request = {req:?}"); + return Err(Error::DescriptorChainTooShort); + } } // The status MUST always be writable. @@ -446,7 +446,9 @@ impl Request { // In case it's not properly aligned, an intermediate buffer is // created with the correct alignment, and a copy from/to the // origin buffer is performed, depending on the type of operation. - let iov_base = if !(origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) { + let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) { + origin_ptr.as_ptr() as *mut libc::c_void + } else { let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap(); // SAFETY: layout has non-zero size let aligned_ptr = unsafe { alloc_zeroed(layout) }; @@ -474,8 +476,6 @@ impl Request { }); aligned_ptr as *mut libc::c_void - } else { - origin_ptr.as_ptr() as *mut libc::c_void }; let iovec = libc::iovec { diff --git a/block/src/qcow/refcount.rs b/block/src/qcow/refcount.rs index 12ab30202e..0ec1ab5f4f 100644 --- a/block/src/qcow/refcount.rs +++ b/block/src/qcow/refcount.rs @@ -119,7 +119,9 @@ impl RefCount { } // Unwrap is safe here as the entry was filled directly above. - let dropped_cluster = if !self.refblock_cache.get(table_index).unwrap().dirty() { + let dropped_cluster = if self.refblock_cache.get(table_index).unwrap().dirty() { + None + } else { // Free the previously used block and use a new one. Writing modified counts to new // blocks keeps the on-disk state consistent even if it's out of date. if let Some((addr, _)) = new_cluster.take() { @@ -128,8 +130,6 @@ impl RefCount { } else { return Err(Error::NeedNewCluster); } - } else { - None }; self.refblock_cache.get_mut(table_index).unwrap()[block_index] = refcount; diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 786ab8a82d..31a6296b69 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -2364,7 +2364,9 @@ impl cpu::Vcpu for KvmVcpu { let expected_num_msrs = msr_entries.len(); let num_msrs = self.get_msrs(&mut msr_entries)?; - let msrs = if num_msrs != expected_num_msrs { + let msrs = if num_msrs == expected_num_msrs { + msr_entries + } else { let mut faulty_msr_index = num_msrs; let mut msr_entries_tmp = msr_entries[..faulty_msr_index].to_vec(); @@ -2390,8 +2392,6 @@ impl cpu::Vcpu for KvmVcpu { } msr_entries_tmp - } else { - msr_entries }; let vcpu_events = self.get_vcpu_events()?; diff --git a/net_util/src/ctrl_queue.rs b/net_util/src/ctrl_queue.rs index 8eb8408621..284b6ec4e6 100644 --- a/net_util/src/ctrl_queue.rs +++ b/net_util/src/ctrl_queue.rs @@ -111,10 +111,7 @@ impl CtrlQueue { .memory() .read_obj::(data_desc_addr) .map_err(Error::GuestMemory)?; - if u32::from(ctrl_hdr.cmd) != VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET { - warn!("Unsupported command: {}", ctrl_hdr.cmd); - false - } else { + if u32::from(ctrl_hdr.cmd) == VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET { let mut ok = true; for tap in self.taps.iter_mut() { info!("Reprogramming tap offload with features: {features}"); @@ -126,6 +123,9 @@ impl CtrlQueue { .ok(); } ok + } else { + warn!("Unsupported command: {}", ctrl_hdr.cmd); + false } } _ => { diff --git a/net_util/src/open_tap.rs b/net_util/src/open_tap.rs index bbe1db432e..39d4285df3 100644 --- a/net_util/src/open_tap.rs +++ b/net_util/src/open_tap.rs @@ -80,16 +80,14 @@ fn open_tap_rx_q_0( None => Tap::new(num_rx_q).map_err(Error::TapOpen)?, }; // Don't overwrite ip configuration of existing interfaces: - if !tap_exists { - if let Some(ip) = ip_addr { - tap.set_ip_addr(ip, netmask) - .map_err(Error::TapSetIpNetmask)?; - } - } else { + if tap_exists { warn!( "Tap {} already exists. IP configuration will not be overwritten.", if_name.unwrap_or_default() ); + } else if let Some(ip) = ip_addr { + tap.set_ip_addr(ip, netmask) + .map_err(Error::TapSetIpNetmask)?; } if let Some(mac) = host_mac { tap.set_mac_addr(*mac).map_err(Error::TapSetMac)?; diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index a6bce4409b..86a1c758dc 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -88,7 +88,9 @@ impl TxVirtio { next_desc = desc_chain.next(); } - let len = if !iovecs.is_empty() { + let len = if iovecs.is_empty() { + 0 + } else { // SAFETY: FFI call with correct arguments let result = unsafe { libc::writev( @@ -119,8 +121,6 @@ impl TxVirtio { self.counter_frames += Wrapping(1); result as u32 - } else { - 0 }; // For the sake of simplicity (similar to the RX rate limiting), we always @@ -230,7 +230,9 @@ impl RxVirtio { next_desc = desc_chain.next(); } - let len = if !iovecs.is_empty() { + let len = if iovecs.is_empty() { + 0 + } else { // SAFETY: FFI call with correct arguments let result = unsafe { libc::readv( @@ -268,8 +270,6 @@ impl RxVirtio { self.counter_frames += Wrapping(1); result as u32 - } else { - 0 }; // For the sake of simplicity (keeping the handling of RX_QUEUE_EVENT and diff --git a/pci/src/bus.rs b/pci/src/bus.rs index 3ba2c056b7..041324d96f 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -180,11 +180,11 @@ impl PciBus { pub fn get_device_id(&mut self, id: usize) -> Result<()> { if id < NUM_DEVICE_IDS { - if !self.device_ids[id] { + if self.device_ids[id] { + Err(PciRootError::AlreadyInUsePciDeviceSlot(id)) + } else { self.device_ids[id] = true; Ok(()) - } else { - Err(PciRootError::AlreadyInUsePciDeviceSlot(id)) } } else { Err(PciRootError::InvalidPciDeviceSlot(id)) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index bb45e6c00c..76c5d72e39 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -646,20 +646,20 @@ impl VfioCommon { flags = self.vfio_wrapper.read_config_dword(bar_offset); // Is this an IO BAR? - let io_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX { - matches!(flags & PCI_CONFIG_IO_BAR, PCI_CONFIG_IO_BAR) - } else { + let io_bar = if bar_id == VFIO_PCI_ROM_REGION_INDEX { false + } else { + matches!(flags & PCI_CONFIG_IO_BAR, PCI_CONFIG_IO_BAR) }; // Is this a 64-bit BAR? - let is_64bit_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX { + let is_64bit_bar = if bar_id == VFIO_PCI_ROM_REGION_INDEX { + false + } else { matches!( flags & PCI_CONFIG_MEMORY_BAR_64BIT, PCI_CONFIG_MEMORY_BAR_64BIT ) - } else { - false }; if matches!( @@ -915,11 +915,10 @@ impl VfioCommon { & PCI_CONFIG_CAPABILITY_PTR_MASK; // See parse_capabilities below for an explanation. - if cap_ptr != cap_next { - cap_next = cap_ptr; - } else { + if cap_ptr == cap_next { break; } + cap_next = cap_ptr; } None diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 99f41f5273..2f68a5cc56 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -757,12 +757,11 @@ pub fn ssh_command_ip( pub fn exec_host_command_with_retries(command: &str, retries: u32, interval: Duration) -> bool { for _ in 0..retries { let s = exec_host_command_output(command).status; - if !s.success() { - eprintln!("\n\n==== retrying in {interval:?} ===\n\n"); - thread::sleep(interval); - } else { + if s.success() { return true; } + eprintln!("\n\n==== retrying in {interval:?} ===\n\n"); + thread::sleep(interval); } false @@ -1686,17 +1685,17 @@ pub fn measure_virtio_net_throughput( failed = true; } - if !failed { - // Safe to unwrap as we know the child has terminated successfully - let output = c.wait_with_output().unwrap(); - results.push(parse_iperf3_output(&output.stdout, receive, bandwidth)?); - } else { + if failed { let _ = c.kill(); let output = c.wait_with_output().unwrap(); println!( "=============== Client output [Error] ===============\n\n{}\n\n===========end============\n\n", String::from_utf8_lossy(&output.stdout) ); + } else { + // Safe to unwrap as we know the child has terminated successfully + let output = c.wait_with_output().unwrap(); + results.push(parse_iperf3_output(&output.stdout, receive, bandwidth)?); } } diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 867cf21cce..49248a6780 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -463,12 +463,12 @@ impl VirtioDevice for Vdpa { impl Pausable for Vdpa { fn pause(&mut self) -> std::result::Result<(), MigratableError> { - if !self.migrating { + if self.migrating { + Ok(()) + } else { Err(MigratableError::Pause(anyhow!( "Can't pause a vDPA device outside live migration" ))) - } else { - Ok(()) } } @@ -477,12 +477,12 @@ impl Pausable for Vdpa { return Ok(()); } - if !self.migrating { + if self.migrating { + Ok(()) + } else { Err(MigratableError::Resume(anyhow!( "Can't resume a vDPA device outside live migration" ))) - } else { - Ok(()) } } } diff --git a/vm-allocator/src/address.rs b/vm-allocator/src/address.rs index 3cc5ed9c81..2eb99670e8 100644 --- a/vm-allocator/src/address.rs +++ b/vm-allocator/src/address.rs @@ -68,10 +68,10 @@ impl AddressAllocator { } fn align_address(&self, address: GuestAddress, alignment: GuestUsize) -> GuestAddress { - let align_adjust = if !address.raw_value().is_multiple_of(alignment) { - alignment - (address.raw_value() % alignment) - } else { + let align_adjust = if address.raw_value().is_multiple_of(alignment) { 0 + } else { + alignment - (address.raw_value() % alignment) }; address.unchecked_add(align_adjust) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 292cd275a0..6d5dcf5da2 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2733,16 +2733,15 @@ impl VmConfig { for numa_node in numa.iter() { if let Some(memory_zones) = numa_node.memory_zones.clone() { for memory_zone in memory_zones.iter() { - if !used_numa_node_memory_zones.contains_key(memory_zone) { - used_numa_node_memory_zones - .insert(memory_zone.to_string(), numa_node.guest_numa_id); - } else { + if used_numa_node_memory_zones.contains_key(memory_zone) { return Err(ValidationError::MemoryZoneReused( memory_zone.to_string(), *used_numa_node_memory_zones.get(memory_zone).unwrap(), numa_node.guest_numa_id, )); } + used_numa_node_memory_zones + .insert(memory_zone.to_string(), numa_node.guest_numa_id); } } @@ -2756,15 +2755,14 @@ impl VmConfig { numa_node.guest_numa_id, )); } - if !used_pci_segments.contains_key(pci_segment) { - used_pci_segments.insert(*pci_segment, numa_node.guest_numa_id); - } else { + if used_pci_segments.contains_key(pci_segment) { return Err(ValidationError::PciSegmentReused( *pci_segment, *used_pci_segments.get(pci_segment).unwrap(), numa_node.guest_numa_id, )); } + used_pci_segments.insert(*pci_segment, numa_node.guest_numa_id); } } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 727c5b504d..7766caa802 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -5246,15 +5246,15 @@ impl Aml for DeviceManager { let serial_irq = 4; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] let serial_irq = - if self.config.lock().unwrap().serial.clone().mode != ConsoleOutputMode::Off { + if self.config.lock().unwrap().serial.clone().mode == ConsoleOutputMode::Off { + // If serial is turned off, add a fake device with invalid irq. + 31 + } else { self.get_device_info() .clone() .get(&(DeviceType::Serial, DeviceType::Serial.to_string())) .unwrap() .irq() - } else { - // If serial is turned off, add a fake device with invalid irq. - 31 }; if self.config.lock().unwrap().serial.mode != ConsoleOutputMode::Off { aml::Device::new( diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index f73790e421..8bfcd6d822 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -766,61 +766,7 @@ impl MemoryManager { ) -> Result<(u64, Vec, bool), Error> { let mut allow_mem_hotplug = false; - if !user_provided_zones { - if config.zones.is_some() { - error!( - "User defined memory regions can't be provided if the \ - memory size is not 0" - ); - return Err(Error::InvalidMemoryParameters); - } - - if config.hotplug_size.is_some() { - allow_mem_hotplug = true; - } - - if let Some(hotplugged_size) = config.hotplugged_size { - if let Some(hotplug_size) = config.hotplug_size { - if hotplugged_size > hotplug_size { - error!( - "'hotplugged_size' {hotplugged_size} can't be bigger than \ - 'hotplug_size' {hotplug_size}", - ); - return Err(Error::InvalidMemoryParameters); - } - } else { - error!( - "Invalid to define 'hotplugged_size' when there is\ - no 'hotplug_size'" - ); - return Err(Error::InvalidMemoryParameters); - } - if config.hotplug_method == HotplugMethod::Acpi { - error!( - "Invalid to define 'hotplugged_size' with hotplug \ - method 'acpi'" - ); - return Err(Error::InvalidMemoryParameters); - } - } - - // Create a single zone from the global memory config. This lets - // us reuse the codepath for user defined memory zones. - let zones = vec![MemoryZoneConfig { - id: String::from(DEFAULT_MEMORY_ZONE), - size: config.size, - file: None, - shared: config.shared, - hugepages: config.hugepages, - hugepage_size: config.hugepage_size, - host_numa_node: None, - hotplug_size: config.hotplug_size, - hotplugged_size: config.hotplugged_size, - prefault: config.prefault, - }]; - - Ok((config.size, zones, allow_mem_hotplug)) - } else { + if user_provided_zones { if config.zones.is_none() { error!( "User defined memory regions must be provided if the \ @@ -880,6 +826,60 @@ impl MemoryManager { } Ok((total_ram_size, zones, allow_mem_hotplug)) + } else { + if config.zones.is_some() { + error!( + "User defined memory regions can't be provided if the \ + memory size is not 0" + ); + return Err(Error::InvalidMemoryParameters); + } + + if config.hotplug_size.is_some() { + allow_mem_hotplug = true; + } + + if let Some(hotplugged_size) = config.hotplugged_size { + if let Some(hotplug_size) = config.hotplug_size { + if hotplugged_size > hotplug_size { + error!( + "'hotplugged_size' {hotplugged_size} can't be bigger than \ + 'hotplug_size' {hotplug_size}", + ); + return Err(Error::InvalidMemoryParameters); + } + } else { + error!( + "Invalid to define 'hotplugged_size' when there is\ + no 'hotplug_size'" + ); + return Err(Error::InvalidMemoryParameters); + } + if config.hotplug_method == HotplugMethod::Acpi { + error!( + "Invalid to define 'hotplugged_size' with hotplug \ + method 'acpi'" + ); + return Err(Error::InvalidMemoryParameters); + } + } + + // Create a single zone from the global memory config. This lets + // us reuse the codepath for user defined memory zones. + let zones = vec![MemoryZoneConfig { + id: String::from(DEFAULT_MEMORY_ZONE), + size: config.size, + file: None, + shared: config.shared, + hugepages: config.hugepages, + hugepage_size: config.hugepage_size, + host_numa_node: None, + hotplug_size: config.hotplug_size, + hotplugged_size: config.hotplugged_size, + prefault: config.prefault, + }]; + + Ok((config.size, zones, allow_mem_hotplug)) } } From 6a86c157afde79e734bc35e1065403d532c51f34 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Nov 2025 14:29:36 +0100 Subject: [PATCH 0355/1893] misc: clippy: add needless_pass_by_value (partially) This helps to uncover expensive and needless clones in the code base. For example, I prevented extensive clones in the snapshot path where (nested) BTreeMap's have been cloned over and over again. Further, the lint helps devs to much better reason about the ownership of parameters. All of these changes have been done manually with the necessary caution. A few structs that are cheap to clone are now `copy` so that this lint won't trigger for them. I didn't enable the lint so far as it is a massive rabbit hole and needs much more fixes. Nevertheless, it is very useful. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 2 + api_client/src/lib.rs | 20 +++----- arch/src/x86_64/mod.rs | 6 +-- block/src/fcntl.rs | 6 +-- block/src/lib.rs | 1 + block/src/qcow/mod.rs | 23 ++++----- block/src/qcow/qcow_raw_file.rs | 2 +- devices/src/ioapic.rs | 2 +- devices/src/ivshmem.rs | 14 +++--- devices/src/pvpanic.rs | 14 +++--- devices/src/tpm.rs | 4 +- pci/src/vfio.rs | 54 ++++++++++---------- pci/src/vfio_user.rs | 12 +++-- rate_limiter/src/lib.rs | 1 + src/bin/ch-remote.rs | 4 +- tpm/src/emulator.rs | 2 +- tpm/src/socket.rs | 4 +- vhost_user_block/src/lib.rs | 6 +-- virtio-devices/src/block.rs | 7 +-- virtio-devices/src/transport/pci_device.rs | 30 +++++------- virtio-devices/src/vdpa.rs | 4 +- vm-allocator/src/gsi.rs | 4 +- vm-allocator/src/system.rs | 8 +-- vm-migration/src/lib.rs | 4 +- vmm/src/cpu.rs | 12 +++-- vmm/src/device_manager.rs | 8 +-- vmm/src/lib.rs | 4 +- vmm/src/memory_manager.rs | 2 +- vmm/src/vm.rs | 57 +++++++++++----------- 29 files changed, 162 insertions(+), 155 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b15a67a0e..155e2fc17a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -174,6 +174,8 @@ assertions_on_result_states = "deny" if_not_else = "deny" manual_string_new = "deny" map_unwrap_or = "deny" +# Very helpful to uncover costly clones, but unfortunately also a rabbit hole. +#needless_pass_by_value = "deny" redundant_else = "deny" semicolon_if_nothing_returned = "deny" undocumented_unsafe_blocks = "deny" diff --git a/api_client/src/lib.rs b/api_client/src/lib.rs index 8ced48f7c1..f5bdd1302a 100644 --- a/api_client/src/lib.rs +++ b/api_client/src/lib.rs @@ -142,7 +142,7 @@ pub fn simple_api_full_command_with_fds_and_response, - request_fds: Vec, + request_fds: &[RawFd], ) -> Result, Error> { socket .send_with_fds( @@ -150,7 +150,7 @@ pub fn simple_api_full_command_with_fds_and_response( method: &str, full_command: &str, request_body: Option<&str>, - request_fds: Vec, + request_fds: &[RawFd], ) -> Result<(), Error> { let response = simple_api_full_command_with_fds_and_response( socket, @@ -201,7 +201,7 @@ pub fn simple_api_full_command( full_command: &str, request_body: Option<&str>, ) -> Result<(), Error> { - simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new()) + simple_api_full_command_with_fds(socket, method, full_command, request_body, &[]) } pub fn simple_api_full_command_and_response( @@ -210,13 +210,7 @@ pub fn simple_api_full_command_and_response( full_command: &str, request_body: Option<&str>, ) -> Result, Error> { - simple_api_full_command_with_fds_and_response( - socket, - method, - full_command, - request_body, - Vec::new(), - ) + simple_api_full_command_with_fds_and_response(socket, method, full_command, request_body, &[]) } pub fn simple_api_command_with_fds( @@ -224,7 +218,7 @@ pub fn simple_api_command_with_fds( method: &str, c: &str, request_body: Option<&str>, - request_fds: Vec, + request_fds: &[RawFd], ) -> Result<(), Error> { // Create the full VM command. For VMM commands, use // simple_api_full_command(). @@ -239,5 +233,5 @@ pub fn simple_api_command( c: &str, request_body: Option<&str>, ) -> Result<(), Error> { - simple_api_command_with_fds(socket, method, c, request_body, Vec::new()) + simple_api_command_with_fds(socket, method, c, request_body, &[]) } diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index e2cb1eab28..ab38359936 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -283,7 +283,7 @@ impl CpuidPatch { } } - pub fn patch_cpuid(cpuid: &mut [CpuIdEntry], patches: Vec) { + pub fn patch_cpuid(cpuid: &mut [CpuIdEntry], patches: &[CpuidPatch]) { for entry in cpuid { for patch in patches.iter() { if entry.function == patch.function && entry.index == patch.index { @@ -620,7 +620,7 @@ pub fn generate_common_cpuid( .get_supported_cpuid() .map_err(Error::CpuidGetSupported)?; - CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches); + CpuidPatch::patch_cpuid(&mut cpuid, &cpuid_patches); #[cfg(feature = "tdx")] let tdx_capabilities = if config.tdx { @@ -1421,7 +1421,7 @@ fn update_cpuid_topology( edx_bit: Some(28), }, ]; - CpuidPatch::patch_cpuid(cpuid, cpuid_patches); + CpuidPatch::patch_cpuid(cpuid, &cpuid_patches); CpuidPatch::set_cpuid_reg( cpuid, 0x8000_0008, diff --git a/block/src/fcntl.rs b/block/src/fcntl.rs index 3687288a6b..a2a684f322 100644 --- a/block/src/fcntl.rs +++ b/block/src/fcntl.rs @@ -163,7 +163,7 @@ const fn get_flock(lock_type: LockType, granularity: LockGranularity) -> libc::f /// - `lock_type`: The [`LockType`] /// - `granularity`: The [`LockGranularity`]. pub fn try_acquire_lock( - file: Fd, + file: &Fd, lock_type: LockType, granularity: LockGranularity, ) -> Result<(), LockError> { @@ -191,7 +191,7 @@ pub fn try_acquire_lock( /// # Parameters /// - `file`: The file to clear all locks for [`LockType`]. /// - `granularity`: The [`LockGranularity`]. -pub fn clear_lock(file: Fd, granularity: LockGranularity) -> Result<(), LockError> { +pub fn clear_lock(file: &Fd, granularity: LockGranularity) -> Result<(), LockError> { try_acquire_lock(file, LockType::Unlock, granularity) } @@ -202,7 +202,7 @@ pub fn clear_lock(file: Fd, granularity: LockGranularity) -> Result /// - `file`: The file for which to get the lock state. /// - `granularity`: The [`LockGranularity`]. pub fn get_lock_state( - file: Fd, + file: &Fd, granularity: LockGranularity, ) -> Result { let mut flock = get_flock(LockType::Write, granularity); diff --git a/block/src/lib.rs b/block/src/lib.rs index 997a2f341a..1996c9ba6f 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -861,6 +861,7 @@ ioctl_io_nr!(BLKPBSZGET, 0x12, 123); ioctl_io_nr!(BLKIOMIN, 0x12, 120); ioctl_io_nr!(BLKIOOPT, 0x12, 121); +#[derive(Copy, Clone)] enum BlockSize { LogicalBlock, PhysicalBlock, diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index d713d56936..251c1b7724 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -125,6 +125,7 @@ pub enum Error { pub type Result = std::result::Result; +#[derive(Copy, Clone)] pub enum ImageType { Raw, Qcow2, @@ -683,7 +684,7 @@ impl QcowFile { /// Creates a new QcowFile at the given path. pub fn new(file: RawFile, version: u32, virtual_size: u64) -> Result { let header = QcowHeader::create_for_size_and_path(version, virtual_size, None)?; - QcowFile::new_from_header(file, header) + QcowFile::new_from_header(file, &header) } /// Creates a new QcowFile at the given path. @@ -705,12 +706,12 @@ impl QcowFile { .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; let size = backing_file.virtual_size(); let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?; - let mut result = QcowFile::new_from_header(file, header)?; + let mut result = QcowFile::new_from_header(file, &header)?; result.backing_file = Some(Box::new(backing_file)); Ok(result) } - fn new_from_header(mut file: RawFile, header: QcowHeader) -> Result { + fn new_from_header(mut file: RawFile, header: &QcowHeader) -> Result { file.rewind().map_err(Error::SeekingFile)?; header.write_to(&mut file)?; @@ -854,7 +855,7 @@ impl QcowFile { // Add references to the L1 table clusters. fn set_l1_refcounts( refcounts: &mut [u16], - header: QcowHeader, + header: &QcowHeader, cluster_size: u64, ) -> Result<()> { let entries_per_cluster = cluster_size / size_of::() as u64; @@ -869,7 +870,7 @@ impl QcowFile { // Traverse the L1 and L2 tables to find all reachable data clusters. fn set_data_refcounts( refcounts: &mut [u16], - header: QcowHeader, + header: &QcowHeader, cluster_size: u64, raw_file: &mut QcowRawFile, ) -> Result<()> { @@ -908,7 +909,7 @@ impl QcowFile { // Add references to the top-level refcount table clusters. fn set_refcount_table_refcounts( refcounts: &mut [u16], - header: QcowHeader, + header: &QcowHeader, cluster_size: u64, ) -> Result<()> { let refcount_table_offset = header.refcount_table_offset; @@ -1046,9 +1047,9 @@ impl QcowFile { // Find all references clusters and rebuild refcounts. set_header_refcount(&mut refcounts, cluster_size)?; - set_l1_refcounts(&mut refcounts, header.clone(), cluster_size)?; - set_data_refcounts(&mut refcounts, header.clone(), cluster_size, raw_file)?; - set_refcount_table_refcounts(&mut refcounts, header.clone(), cluster_size)?; + set_l1_refcounts(&mut refcounts, &header, cluster_size)?; + set_data_refcounts(&mut refcounts, &header, cluster_size, raw_file)?; + set_refcount_table_refcounts(&mut refcounts, &header, cluster_size)?; // Allocate clusters to store the new reference count blocks. let ref_table = alloc_refblocks(&mut refcounts, cluster_size, refblock_clusters)?; @@ -1276,7 +1277,7 @@ impl QcowFile { // First use a pre allocated cluster if one is available. if let Some(free_cluster) = self.avail_clusters.pop() { if let Some(initial_data) = initial_data { - self.raw_file.write_cluster(free_cluster, initial_data)?; + self.raw_file.write_cluster(free_cluster, &initial_data)?; } else { self.raw_file.zero_cluster(free_cluster)?; } @@ -1286,7 +1287,7 @@ impl QcowFile { let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset(); if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? { if let Some(initial_data) = initial_data { - self.raw_file.write_cluster(new_cluster, initial_data)?; + self.raw_file.write_cluster(new_cluster, &initial_data)?; } Ok(new_cluster) } else { diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index bb4f849ac8..ceebfd1a9c 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -144,7 +144,7 @@ impl QcowRawFile { } /// Writes - pub fn write_cluster(&mut self, address: u64, data: Vec) -> io::Result<()> { + pub fn write_cluster(&mut self, address: u64, data: &[u8]) -> io::Result<()> { let cluster_size = self.cluster_size as usize; self.file.seek(SeekFrom::Start(address))?; self.file.write_all(&data[0..cluster_size]) diff --git a/devices/src/ioapic.rs b/devices/src/ioapic.rs index c43f629d8f..660531eb62 100644 --- a/devices/src/ioapic.rs +++ b/devices/src/ioapic.rs @@ -191,7 +191,7 @@ impl Ioapic { pub fn new( id: String, apic_address: GuestAddress, - interrupt_manager: Arc>, + interrupt_manager: &dyn InterruptManager, state: Option, ) -> Result { let interrupt_source_group = interrupt_manager diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs index 4e1a01bf37..fff48a72c6 100644 --- a/devices/src/ivshmem.rs +++ b/devices/src/ivshmem.rs @@ -114,14 +114,14 @@ impl IvshmemDevice { region_size: u64, backend_file: Option, ivshmem_ops: Arc>, - snapshot: Option, + snapshot: Option<&Snapshot>, ) -> Result { - let pci_configuration_state = - vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| { - IvshmemError::RetrievePciConfigurationState(anyhow!( - "Failed to get PciConfigurationState from Snapshot: {e}", - )) - })?; + let pci_configuration_state = vm_migration::state_from_id(snapshot, PCI_CONFIGURATION_ID) + .map_err(|e| { + IvshmemError::RetrievePciConfigurationState(anyhow!( + "Failed to get PciConfigurationState from Snapshot: {e}", + )) + })?; let state: Option = snapshot .as_ref() diff --git a/devices/src/pvpanic.rs b/devices/src/pvpanic.rs index ef42b03610..0451da1a05 100644 --- a/devices/src/pvpanic.rs +++ b/devices/src/pvpanic.rs @@ -66,13 +66,13 @@ pub struct PvPanicDeviceState { } impl PvPanicDevice { - pub fn new(id: String, snapshot: Option) -> Result { - let pci_configuration_state = - vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| { - PvPanicError::RetrievePciConfigurationState(anyhow!( - "Failed to get PciConfigurationState from Snapshot: {e}" - )) - })?; + pub fn new(id: String, snapshot: Option<&Snapshot>) -> Result { + let pci_configuration_state = vm_migration::state_from_id(snapshot, PCI_CONFIGURATION_ID) + .map_err(|e| { + PvPanicError::RetrievePciConfigurationState(anyhow!( + "Failed to get PciConfigurationState from Snapshot: {e}" + )) + })?; let mut configuration = PciConfiguration::new( PVPANIC_VENDOR_ID, diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs index 532b2c4a94..15494e3233 100644 --- a/devices/src/tpm.rs +++ b/devices/src/tpm.rs @@ -27,6 +27,7 @@ pub enum Error { type Result = anyhow::Result; #[allow(dead_code)] +#[derive(Copy, Clone)] enum LocStateFields { TpmEstablished, LocAssigned, @@ -63,6 +64,7 @@ enum IntfId2Fields { Did, } +#[derive(Copy, Clone)] enum CtrlStsFields { TpmSts, TpmIdle, @@ -220,7 +222,7 @@ pub struct Tpm { } impl Tpm { - pub fn new(path: String) -> Result { + pub fn new(path: &str) -> Result { let emulator = Emulator::new(path) .map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {e:?}")))?; let mut tpm = Tpm { diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 76c5d72e39..914feaffde 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -493,15 +493,15 @@ impl VfioCommon { vfio_wrapper: Arc, subclass: &dyn PciSubclass, bdf: PciBdf, - snapshot: Option, + snapshot: Option<&Snapshot>, x_nv_gpudirect_clique: Option, ) -> Result { - let pci_configuration_state = - vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| { - VfioPciError::RetrievePciConfigurationState(anyhow!( - "Failed to get PciConfigurationState from Snapshot: {e}" - )) - })?; + let pci_configuration_state = vm_migration::state_from_id(snapshot, PCI_CONFIGURATION_ID) + .map_err(|e| { + VfioPciError::RetrievePciConfigurationState(anyhow!( + "Failed to get PciConfigurationState from Snapshot: {e}" + )) + })?; let configuration = PciConfiguration::new( 0, @@ -541,18 +541,16 @@ impl VfioCommon { "Failed to get VfioCommonState from Snapshot: {e}" )) })?; - let msi_state = - vm_migration::state_from_id(snapshot.as_ref(), MSI_CONFIG_ID).map_err(|e| { - VfioPciError::RetrieveMsiConfigState(anyhow!( - "Failed to get MsiConfigState from Snapshot: {e}" - )) - })?; - let msix_state = - vm_migration::state_from_id(snapshot.as_ref(), MSIX_CONFIG_ID).map_err(|e| { - VfioPciError::RetrieveMsixConfigState(anyhow!( - "Failed to get MsixConfigState from Snapshot: {e}" - )) - })?; + let msi_state = vm_migration::state_from_id(snapshot, MSI_CONFIG_ID).map_err(|e| { + VfioPciError::RetrieveMsiConfigState(anyhow!( + "Failed to get MsiConfigState from Snapshot: {e}" + )) + })?; + let msix_state = vm_migration::state_from_id(snapshot, MSIX_CONFIG_ID).map_err(|e| { + VfioPciError::RetrieveMsixConfigState(anyhow!( + "Failed to get MsixConfigState from Snapshot: {e}" + )) + })?; if let Some(state) = state.as_ref() { vfio_common.set_state(state, msi_state, msix_state)?; @@ -598,7 +596,7 @@ impl VfioCommon { allocator: &Arc>, mmio32_allocator: &mut AddressAllocator, mmio64_allocator: &mut AddressAllocator, - resources: Option>, + resources: Option<&[Resource]>, ) -> Result, PciDeviceError> { let mut bars = Vec::new(); let mut bar_id = VFIO_PCI_BAR0_REGION_INDEX; @@ -614,7 +612,7 @@ impl VfioCommon { let mut flags: u32 = 0; let mut restored_bar_addr = None; - if let Some(resources) = &resources { + if let Some(resources) = resources { for resource in resources { if let Resource::PciBar { index, @@ -1480,7 +1478,7 @@ impl VfioPciDevice { iommu_attached: bool, bdf: PciBdf, memory_slot_allocator: MemorySlotAllocator, - snapshot: Option, + snapshot: Option<&Snapshot>, x_nv_gpudirect_clique: Option, device_path: PathBuf, ) -> Result { @@ -1495,7 +1493,7 @@ impl VfioPciDevice { Arc::new(vfio_wrapper) as Arc, &PciVfioSubclass::VfioSubclass, bdf, - vm_migration::snapshot_from_id(snapshot.as_ref(), VFIO_COMMON_ID), + vm_migration::snapshot_from_id(snapshot, VFIO_COMMON_ID), x_nv_gpudirect_clique, )?; @@ -1508,7 +1506,7 @@ impl VfioPciDevice { iommu_attached, memory_slot_allocator, bdf, - device_path: device_path.clone(), + device_path, }; Ok(vfio_pci_device) @@ -1832,8 +1830,12 @@ impl PciDevice for VfioPciDevice { mmio64_allocator: &mut AddressAllocator, resources: Option>, ) -> Result, PciDeviceError> { - self.common - .allocate_bars(allocator, mmio32_allocator, mmio64_allocator, resources) + self.common.allocate_bars( + allocator, + mmio32_allocator, + mmio64_allocator, + resources.as_deref(), + ) } fn free_bars( diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index 5aa2266cab..248f87272a 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -79,7 +79,7 @@ impl VfioUserPciDevice { legacy_interrupt_group: Option>, bdf: PciBdf, memory_slot_allocator: MemorySlotAllocator, - snapshot: Option, + snapshot: Option<&Snapshot>, ) -> Result { let resettable = client.lock().unwrap().resettable(); if resettable { @@ -100,7 +100,7 @@ impl VfioUserPciDevice { Arc::new(vfio_wrapper) as Arc, &PciVfioUserSubclass::VfioUserSubclass, bdf, - vm_migration::snapshot_from_id(snapshot.as_ref(), VFIO_COMMON_ID), + vm_migration::snapshot_from_id(snapshot, VFIO_COMMON_ID), None, ) .map_err(VfioUserPciDeviceError::CreateVfioCommon)?; @@ -396,8 +396,12 @@ impl PciDevice for VfioUserPciDevice { mmio64_allocator: &mut AddressAllocator, resources: Option>, ) -> Result, PciDeviceError> { - self.common - .allocate_bars(allocator, mmio32_allocator, mmio64_allocator, resources) + self.common.allocate_bars( + allocator, + mmio32_allocator, + mmio64_allocator, + resources.as_deref(), + ) } fn free_bars( diff --git a/rate_limiter/src/lib.rs b/rate_limiter/src/lib.rs index 029288ad2f..0239bd4ef5 100644 --- a/rate_limiter/src/lib.rs +++ b/rate_limiter/src/lib.rs @@ -245,6 +245,7 @@ impl TokenBucket { } /// Enum that describes the type of token used. +#[derive(Copy, Clone)] pub enum TokenType { /// Token type used for bandwidth limiting. Bytes, diff --git a/src/bin/ch-remote.rs b/src/bin/ch-remote.rs index 8d89b607b9..39bfaeecd6 100644 --- a/src/bin/ch-remote.rs +++ b/src/bin/ch-remote.rs @@ -399,7 +399,7 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu .get_one::("net_config") .unwrap(), )?; - simple_api_command_with_fds(socket, "PUT", "add-net", Some(&net_config), fds) + simple_api_command_with_fds(socket, "PUT", "add-net", Some(&net_config), &fds) .map_err(Error::HttpApiClient) } Some("add-user-device") => { @@ -454,7 +454,7 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu .get_one::("restore_config") .unwrap(), )?; - simple_api_command_with_fds(socket, "PUT", "restore", Some(&restore_config), fds) + simple_api_command_with_fds(socket, "PUT", "restore", Some(&restore_config), &fds) .map_err(Error::HttpApiClient) } Some("coredump") => { diff --git a/tpm/src/emulator.rs b/tpm/src/emulator.rs index a0e9ab6ecf..3699c3da7c 100644 --- a/tpm/src/emulator.rs +++ b/tpm/src/emulator.rs @@ -86,7 +86,7 @@ impl Emulator { /// /// * `path` - A path to the Unix Domain Socket swtpm is listening on /// - pub fn new(path: String) -> Result { + pub fn new(path: &str) -> Result { if !Path::new(&path).exists() { return Err(Error::InitializeEmulator(anyhow!( "The input TPM Socket path: {path:?} does not exist" diff --git a/tpm/src/socket.rs b/tpm/src/socket.rs index ca4ae54358..4fe5b6370b 100644 --- a/tpm/src/socket.rs +++ b/tpm/src/socket.rs @@ -58,8 +58,8 @@ impl SocketDev { } } - pub fn init(&mut self, path: String) -> Result<()> { - self.connect(&path)?; + pub fn init(&mut self, path: &str) -> Result<()> { + self.connect(path)?; Ok(()) } diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 5ad48608ee..0e28377b6c 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -203,7 +203,7 @@ struct VhostUserBlkBackend { impl VhostUserBlkBackend { fn new( - image_path: String, + image_path: &str, num_queues: usize, rdonly: bool, direct: bool, @@ -217,7 +217,7 @@ impl VhostUserBlkBackend { if direct { options.custom_flags(libc::O_DIRECT); } - let image: File = options.open(&image_path).unwrap(); + let image: File = options.open(image_path).unwrap(); let mut raw_img: qcow::RawFile = qcow::RawFile::new(image, direct); let serial = build_serial(&PathBuf::from(&image_path)); @@ -510,7 +510,7 @@ pub fn start_block_backend(backend_command: &str) { let blk_backend = Arc::new(RwLock::new( VhostUserBlkBackend::new( - backend_config.path, + &backend_config.path, backend_config.num_queues, backend_config.readonly, backend_config.direct, diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 93c07763e4..898b13616a 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -800,8 +800,8 @@ impl Block { self.disk_path.display() ); let fd = self.disk_image.fd(); - fcntl::try_acquire_lock(fd, lock_type, granularity).map_err(|error| { - let current_lock = get_lock_state(fd, granularity); + fcntl::try_acquire_lock(&fd, lock_type, granularity).map_err(|error| { + let current_lock = get_lock_state(&fd, granularity); // Don't propagate the error to the outside, as it is not useful at all. Instead, // we try to log additional help to the user. if let Ok(current_lock) = current_lock { @@ -830,7 +830,8 @@ impl Block { // It is very unlikely that this fails; // Should we remove the Result to simplify the error propagation on // higher levels? - fcntl::clear_lock(self.disk_image.fd(), granularity).map_err(|error| Error::LockDiskImage { + let fd = self.disk_image.fd(); + fcntl::clear_lock(&fd, granularity).map_err(|error| Error::LockDiskImage { path: self.disk_path.clone(), error, lock_type: LockType::Unlock, diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 0e1eb2ccef..408611e29a 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -389,7 +389,7 @@ impl VirtioPciDevice { use_64bit_bar: bool, dma_handler: Option>, pending_activations: Arc>>, - snapshot: Option, + snapshot: Option<&Snapshot>, ) -> Result { let mut locked_device = device.lock().unwrap(); let mut queue_evts = Vec::new(); @@ -423,8 +423,8 @@ impl VirtioPciDevice { )) })?; - let msix_state = vm_migration::state_from_id(snapshot.as_ref(), pci::MSIX_CONFIG_ID) - .map_err(|e| { + let msix_state = + vm_migration::state_from_id(snapshot, pci::MSIX_CONFIG_ID).map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( "Failed to get MsixConfigState from Snapshot: {e}" )) @@ -462,13 +462,11 @@ impl VirtioPciDevice { }; let pci_configuration_state = - vm_migration::state_from_id(snapshot.as_ref(), pci::PCI_CONFIGURATION_ID).map_err( - |e| { - VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( - "Failed to get PciConfigurationState from Snapshot: {e}" - )) - }, - )?; + vm_migration::state_from_id(snapshot, pci::PCI_CONFIGURATION_ID).map_err(|e| { + VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( + "Failed to get PciConfigurationState from Snapshot: {e}" + )) + })?; let configuration = PciConfiguration::new( VIRTIO_PCI_VENDOR_ID, @@ -485,13 +483,11 @@ impl VirtioPciDevice { ); let common_config_state = - vm_migration::state_from_id(snapshot.as_ref(), VIRTIO_PCI_COMMON_CONFIG_ID).map_err( - |e| { - VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( - "Failed to get VirtioPciCommonConfigState from Snapshot: {e}" - )) - }, - )?; + vm_migration::state_from_id(snapshot, VIRTIO_PCI_COMMON_CONFIG_ID).map_err(|e| { + VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( + "Failed to get VirtioPciCommonConfigState from Snapshot: {e}" + )) + })?; let common_config = if let Some(common_config_state) = common_config_state { VirtioPciCommonConfig::new(common_config_state, access_platform) diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 49248a6780..725f215c77 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -219,7 +219,7 @@ impl Vdpa { &mut self, mem: &GuestMemoryMmap, virtio_interrupt: &dyn VirtioInterrupt, - queues: Vec<(usize, Queue, EventFd)>, + queues: &[(usize, Queue, EventFd)], ) -> Result<()> { assert!(self.vhost.is_some()); self.vhost @@ -434,7 +434,7 @@ impl VirtioDevice for Vdpa { virtio_interrupt: Arc, queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { - self.activate_vdpa(&mem.memory(), virtio_interrupt.as_ref(), queues) + self.activate_vdpa(&mem.memory(), virtio_interrupt.as_ref(), &queues) .map_err(ActivateError::ActivateVdpa)?; // Store the virtio interrupt handler as we need to return it on reset diff --git a/vm-allocator/src/gsi.rs b/vm-allocator/src/gsi.rs index 8e0a340217..d58670a43c 100644 --- a/vm-allocator/src/gsi.rs +++ b/vm-allocator/src/gsi.rs @@ -40,14 +40,14 @@ pub struct GsiAllocator { impl GsiAllocator { #[cfg(target_arch = "x86_64")] /// New GSI allocator - pub fn new(apics: Vec) -> Self { + pub fn new(apics: &[GsiApic]) -> Self { let mut allocator = GsiAllocator { apics: BTreeMap::new(), next_irq: 0xffff_ffff, next_gsi: 0, }; - for apic in &apics { + for apic in apics { if apic.base < allocator.next_irq { allocator.next_irq = apic.base; } diff --git a/vm-allocator/src/system.rs b/vm-allocator/src/system.rs index bad0272c37..02ea86c6a1 100644 --- a/vm-allocator/src/system.rs +++ b/vm-allocator/src/system.rs @@ -19,7 +19,7 @@ use crate::page_size::get_page_size; /// /// # Example - Use the `SystemAddress` builder. /// -/// ``` +/// ```rust /// # #[cfg(target_arch = "x86_64")] /// # use vm_allocator::{GsiApic, SystemAllocator}; /// # #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] @@ -29,7 +29,7 @@ use crate::page_size::get_page_size; /// GuestAddress(0x1000), /// 0x10000, /// GuestAddress(0x10000000), 0x10000000, -/// #[cfg(target_arch = "x86_64")] vec![GsiApic::new(5, 19)]).unwrap(); +/// #[cfg(target_arch = "x86_64")] &[GsiApic::new(5, 19)]).unwrap(); /// #[cfg(target_arch = "x86_64")] /// assert_eq!(allocator.allocate_irq(), Some(5)); /// #[cfg(target_arch = "aarch64")] @@ -59,14 +59,14 @@ impl SystemAllocator { /// * `io_size` - (X86) The size of IO memory. /// * `platform_mmio_base` - The starting address of platform MMIO memory. /// * `platform_mmio_size` - The size of platform MMIO memory. - /// * `apics` - (X86) Vector of APIC's. + /// * `apics` - (X86) slice of APIC's. /// pub fn new( io_base: GuestAddress, io_size: GuestUsize, platform_mmio_base: GuestAddress, platform_mmio_size: GuestUsize, - #[cfg(target_arch = "x86_64")] apics: Vec, + #[cfg(target_arch = "x86_64")] apics: &[GsiApic], ) -> Option { Some(SystemAllocator { io_address_space: AddressAllocator::new(io_base, io_size)?, diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 16c0a6df87..921ae5b3db 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -156,8 +156,8 @@ impl Snapshot { } } -pub fn snapshot_from_id(snapshot: Option<&Snapshot>, id: &str) -> Option { - snapshot.and_then(|s| s.snapshots.get(id).cloned()) +pub fn snapshot_from_id<'a>(snapshot: Option<&'a Snapshot>, id: &str) -> Option<&'a Snapshot> { + snapshot.and_then(|s| s.snapshots.get(id)) } pub fn state_from_id<'a, T>(s: Option<&'a Snapshot>, id: &str) -> Result, MigratableError> diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index ee5186e6d5..b3d7f71ef0 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -901,7 +901,11 @@ impl CpuManager { Ok(()) } - fn create_vcpu(&mut self, cpu_id: u32, snapshot: Option) -> Result>> { + fn create_vcpu( + &mut self, + cpu_id: u32, + snapshot: Option<&Snapshot>, + ) -> Result>> { info!("Creating vCPU: cpu_id = {cpu_id}"); #[cfg(target_arch = "x86_64")] @@ -1006,7 +1010,7 @@ impl CpuManager { fn create_vcpus( &mut self, desired_vcpus: u32, - snapshot: Option, + snapshot: Option<&Snapshot>, ) -> Result>>> { let mut vcpus: Vec>> = vec![]; info!( @@ -1027,7 +1031,7 @@ impl CpuManager { cpu_id, // TODO: The special format of the CPU id can be removed once // ready to break live upgrade. - snapshot_from_id(snapshot.as_ref(), cpu_id.to_string().as_str()), + snapshot_from_id(snapshot, cpu_id.to_string().as_str()), )?); } @@ -1386,7 +1390,7 @@ impl CpuManager { pub fn create_boot_vcpus( &mut self, - snapshot: Option, + snapshot: Option<&Snapshot>, ) -> Result>>> { trace_scoped!("create_boot_vcpus"); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 7766caa802..245be2c856 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1155,7 +1155,7 @@ impl DeviceManager { force_iommu: bool, boot_id_list: BTreeSet, #[cfg(not(target_arch = "riscv64"))] timestamp: Instant, - snapshot: Option, + snapshot: Option<&Snapshot>, dynamic: bool, ) -> DeviceManagerResult>> { trace_scoped!("DeviceManager::new"); @@ -1367,7 +1367,7 @@ impl DeviceManager { timestamp, pending_activations: Arc::new(Mutex::new(Vec::default())), acpi_platform_addresses: AcpiPlatformAddresses::default(), - snapshot, + snapshot: snapshot.cloned(), rate_limit_groups, mmio_regions: Arc::new(Mutex::new(Vec::new())), #[cfg(feature = "fw_cfg")] @@ -1795,7 +1795,7 @@ impl DeviceManager { ioapic::Ioapic::new( id.clone(), APIC_START, - Arc::clone(&self.msi_interrupt_manager), + self.msi_interrupt_manager.as_ref(), state_from_id(self.snapshot.as_ref(), id.as_str()) .map_err(DeviceManagerError::RestoreGetState)?, ) @@ -2489,7 +2489,7 @@ impl DeviceManager { tpm_path: PathBuf, ) -> DeviceManagerResult>> { // Create TPM Device - let tpm = devices::tpm::Tpm::new(tpm_path.to_str().unwrap().to_string()).map_err(|e| { + let tpm = devices::tpm::Tpm::new(tpm_path.to_str().unwrap()).map_err(|e| { DeviceManagerError::CreateTpmDevice(anyhow!("Failed to create TPM Device : {e:?}")) })?; let tpm = Arc::new(Mutex::new(tpm)); diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index cb1ce9d54e..1c1eca79be 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -965,7 +965,7 @@ impl Vmm { self.console_info.clone(), self.console_resize_pipe.clone(), Arc::clone(&self.original_termios_opt), - Some(snapshot), + Some(&snapshot), ) .map_err(|e| { MigratableError::MigrateReceive(anyhow!("Error creating VM from snapshot: {e:?}")) @@ -1334,7 +1334,7 @@ impl Vmm { self.console_info.clone(), self.console_resize_pipe.clone(), Arc::clone(&self.original_termios_opt), - Some(snapshot), + Some(&snapshot), Some(source_url), Some(prefault), )?; diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 8bfcd6d822..8f4ff649b3 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -1169,7 +1169,7 @@ impl MemoryManager { start_of_platform_device_area, PLATFORM_DEVICE_AREA_SIZE, #[cfg(target_arch = "x86_64")] - vec![GsiApic::new( + &[GsiApic::new( X86_64_IRQ_BASE, ioapic::NUM_IOAPIC_PINS as u32 - X86_64_IRQ_BASE, )], diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 526649c574..fcd6fbfbec 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -543,7 +543,7 @@ impl Vm { console_info: Option, console_resize_pipe: Option>, original_termios: Arc>>, - snapshot: Option, + snapshot: Option<&Snapshot>, ) -> Result { trace_scoped!("Vm::new_from_memory_manager"); @@ -658,7 +658,7 @@ impl Vm { boot_id_list, #[cfg(not(target_arch = "riscv64"))] timestamp, - snapshot_from_id(snapshot.as_ref(), DEVICE_MANAGER_SNAPSHOT_ID), + snapshot_from_id(snapshot, DEVICE_MANAGER_SNAPSHOT_ID), dynamic, ) .map_err(Error::DeviceManager)?; @@ -723,7 +723,7 @@ impl Vm { cpu_manager .lock() .unwrap() - .create_boot_vcpus(snapshot_from_id(snapshot.as_ref(), CPU_MANAGER_SNAPSHOT_ID)) + .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) .map_err(Error::CpuManager)?; // For KVM, we need to create interrupt controller after we create boot vcpus. @@ -992,7 +992,7 @@ impl Vm { console_info: Option, console_resize_pipe: Option>, original_termios: Arc>>, - snapshot: Option, + snapshot: Option<&Snapshot>, source_url: Option<&str>, prefault: Option, ) -> Result { @@ -1035,31 +1035,30 @@ impl Vm { vm_config.lock().unwrap().cpus.max_phys_bits, ); - let memory_manager = if let Some(snapshot) = - snapshot_from_id(snapshot.as_ref(), MEMORY_MANAGER_SNAPSHOT_ID) - { - MemoryManager::new_from_snapshot( - &snapshot, - vm.clone(), - &vm_config.lock().unwrap().memory.clone(), - source_url, - prefault.unwrap(), - phys_bits, - ) - .map_err(Error::MemoryManager)? - } else { - MemoryManager::new( - vm.clone(), - &vm_config.lock().unwrap().memory.clone(), - None, - phys_bits, - #[cfg(feature = "tdx")] - tdx_enabled, - None, - None, - ) - .map_err(Error::MemoryManager)? - }; + let memory_manager = + if let Some(snapshot) = snapshot_from_id(snapshot, MEMORY_MANAGER_SNAPSHOT_ID) { + MemoryManager::new_from_snapshot( + snapshot, + vm.clone(), + &vm_config.lock().unwrap().memory.clone(), + source_url, + prefault.unwrap(), + phys_bits, + ) + .map_err(Error::MemoryManager)? + } else { + MemoryManager::new( + vm.clone(), + &vm_config.lock().unwrap().memory.clone(), + None, + phys_bits, + #[cfg(feature = "tdx")] + tdx_enabled, + None, + None, + ) + .map_err(Error::MemoryManager)? + }; Vm::new_from_memory_manager( vm_config, From e3fb425615029f36076a34bfb75780337a14ca92 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 25 Nov 2025 17:02:42 +0000 Subject: [PATCH 0356/1893] event_monitor: Send events to info! level logging The resulting output looks like: Event: source = virtio-device event = activated id = _disk0 See: #7484 Signed-off-by: Rob Bradford --- Cargo.lock | 1 + event_monitor/Cargo.toml | 1 + event_monitor/src/lib.rs | 22 +++++++++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 722d1ce6e6..66e62e1f46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -710,6 +710,7 @@ version = "0.1.0" dependencies = [ "flume", "libc", + "log", "serde", "serde_json", ] diff --git a/event_monitor/Cargo.toml b/event_monitor/Cargo.toml index 41d3102807..18ac2567c3 100644 --- a/event_monitor/Cargo.toml +++ b/event_monitor/Cargo.toml @@ -7,6 +7,7 @@ version = "0.1.0" [dependencies] flume = { workspace = true } libc = { workspace = true } +log = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } diff --git a/event_monitor/src/lib.rs b/event_monitor/src/lib.rs index 27b7f7b90c..c7d6341342 100644 --- a/event_monitor/src/lib.rs +++ b/event_monitor/src/lib.rs @@ -6,11 +6,12 @@ use std::borrow::Cow; use std::collections::HashMap; use std::fs::File; -use std::io; use std::os::unix::io::AsRawFd; use std::sync::{Arc, OnceLock}; use std::time::{Duration, Instant}; +use std::{fmt, io}; +use log::info; use serde::Serialize; static MONITOR: OnceLock = OnceLock::new(); @@ -88,12 +89,31 @@ pub fn set_monitor(file: Option) -> io::Result { Ok(monitor) } +struct PropertiesFormatter<'a>(&'a Option<&'a HashMap, Cow<'a, str>>>); +impl<'a> fmt::Display for PropertiesFormatter<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(map) = self.0 { + for (i, (key, value)) in map.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{key} = {value}")?; + } + } + Ok(()) + } +} + pub fn event_log(source: &str, event: &str, properties: Option<&HashMap, Cow>>) { // `MONITOR` is always in a valid state (None or Some), because it is set // only once before any threads are spawned, and it's not mutated // afterwards. This function only creates immutable references to `MONITOR`. // Because `MONITOR.tx` is `Sync`, it's safe to share `MONITOR` across // threads, making this function thread-safe. + info!( + "Event: source = {source} event = {event} {}", + PropertiesFormatter(&properties) + ); if let Some(monitor_handle) = MONITOR.get().as_ref() { let event = Event { timestamp: monitor_handle.start.elapsed(), From e7cda177ccb6af57aa5d2adf411494fd07b5d420 Mon Sep 17 00:00:00 2001 From: Chengyu Fu Date: Wed, 26 Nov 2025 10:14:43 +0800 Subject: [PATCH 0357/1893] hypervisor: kvm: Remove unnecessary `.to_vec()` in set_state .. to avoid copy elements. Signed-off-by: Chengyu Fu --- hypervisor/src/kvm/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 31a6296b69..ce6e138294 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -2601,9 +2601,9 @@ impl cpu::Vcpu for KvmVcpu { // Skip the first bad MSR let start_pos = faulty_msr_index + 1; - let sub_msr_entries = state.msrs[start_pos..].to_vec(); + let sub_msr_entries = &state.msrs[start_pos..]; - let num_msrs = self.set_msrs(&sub_msr_entries)?; + let num_msrs = self.set_msrs(sub_msr_entries)?; if num_msrs == sub_msr_entries.len() { break; From 41382879d35dbfb199170af790f87f76c0af00fc Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Wed, 12 Nov 2025 20:07:06 +0800 Subject: [PATCH 0358/1893] seccomp: add arch_prctl syscall for amx When enabling amx feature, we should call arch_prctl to request permission to use tile data for guest. The permission should be requested before the first vcpu is created, so we need to call arch_prctl in vmm thread. This patch adds the arch_prctl syscall for vmm_thread_rules. Fixes: #7516 Signed-off-by: Songqian Li --- vmm/src/seccomp_filters.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index c78c97f4e0..c4a1663809 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -550,6 +550,8 @@ fn vmm_thread_rules( (libc::SYS_accept4, vec![]), #[cfg(target_arch = "x86_64")] (libc::SYS_access, vec![]), + #[cfg(target_arch = "x86_64")] + (libc::SYS_arch_prctl, vec![]), (libc::SYS_bind, vec![]), (libc::SYS_brk, vec![]), (libc::SYS_clock_gettime, vec![]), From 2f8edc182ebf076b0fedccc182863ec7fe5063ec Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 25 Nov 2025 22:32:49 +0000 Subject: [PATCH 0359/1893] performance-metrics: Respect queue size in block tests Signed-off-by: Wei Liu --- performance-metrics/src/performance_tests.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 6a6cdfaece..e16bd938e9 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -365,6 +365,7 @@ pub fn performance_boot_time_pmem(control: &PerformanceTestControl) -> f64 { pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { let test_timeout = control.test_timeout; let num_queues = control.num_queues.unwrap(); + let queue_size = control.queue_size.unwrap(); let (fio_ops, bandwidth) = control.fio_control.as_ref().unwrap(); let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); @@ -394,7 +395,8 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - format!("path={BLK_IO_TEST_IMG}").as_str(), + format!("path={BLK_IO_TEST_IMG},queue_size={queue_size},num_queues={num_queues}") + .as_str(), ]) .default_net() .args(["--api-socket", &api_socket]) @@ -409,7 +411,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { let fio_command = format!( "sudo fio --filename=/dev/vdc --name=test --output-format=json \ - --direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \ + --direct=1 --bs=4k --ioengine=io_uring --iodepth={queue_size} \ --rw={fio_ops} --runtime={test_timeout} --numjobs={num_queues}" ); let output = guest From 8e5dd296de87c5f26fe72ffa716d272323fb150b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:18:33 +0000 Subject: [PATCH 0360/1893] build: Bump crate-ci/typos from 1.39.2 to 1.40.0 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.39.2 to 1.40.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.39.2...v1.40.0) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.40.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index b38f9205a5..c97487d198 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.39.2 + - uses: crate-ci/typos@v1.40.0 From 0d884d3f508e7c79d8b3e0c7170ca5c9ad89ced8 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 21 Nov 2025 15:15:33 +0100 Subject: [PATCH 0361/1893] arch: Mask (out) extended AMX features The Intel Granite Rapids processors include more AMX related features that are advertised in leaf 0x7 subleaf 0x1. If the VM is not configured to support AMX (the default) then these feature bits need to be masked out. Furthermore Tile information and TMUL information in leaves 0x1d and 0x1e respectively are also purely related to AMX and should also be zeroed whenever AMX support is disabled. Signed-off-by: Oliver Anderson On-behalf-of: SAP --- arch/src/x86_64/mod.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index ab38359936..17bae5f5a8 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -50,6 +50,9 @@ const AMX_BF16: u8 = 22; // AMX tile computation on bfloat16 numbers const AMX_TILE: u8 = 24; // AMX tile load/store instructions 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 + // KVM feature bits #[cfg(feature = "tdx")] const KVM_FEATURE_CLOCKSOURCE_BIT: u8 = 0; @@ -638,8 +641,14 @@ pub fn generate_common_cpuid( match entry.function { // Clear AMX related bits if the AMX feature is not enabled 0x7 => { - if !config.amx && entry.index == 0 { - entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8)); + 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 => @@ -661,6 +670,25 @@ pub fn generate_common_cpuid( } } } + 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; + } + } + // 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 { From ac5fc69b2e14dcc6d5c5380c01a9e8602bc6fa65 Mon Sep 17 00:00:00 2001 From: Praveen K Paladugu Date: Mon, 24 Nov 2025 15:03:09 -0600 Subject: [PATCH 0362/1893] vmm: Fix fd leak in socket connection handling Previously, `into_raw_fd()` transferred fd ownership to epoll while keeping separate clones in `reader` and `writer`, causing leaks when the stream was closed. Now, `reader` owns the fd and epoll borrows it. When the FD is closed in the kernel, reader will be reset to None. Signed-off-by: Praveen K Paladugu --- vmm/src/serial_manager.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index efb8d67e82..4c886c73ea 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -6,7 +6,7 @@ use std::fs::File; use std::io::Read; use std::net::Shutdown; -use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; +use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::net::UnixStream; use std::panic::AssertUnwindSafe; use std::path::PathBuf; @@ -329,20 +329,19 @@ impl SerialManager { listener.accept().map_err(Error::AcceptConnection)?; let writer = unix_stream.try_clone().map_err(Error::CloneUnixStream)?; - reader = Some( - unix_stream.try_clone().map_err(Error::CloneUnixStream)?, - ); epoll::ctl( epoll_fd, epoll::ControlOptions::EPOLL_CTL_ADD, - unix_stream.into_raw_fd(), + unix_stream.as_raw_fd(), epoll::Event::new( epoll::Events::EPOLLIN, EpollDispatch::File as u64, ), ) .map_err(Error::Epoll)?; + + reader = Some(unix_stream); serial.lock().unwrap().set_out(Some(Box::new(writer))); } EpollDispatch::File => { From ed4af3a005e31cd67e1ff45f8afacacc4dae0497 Mon Sep 17 00:00:00 2001 From: Praveen K Paladugu Date: Wed, 26 Nov 2025 11:45:45 -0600 Subject: [PATCH 0363/1893] vmm: fix the initial flush while using PTY Fix the condition to correctly identify a new PTY connection. Fixes: 287887c99 (vmm: fix console IO safety) Signed-off-by: Praveen K Paladugu --- vmm/src/serial_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 4c886c73ea..5f8de1874a 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -294,7 +294,7 @@ impl SerialManager { } }; - if matches!(in_file, ConsoleOutput::Socket(_)) && num_events == 0 { + if matches!(in_file, ConsoleOutput::Pty(_)) && num_events == 0 { // This very specific case happens when the serial is connected // to a PTY. We know EPOLLHUP is always present when there's nothing // connected at the other end of the PTY. That's why getting no event From c53781bf5f17726c94a1d2cfd7e1b799b4ad92bb Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 26 Nov 2025 14:00:44 +0100 Subject: [PATCH 0364/1893] misc: clippy: add needless_pass_by_value This is a follow-up of [0]. # Advantages - This saves dozens of unneeded clone()s across the whole code base - Makes it much easier to reason about how parameters are used (often we passed owned Arc/Rc versions without actually needing ownership) # Exceptions For certain code paths, the alternatives would require awkward or overly complex code, and in some cases the functions are the logical owners of the values they take. In those cases, I've added #[allow(clippy::needless_pass_by_value)]. This does not mean that one should not improve this in the future. [0] 6a86c157afde79e734bc35e1065403d532c51f34 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 3 +- arch/src/aarch64/fdt.rs | 9 +- arch/src/aarch64/mod.rs | 4 +- arch/src/riscv64/fdt.rs | 4 +- arch/src/riscv64/mod.rs | 2 +- devices/src/aia.rs | 6 +- devices/src/gic.rs | 6 +- devices/src/ioapic.rs | 2 +- devices/src/tpm.rs | 5 + hypervisor/src/kvm/aarch64/gic/mod.rs | 15 +-- hypervisor/src/kvm/mod.rs | 4 +- hypervisor/src/kvm/riscv64/aia.rs | 5 +- hypervisor/src/mshv/aarch64/gic/mod.rs | 2 +- hypervisor/src/mshv/mod.rs | 2 +- hypervisor/src/vm.rs | 4 +- pci/src/bus.rs | 1 + src/bin/ch-remote.rs | 1 + src/main.rs | 1 + test_infra/src/lib.rs | 9 +- tests/integration.rs | 127 ++++++++++-------- virtio-devices/src/balloon.rs | 6 +- virtio-devices/src/block.rs | 6 +- virtio-devices/src/console.rs | 6 +- virtio-devices/src/epoll_helper.rs | 14 +- virtio-devices/src/iommu.rs | 6 +- virtio-devices/src/mem.rs | 10 +- virtio-devices/src/net.rs | 12 +- virtio-devices/src/pmem.rs | 6 +- virtio-devices/src/rng.rs | 6 +- virtio-devices/src/seccomp_filters.rs | 1 + .../src/transport/pci_common_config.rs | 5 + virtio-devices/src/vhost_user/blk.rs | 4 +- virtio-devices/src/vhost_user/fs.rs | 4 +- virtio-devices/src/vhost_user/mod.rs | 25 ++-- virtio-devices/src/vhost_user/net.rs | 6 +- .../src/vhost_user/vu_common_ctrl.rs | 4 +- virtio-devices/src/vsock/device.rs | 6 +- virtio-devices/src/watchdog.rs | 6 +- vm-device/src/bus.rs | 1 + vmm/src/acpi.rs | 1 + vmm/src/api/http/mod.rs | 1 + vmm/src/api/mod.rs | 1 + vmm/src/config.rs | 1 + vmm/src/console_devices.rs | 16 +-- vmm/src/cpu.rs | 5 +- vmm/src/device_manager.rs | 49 +++---- vmm/src/igvm/igvm_loader.rs | 2 +- vmm/src/igvm/mod.rs | 2 +- vmm/src/lib.rs | 30 ++--- vmm/src/memory_manager.rs | 8 +- vmm/src/seccomp_filters.rs | 1 + vmm/src/sigwinch_listener.rs | 10 +- vmm/src/vm.rs | 31 +++-- 53 files changed, 273 insertions(+), 231 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 155e2fc17a..54fc855451 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -174,8 +174,7 @@ assertions_on_result_states = "deny" if_not_else = "deny" manual_string_new = "deny" map_unwrap_or = "deny" -# Very helpful to uncover costly clones, but unfortunately also a rabbit hole. -#needless_pass_by_value = "deny" +needless_pass_by_value = "deny" redundant_else = "deny" semicolon_if_nothing_returned = "deny" undocumented_unsafe_blocks = "deny" diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index f466109e88..5ce1bea206 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -88,6 +88,7 @@ pub enum Error { } type Result = result::Result; +#[derive(Copy, Clone)] pub enum CacheLevel { /// L1 data cache L1D = 0, @@ -207,7 +208,7 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool { pub fn create_fdt( guest_mem: &GuestMemoryMmap, cmdline: &str, - vcpu_mpidr: Vec, + vcpu_mpidr: &[u64], vcpu_topology: Option<(u16, u16, u16, u16)>, device_info: &HashMap<(DeviceType, String), T, S>, gic_device: &Arc>, @@ -234,7 +235,7 @@ pub fn create_fdt, guest_mem: &GuestMemoryMmap) -> Result<()> { +pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Result<()> { // Write FDT to memory. guest_mem - .write_slice(fdt_final.as_slice(), super::layout::FDT_START) + .write_slice(fdt_final, super::layout::FDT_START) .map_err(Error::WriteFdtToMemory)?; Ok(()) } diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index 910c14998a..79c2670920 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -125,7 +125,7 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> { pub fn configure_system( guest_mem: &GuestMemoryMmap, cmdline: &str, - vcpu_mpidr: Vec, + vcpu_mpidr: &[u64], vcpu_topology: Option<(u16, u16, u16, u16)>, device_info: &HashMap<(DeviceType, String), T, S>, initrd: &Option, @@ -154,7 +154,7 @@ pub fn configure_system, guest_mem: &GuestMemoryMmap) -> Result<()> { +pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Result<()> { // Write FDT to memory. guest_mem - .write_slice(fdt_final.as_slice(), super::layout::FDT_START) + .write_slice(fdt_final, super::layout::FDT_START) .map_err(Error::WriteFdtToMemory)?; Ok(()) } diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index 0ee66db32d..8b89b94a5d 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -187,7 +187,7 @@ pub fn configure_system>, @@ -51,9 +52,8 @@ impl Aia { }) .map_err(Error::CreateInterruptSourceGroup)?; - let vaia = vm - .create_vaia(Aia::create_default_config(vcpu_count as u64)) - .map_err(Error::CreateAia)?; + let config = Aia::create_default_config(vcpu_count as u64); + let vaia = vm.create_vaia(&config).map_err(Error::CreateAia)?; let aia = Aia { interrupt_source_group, diff --git a/devices/src/gic.rs b/devices/src/gic.rs index fd7199d00b..65e8384023 100644 --- a/devices/src/gic.rs +++ b/devices/src/gic.rs @@ -38,6 +38,7 @@ pub struct Gic { } impl Gic { + #[allow(clippy::needless_pass_by_value)] pub fn new( vcpu_count: u32, interrupt_manager: Arc>, @@ -50,9 +51,8 @@ impl Gic { }) .map_err(Error::CreateInterruptSourceGroup)?; - let vgic = vm - .create_vgic(Gic::create_default_config(vcpu_count as u64)) - .map_err(Error::CreateGic)?; + let config = Gic::create_default_config(vcpu_count as u64); + let vgic = vm.create_vgic(&config).map_err(Error::CreateGic)?; let gic = Gic { interrupt_source_group, diff --git a/devices/src/ioapic.rs b/devices/src/ioapic.rs index 660531eb62..ba05c1ed5b 100644 --- a/devices/src/ioapic.rs +++ b/devices/src/ioapic.rs @@ -192,7 +192,7 @@ impl Ioapic { id: String, apic_address: GuestAddress, interrupt_manager: &dyn InterruptManager, - state: Option, + state: Option<&IoapicState>, ) -> Result { let interrupt_source_group = interrupt_manager .create_group(MsiIrqGroupConfig { diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs index 15494e3233..86866b2e75 100644 --- a/devices/src/tpm.rs +++ b/devices/src/tpm.rs @@ -36,12 +36,14 @@ enum LocStateFields { TpmRegValidSts, } +#[derive(Copy, Clone)] enum LocStsFields { Granted, BeenSeized, } #[allow(dead_code)] +#[derive(Copy, Clone)] enum IntfIdFields { InterfaceType, InterfaceVersion, @@ -59,6 +61,7 @@ enum IntfIdFields { } #[allow(dead_code)] +#[derive(Copy, Clone)] enum IntfId2Fields { Vid, Did, @@ -70,6 +73,7 @@ enum CtrlStsFields { TpmIdle, } +#[derive(Copy, Clone)] enum CrbRegister { LocState(LocStateFields), LocSts(LocStsFields), @@ -102,6 +106,7 @@ const CRB_LOC_CTRL_REQUEST_ACCESS: u32 = 1 << 0; const CRB_LOC_CTRL_RELINQUISH: u32 = 1 << 1; const CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: u32 = 1 << 3; const CRB_LOC_STS: u32 = 0x0C; + const fn get_crb_loc_sts_field(f: LocStsFields) -> (u32, u32, u32) { let (offset, len) = match f { LocStsFields::Granted => (0, 1), diff --git a/hypervisor/src/kvm/aarch64/gic/mod.rs b/hypervisor/src/kvm/aarch64/gic/mod.rs index 2e5523e972..8dcf060781 100644 --- a/hypervisor/src/kvm/aarch64/gic/mod.rs +++ b/hypervisor/src/kvm/aarch64/gic/mod.rs @@ -262,7 +262,7 @@ impl KvmGicV3Its { } /// Method to initialize the GIC device - pub fn new(vm: &dyn Vm, config: VgicConfig) -> Result { + pub fn new(vm: &dyn Vm, config: &VgicConfig) -> Result { // This is inside KVM module let vm = vm.as_any().downcast_ref::().expect("Wrong VM type?"); @@ -509,7 +509,7 @@ mod unit_tests { let hv = crate::new().unwrap(); let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); - KvmGicV3Its::new(&*vm, create_test_vgic_config()).unwrap(); + KvmGicV3Its::new(&*vm, &create_test_vgic_config()).unwrap(); } #[test] @@ -517,7 +517,7 @@ mod unit_tests { let hv = crate::new().unwrap(); let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _ = vm.create_vcpu(0, None).unwrap(); - let gic = KvmGicV3Its::new(&*vm, create_test_vgic_config()).expect("Cannot create gic"); + let gic = KvmGicV3Its::new(&*vm, &create_test_vgic_config()).expect("Cannot create gic"); let state = get_dist_regs(&gic.device).unwrap(); assert_eq!(state.len(), 568); @@ -530,7 +530,7 @@ mod unit_tests { let hv = crate::new().unwrap(); let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _ = vm.create_vcpu(0, None).unwrap(); - let gic = KvmGicV3Its::new(&*vm, create_test_vgic_config()).expect("Cannot create gic"); + let gic = KvmGicV3Its::new(&*vm, &create_test_vgic_config()).expect("Cannot create gic"); let gicr_typer = vec![123]; let state = get_redist_regs(&gic.device, &gicr_typer).unwrap(); @@ -545,7 +545,7 @@ mod unit_tests { let hv = crate::new().unwrap(); let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _ = vm.create_vcpu(0, None).unwrap(); - let gic = KvmGicV3Its::new(&*vm, create_test_vgic_config()).expect("Cannot create gic"); + let gic = KvmGicV3Its::new(&*vm, &create_test_vgic_config()).expect("Cannot create gic"); let gicr_typer = vec![123]; let state = get_icc_regs(&gic.device, &gicr_typer).unwrap(); @@ -560,9 +560,8 @@ mod unit_tests { let hv = crate::new().unwrap(); let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _ = vm.create_vcpu(0, None).unwrap(); - let gic = vm - .create_vgic(create_test_vgic_config()) - .expect("Cannot create gic"); + let vgic_config = create_test_vgic_config(); + let gic = vm.create_vgic(&vgic_config).expect("Cannot create gic"); gic.lock().unwrap().save_data_tables().unwrap(); } diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index ce6e138294..74ee19a30f 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -579,7 +579,7 @@ impl vm::Vm for KvmVm { /// /// Creates a virtual GIC device. /// - fn create_vgic(&self, config: VgicConfig) -> vm::Result>> { + fn create_vgic(&self, config: &VgicConfig) -> vm::Result>> { let gic_device = KvmGicV3Its::new(self, config) .map_err(|e| vm::HypervisorVmError::CreateVgic(anyhow!("Vgic error {e:?}")))?; Ok(Arc::new(Mutex::new(gic_device))) @@ -589,7 +589,7 @@ impl vm::Vm for KvmVm { /// /// Creates a virtual AIA device. /// - fn create_vaia(&self, config: VaiaConfig) -> vm::Result>> { + fn create_vaia(&self, config: &VaiaConfig) -> vm::Result>> { let aia_device = KvmAiaImsics::new(self, config) .map_err(|e| vm::HypervisorVmError::CreateVaia(anyhow!("Vaia error {e:?}")))?; Ok(Arc::new(Mutex::new(aia_device))) diff --git a/hypervisor/src/kvm/riscv64/aia.rs b/hypervisor/src/kvm/riscv64/aia.rs index 9eb6e5f397..67857c6ae9 100644 --- a/hypervisor/src/kvm/riscv64/aia.rs +++ b/hypervisor/src/kvm/riscv64/aia.rs @@ -180,7 +180,7 @@ impl KvmAiaImsics { } /// Method to initialize the AIA device - pub fn new(vm: &dyn Vm, config: VaiaConfig) -> Result { + pub fn new(vm: &dyn Vm, config: &VaiaConfig) -> Result { // This is inside KVM module let vm = vm.as_any().downcast_ref::().expect("Wrong VM type?"); @@ -270,6 +270,7 @@ mod unit_tests { let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); let _vcpu = vm.create_vcpu(0, None).unwrap(); - assert!(KvmAiaImsics::new(&*vm, create_test_vaia_config()).is_ok()); + let vaia_config = create_test_vaia_config(); + assert!(KvmAiaImsics::new(&*vm, &vaia_config).is_ok()); } } diff --git a/hypervisor/src/mshv/aarch64/gic/mod.rs b/hypervisor/src/mshv/aarch64/gic/mod.rs index 631c5d4f01..47dab6ff34 100644 --- a/hypervisor/src/mshv/aarch64/gic/mod.rs +++ b/hypervisor/src/mshv/aarch64/gic/mod.rs @@ -56,7 +56,7 @@ impl From for GicState { impl MshvGicV2M { /// Create a new GICv2m device - pub fn new(_vm: &dyn Vm, config: VgicConfig) -> Result { + pub fn new(_vm: &dyn Vm, config: &VgicConfig) -> Result { let gic_device = MshvGicV2M { dist_addr: config.dist_addr, dist_size: config.dist_size, diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index a7de460806..d4ececd8e2 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -2193,7 +2193,7 @@ impl vm::Vm for MshvVm { } #[cfg(target_arch = "aarch64")] - fn create_vgic(&self, config: VgicConfig) -> vm::Result>> { + fn create_vgic(&self, config: &VgicConfig) -> vm::Result>> { let gic_device = MshvGicV2M::new(self, config) .map_err(|e| vm::HypervisorVmError::CreateVgic(anyhow!("Vgic error {e:?}")))?; diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 3b43c453ab..5b56923b19 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -318,9 +318,9 @@ pub trait Vm: Send + Sync + Any { /// Creates a new KVM vCPU file descriptor and maps the memory corresponding fn create_vcpu(&self, id: u32, vm_ops: Option>) -> Result>; #[cfg(target_arch = "aarch64")] - fn create_vgic(&self, config: VgicConfig) -> Result>>; + fn create_vgic(&self, config: &VgicConfig) -> Result>>; #[cfg(target_arch = "riscv64")] - fn create_vaia(&self, config: VaiaConfig) -> Result>>; + fn create_vaia(&self, config: &VaiaConfig) -> Result>>; /// Registers an event to be signaled whenever a certain address is written to. fn register_ioevent( diff --git a/pci/src/bus.rs b/pci/src/bus.rs index 041324d96f..eaae23a4d8 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -133,6 +133,7 @@ impl PciBus { } } + #[allow(clippy::needless_pass_by_value)] pub fn register_mapping( &self, dev: Arc, diff --git a/src/bin/ch-remote.rs b/src/bin/ch-remote.rs index 39bfaeecd6..aa95ce58ae 100644 --- a/src/bin/ch-remote.rs +++ b/src/bin/ch-remote.rs @@ -1137,6 +1137,7 @@ fn main() { if let Err(top_error) = target_api.do_command(&matches) { // Helper to join strings with a newline. + #[allow(clippy::needless_pass_by_value)] fn join_strs(mut acc: String, next: String) -> String { if !acc.is_empty() { acc.push('\n'); diff --git a/src/main.rs b/src/main.rs index 192ac7488c..a5ff044f99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -486,6 +486,7 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String .args(args) } +#[allow(clippy::needless_pass_by_value)] fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { let log_level = match cmd_arguments.get_count("v") { 0 => LevelFilter::Warn, diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 2f68a5cc56..96cfe3659b 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -504,6 +504,7 @@ pub fn rate_limited_copy, Q: AsRef>(from: P, to: Q) -> io:: Err(io::Error::last_os_error()) } +#[allow(clippy::needless_pass_by_value)] pub fn handle_child_output( r: Result<(), std::boxed::Box>, output: &std::process::Output, @@ -804,7 +805,7 @@ pub fn check_lines_count(input: &str, line_count: usize) -> bool { } } -pub fn check_matched_lines_count(input: &str, keywords: Vec<&str>, line_count: usize) -> bool { +pub fn check_matched_lines_count(input: &str, keywords: &[&str], line_count: usize) -> bool { let mut matches = String::new(); for line in input.lines() { if keywords.iter().all(|k| line.contains(k)) { @@ -1047,7 +1048,7 @@ impl Guest { .map_err(Error::WaitForBoot) } - pub fn check_numa_node_cpus(&self, node_id: usize, cpus: Vec) -> Result<(), Error> { + pub fn check_numa_node_cpus(&self, node_id: usize, cpus: &[usize]) -> Result<(), Error> { for cpu in cpus.iter() { let cmd = format!("[ -d \"/sys/devices/system/node/node{node_id}/cpu{cpu}\" ]"); self.ssh_command(cmd.as_str())?; @@ -1072,7 +1073,7 @@ impl Guest { pub fn check_numa_common( &self, mem_ref: Option<&[u32]>, - node_ref: Option<&[Vec]>, + node_ref: Option<&[&[usize]]>, distance_ref: Option<&[&str]>, ) { if let Some(mem_ref) = mem_ref { @@ -1086,7 +1087,7 @@ impl Guest { if let Some(node_ref) = node_ref { // Check each NUMA node has been assigned the right CPUs set. for (i, n) in node_ref.iter().enumerate() { - self.check_numa_node_cpus(i, n.clone()).unwrap(); + self.check_numa_node_cpus(i, n).unwrap(); } } diff --git a/tests/integration.rs b/tests/integration.rs index 845121635c..3d2edea217 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -15,7 +15,7 @@ use std::fs::OpenOptions; use std::io::{BufRead, Read, Seek, SeekFrom, Write}; use std::net::TcpListener; use std::os::unix::io::AsRawFd; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; use std::string::String; use std::sync::mpsc::Receiver; @@ -151,8 +151,8 @@ impl TargetApi { // Start cloud-hypervisor with no VM parameters, only the API server running. // From the API: Create a VM, boot it and check that it looks as expected. -fn _test_api_create_boot(target_api: TargetApi, guest: Guest) { - let mut child = GuestCommand::new(&guest) +fn _test_api_create_boot(target_api: &TargetApi, guest: &Guest) { + let mut child = GuestCommand::new(guest) .args(target_api.guest_args()) .capture_output() .spawn() @@ -196,8 +196,8 @@ fn _test_api_create_boot(target_api: TargetApi, guest: Guest) { // Start cloud-hypervisor with no VM parameters, only the API server running. // From the API: Create a VM, boot it and check it can be shutdown and then // booted again -fn _test_api_shutdown(target_api: TargetApi, guest: Guest) { - let mut child = GuestCommand::new(&guest) +fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { + let mut child = GuestCommand::new(guest) .args(target_api.guest_args()) .capture_output() .spawn() @@ -262,8 +262,8 @@ fn _test_api_shutdown(target_api: TargetApi, guest: Guest) { // Start cloud-hypervisor with no VM parameters, only the API server running. // From the API: Create a VM, boot it and check it can be deleted and then recreated // booted again. -fn _test_api_delete(target_api: TargetApi, guest: Guest) { - let mut child = GuestCommand::new(&guest) +fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { + let mut child = GuestCommand::new(guest) .args(target_api.guest_args()) .capture_output() .spawn() @@ -330,8 +330,8 @@ fn _test_api_delete(target_api: TargetApi, guest: Guest) { // From the API: Create a VM, boot it and check that it looks as expected. // Then we pause the VM, check that it's no longer available. // Finally we resume the VM and check that it's available. -fn _test_api_pause_resume(target_api: TargetApi, guest: Guest) { - let mut child = GuestCommand::new(&guest) +fn _test_api_pause_resume(target_api: &TargetApi, guest: &Guest) { + let mut child = GuestCommand::new(guest) .args(target_api.guest_args()) .capture_output() .spawn() @@ -749,10 +749,10 @@ fn setup_ovs_dpdk_guests( ) -> (Child, Child) { setup_ovs_dpdk(); - let clh_path = if !release_binary { - clh_command("cloud-hypervisor") - } else { + let clh_path = if release_binary { cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") }; let mut child1 = GuestCommand::new_with_binary_path(guest1, &clh_path) @@ -1125,7 +1125,7 @@ fn _test_guest_numa_nodes(acpi: bool) { guest.check_numa_common( Some(&[960_000, 1_920_000, 2_880_000]), - Some(&[vec![0, 1, 2], vec![3, 4], vec![5]]), + Some(&[&[0, 1, 2], &[3, 4], &[5]]), Some(&["10 15 20", "20 10 25", "25 30 10"]), ); @@ -1147,7 +1147,7 @@ fn _test_guest_numa_nodes(acpi: bool) { guest.check_numa_common( Some(&[3_840_000, 3_840_000, 3_840_000]), - Some(&[vec![0, 1, 2, 9], vec![3, 4, 6, 7, 8], vec![5, 10, 11]]), + Some(&[&[0, 1, 2, 9], &[3, 4, 6, 7, 8], &[5, 10, 11]]), None, ); } @@ -2371,20 +2371,21 @@ fn make_guest_panic(guest: &Guest) { // and write data to host(guest write data to ivshmem pci bar2 memory, host read it from // ivshmem backend file). // It also checks the size of the shared memory region. -fn _test_ivshmem(guest: &Guest, ivshmem_file_path: String, file_size: &str) { +fn _test_ivshmem(guest: &Guest, ivshmem_file_path: impl AsRef, file_size: &str) { + let ivshmem_file_path = ivshmem_file_path.as_ref(); let test_message_read = String::from("ivshmem device test data read"); // Modify backend file data before function test let mut file = OpenOptions::new() .read(true) .write(true) - .open(ivshmem_file_path.as_str()) + .open(ivshmem_file_path) .unwrap(); file.seek(SeekFrom::Start(0)).unwrap(); file.write_all(test_message_read.as_bytes()).unwrap(); file.write_all(b"\0").unwrap(); file.flush().unwrap(); - let output = fs::read_to_string(ivshmem_file_path.as_str()).unwrap(); + let output = fs::read_to_string(ivshmem_file_path).unwrap(); let nul_pos = output.as_bytes().iter().position(|&b| b == 0).unwrap(); let c_str = CStr::from_bytes_until_nul(&output.as_bytes()[..=nul_pos]).unwrap(); let file_message = c_str.to_string_lossy().to_string(); @@ -2498,7 +2499,7 @@ EOF let _ = guest.ssh_command("sudo python3 test_write.py").unwrap(); - let output = fs::read_to_string(ivshmem_file_path.as_str()).unwrap(); + let output = fs::read_to_string(ivshmem_file_path).unwrap(); let nul_pos = output.as_bytes().iter().position(|&b| b == 0).unwrap(); let c_str = CStr::from_bytes_until_nul(&output.as_bytes()[..=nul_pos]).unwrap(); let file_message = c_str.to_string_lossy().to_string(); @@ -2515,17 +2516,19 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_focal_hypervisor_fw() { - test_simple_launch(fw_path(FwType::RustHypervisorFirmware), FOCAL_IMAGE_NAME); + let path = fw_path(FwType::RustHypervisorFirmware); + test_simple_launch(&path, FOCAL_IMAGE_NAME); } #[test] #[cfg(target_arch = "x86_64")] fn test_focal_ovmf() { - test_simple_launch(fw_path(FwType::Ovmf), FOCAL_IMAGE_NAME); + let path = fw_path(FwType::Ovmf); + test_simple_launch(&path, FOCAL_IMAGE_NAME); } #[cfg(target_arch = "x86_64")] - fn test_simple_launch(fw_path: String, disk_path: &str) { + fn test_simple_launch(fw_path: &str, disk_path: &str) { let disk_config = Box::new(UbuntuDiskConfig::new(disk_path.to_string())); let guest = Guest::new(disk_config); let event_path = temp_event_monitor_path(&guest.tmp_dir); @@ -2533,7 +2536,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=1"]) .args(["--memory", "size=512M"]) - .args(["--kernel", fw_path.as_str()]) + .args(["--kernel", fw_path]) .default_disks() .default_net() .args(["--serial", "tty", "--console", "off"]) @@ -4702,7 +4705,7 @@ mod common_parallel { // does not have this command line tag. assert!(check_matched_lines_count( guest.ssh_command_l2_1("cat /proc/cmdline").unwrap().trim(), - vec!["VFIOTAG"], + &["VFIOTAG"], 1 )); @@ -4710,7 +4713,7 @@ mod common_parallel { // the L2 VM. assert!(check_matched_lines_count( guest.ssh_command_l2_2("cat /proc/cmdline").unwrap().trim(), - vec!["VFIOTAG"], + &["VFIOTAG"], 1 )); @@ -4726,7 +4729,7 @@ mod common_parallel { // Check both if /dev/vdc exists and if the block size is 16M in L2 VM assert!(check_matched_lines_count( guest.ssh_command_l2_1("lsblk").unwrap().trim(), - vec!["vdc", "16M"], + &["vdc", "16M"], 1 )); @@ -4748,7 +4751,7 @@ mod common_parallel { .unwrap(); assert!(check_matched_lines_count( vfio_hotplug_output.trim(), - vec!["{\"id\":\"vfio123\",\"bdf\":\"0000:00:08.0\"}"], + &["{\"id\":\"vfio123\",\"bdf\":\"0000:00:08.0\"}"], 1 )); @@ -4759,7 +4762,7 @@ mod common_parallel { // VM, so this is our way to validate hotplug works for VFIO PCI. assert!(check_matched_lines_count( guest.ssh_command_l2_3("cat /proc/cmdline").unwrap().trim(), - vec!["VFIOTAG"], + &["VFIOTAG"], 1 )); @@ -4873,7 +4876,8 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_shutdown(TargetApi::new_http_api(&guest.tmp_dir), guest); + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_shutdown(&target_api, &guest); } #[test] @@ -4881,7 +4885,8 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_delete(TargetApi::new_http_api(&guest.tmp_dir), guest); + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_delete(&target_api, &guest); } #[test] @@ -4889,7 +4894,8 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_pause_resume(TargetApi::new_http_api(&guest.tmp_dir), guest); + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_pause_resume(&target_api, &guest); } #[test] @@ -4897,7 +4903,8 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_create_boot(TargetApi::new_http_api(&guest.tmp_dir), guest); + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_create_boot(&target_api, &guest); } #[test] @@ -7541,7 +7548,8 @@ mod dbus_api { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_create_boot(TargetApi::new_dbus_api(&guest.tmp_dir), guest); + let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); + _test_api_create_boot(&target_api, &guest); } #[test] @@ -7549,7 +7557,8 @@ mod dbus_api { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_shutdown(TargetApi::new_dbus_api(&guest.tmp_dir), guest); + let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); + _test_api_shutdown(&target_api, &guest); } #[test] @@ -7557,7 +7566,8 @@ mod dbus_api { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_delete(TargetApi::new_dbus_api(&guest.tmp_dir), guest); + let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); + _test_api_delete(&target_api, &guest); } #[test] @@ -7565,7 +7575,8 @@ mod dbus_api { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - _test_api_pause_resume(TargetApi::new_dbus_api(&guest.tmp_dir), guest); + let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); + _test_api_pause_resume(&target_api, &guest); } } @@ -7693,7 +7704,7 @@ mod ivshmem { } // Check ivshmem device in src guest. - _test_ivshmem(&guest, ivshmem_file_path.clone(), file_size); + _test_ivshmem(&guest, &ivshmem_file_path, file_size); // Allow some normal time to elapse to check we don't get spurious reboots thread::sleep(std::time::Duration::new(40, 0)); @@ -7748,7 +7759,7 @@ mod ivshmem { guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); // Check ivshmem device - _test_ivshmem(&guest, ivshmem_file_path, file_size); + _test_ivshmem(&guest, &ivshmem_file_path, file_size); }); // Clean-up the destination VM and make sure it terminated correctly @@ -7810,7 +7821,7 @@ mod ivshmem { let r = std::panic::catch_unwind(|| { guest.wait_vm_boot(None).unwrap(); - _test_ivshmem(&guest, ivshmem_file_path, file_size); + _test_ivshmem(&guest, &ivshmem_file_path, file_size); }); kill_child(&mut child); let output = child.wait_with_output().unwrap(); @@ -7958,7 +7969,7 @@ mod ivshmem { // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); guest.check_devices_common(Some(&socket), Some(&console_text), None); - _test_ivshmem(&guest, ivshmem_file_path, file_size); + _test_ivshmem(&guest, &ivshmem_file_path, file_size); }); // Shutdown the target VM and check console output kill_child(&mut child); @@ -8278,9 +8289,7 @@ mod common_sequential { // Perform same checks to validate VM has been properly restored assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); let total_memory = guest.get_total_memory().unwrap_or_default(); - if !use_hotplug { - assert!(total_memory > 1_920_000); - } else { + if use_hotplug { assert!(total_memory > 4_800_000); assert!(total_memory < 5_760_000); // Deflate balloon to restore entire RAM to the VM @@ -8293,6 +8302,8 @@ mod common_sequential { let total_memory = guest.get_total_memory().unwrap_or_default(); assert!(total_memory > 4_800_000); assert!(total_memory < 5_760_000); + } else { + assert!(total_memory > 1_920_000); } guest.check_devices_common(Some(&socket), Some(&console_text), None); @@ -10050,10 +10061,10 @@ mod live_migration { let pmem_path = String::from("/dev/pmem0"); // Start the source VM - let src_vm_path = if !upgrade_test { - clh_command("cloud-hypervisor") - } else { + let src_vm_path = if upgrade_test { cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") }; let src_api_socket = temp_api_path(&guest.tmp_dir); let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); @@ -10214,10 +10225,10 @@ mod live_migration { let pmem_path = String::from("/dev/pmem0"); // Start the source VM - let src_vm_path = if !upgrade_test { - clh_command("cloud-hypervisor") - } else { + let src_vm_path = if upgrade_test { cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") }; let src_api_socket = temp_api_path(&guest.tmp_dir); let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); @@ -10415,10 +10426,10 @@ mod live_migration { let pmem_path = String::from("/dev/pmem0"); // Start the source VM - let src_vm_path = if !upgrade_test { - clh_command("cloud-hypervisor") - } else { + let src_vm_path = if upgrade_test { cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") }; let src_api_socket = temp_api_path(&guest.tmp_dir); let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); @@ -10467,7 +10478,7 @@ mod live_migration { { guest.check_numa_common( Some(&[960_000, 960_000, 1_920_000]), - Some(&[vec![0, 1, 2], vec![3, 4], vec![5]]), + Some(&[&[0, 1, 2], &[3, 4], &[5]]), Some(&["10 15 20", "20 10 25", "25 30 10"]), ); @@ -10563,7 +10574,7 @@ mod live_migration { { guest.check_numa_common( Some(&[960_000, 960_000, 1_920_000]), - Some(&[vec![0, 1, 2], vec![3, 4], vec![5]]), + Some(&[&[0, 1, 2], &[3, 4], &[5]]), Some(&["10 15 20", "20 10 25", "25 30 10"]), ); } @@ -10574,7 +10585,7 @@ mod live_migration { { guest.check_numa_common( Some(&[1_920_000, 1_920_000, 2_880_000]), - Some(&[vec![0, 1, 2], vec![3, 4], vec![5]]), + Some(&[&[0, 1, 2], &[3, 4], &[5]]), Some(&["10 15 20", "20 10 25", "25 30 10"]), ); @@ -10592,7 +10603,7 @@ mod live_migration { guest.check_numa_common( Some(&[3_840_000, 3_840_000, 3_840_000]), - Some(&[vec![0, 1, 2, 9], vec![3, 4, 6, 7, 8], vec![5, 10, 11]]), + Some(&[&[0, 1, 2, 9], &[3, 4, 6, 7, 8], &[5, 10, 11]]), None, ); } @@ -10640,10 +10651,10 @@ mod live_migration { let pmem_path = String::from("/dev/pmem0"); // Start the source VM - let src_vm_path = if !upgrade_test { - clh_command("cloud-hypervisor") - } else { + let src_vm_path = if upgrade_test { cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") }; let src_api_socket = temp_api_path(&guest.tmp_dir); let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index ca690ff3f8..3db6832617 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -342,8 +342,8 @@ impl BalloonEpollHandler { fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.inflate_queue_evt.as_raw_fd(), INFLATE_QUEUE_EVENT)?; @@ -639,7 +639,7 @@ impl VirtioDevice for Balloon { Thread::VirtioBalloon, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.common.epoll_threads = Some(epoll_threads); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 898b13616a..70ec6f9de8 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -531,8 +531,8 @@ impl BlockEpollHandler { fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.queue_evt.as_raw_fd(), QUEUE_AVAIL_EVENT)?; @@ -987,7 +987,7 @@ impl VirtioDevice for Block { Thread::VirtioBlock, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; } diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index a036a54612..c8a9f08a02 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -284,8 +284,8 @@ impl ConsoleEpollHandler { fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.input_queue_evt.as_raw_fd(), INPUT_QUEUE_EVENT)?; @@ -752,7 +752,7 @@ impl VirtioDevice for Console { Thread::VirtioConsole, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.common.epoll_threads = Some(epoll_threads); diff --git a/virtio-devices/src/epoll_helper.rs b/virtio-devices/src/epoll_helper.rs index c20c1190a5..dc062c8d39 100644 --- a/virtio-devices/src/epoll_helper.rs +++ b/virtio-devices/src/epoll_helper.rs @@ -10,8 +10,8 @@ use std::fs::File; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::sync::Barrier; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Barrier}; use std::thread; use log::info; @@ -153,8 +153,8 @@ impl EpollHelper { pub fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, handler: &mut dyn EpollHelperHandler, ) -> std::result::Result<(), EpollHelperError> { self.run_with_timeout(paused, paused_sync, handler, -1, false) @@ -163,8 +163,8 @@ impl EpollHelper { #[cfg(not(fuzzing))] pub fn run_with_timeout( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, handler: &mut dyn EpollHelperHandler, timeout: i32, enable_event_list: bool, @@ -250,8 +250,8 @@ impl EpollHelper { // and return when no epoll events are active pub fn run_with_timeout( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, handler: &mut dyn EpollHelperHandler, _timeout: i32, _enable_event_list: bool, diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index 1318e575f1..f4812b04fb 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -727,8 +727,8 @@ impl IommuEpollHandler { fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.request_queue_evt.as_raw_fd(), REQUEST_Q_EVENT)?; @@ -1110,7 +1110,7 @@ impl VirtioDevice for Iommu { Thread::VirtioIommu, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.common.epoll_threads = Some(epoll_threads); diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 3890928a54..936fdbe42a 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -669,8 +669,8 @@ impl MemEpollHandler { fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.queue_evt.as_raw_fd(), QUEUE_AVAIL_EVENT)?; @@ -879,13 +879,13 @@ impl Mem { pub fn remove_dma_mapping_handler( &mut self, - source: VirtioMemMappingSource, + source: &VirtioMemMappingSource, ) -> result::Result<(), Error> { let handler = self .dma_mapping_handlers .lock() .unwrap() - .remove(&source) + .remove(source) .ok_or(Error::InvalidDmaMappingHandler)?; let config = self.config.lock().unwrap(); @@ -1003,7 +1003,7 @@ impl VirtioDevice for Mem { Thread::VirtioMem, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.common.epoll_threads = Some(epoll_threads); diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 71faf85492..8eee661341 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -75,8 +75,8 @@ impl NetCtrlEpollHandler { pub fn run_ctrl( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> std::result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.queue_evt.as_raw_fd(), CTRL_QUEUE_EVENT)?; @@ -266,8 +266,8 @@ impl NetEpollHandler { fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.queue_evt_pair.0.as_raw_fd(), RX_QUEUE_EVENT)?; @@ -736,7 +736,7 @@ impl VirtioDevice for Net { Thread::VirtioNetCtl, &mut epoll_threads, &self.exit_evt, - move || ctrl_handler.run_ctrl(paused, paused_sync.unwrap()), + move || ctrl_handler.run_ctrl(&paused, paused_sync.as_ref().unwrap()), )?; self.ctrl_queue_epoll_thread = Some(epoll_threads.remove(0)); } @@ -814,7 +814,7 @@ impl VirtioDevice for Net { Thread::VirtioNet, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; } diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index da3722e254..549b62fd96 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -217,8 +217,8 @@ impl PmemEpollHandler { fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.queue_evt.as_raw_fd(), QUEUE_AVAIL_EVENT)?; @@ -414,7 +414,7 @@ impl VirtioDevice for Pmem { Thread::VirtioPmem, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.common.epoll_threads = Some(epoll_threads); diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index aff511b0a3..2f980d4d8b 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -104,8 +104,8 @@ impl RngEpollHandler { fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.queue_evt.as_raw_fd(), QUEUE_AVAIL_EVENT)?; @@ -281,7 +281,7 @@ impl VirtioDevice for Rng { Thread::VirtioRng, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.common.epoll_threads = Some(epoll_threads); diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs index 26d1445f6e..9c8cc3e7de 100644 --- a/virtio-devices/src/seccomp_filters.rs +++ b/virtio-devices/src/seccomp_filters.rs @@ -11,6 +11,7 @@ use seccompiler::{ SeccompFilter, SeccompRule, }; +#[derive(Clone, Copy)] pub enum Thread { VirtioBalloon, VirtioBlock, diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 1a576ad5a7..5a7b5f57a4 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -82,6 +82,8 @@ pub struct VirtioPciCommonConfigState { const VRING_DESC_ELEMENT_SIZE: usize = 16; const VRING_AVAIL_ELEMENT_SIZE: usize = 2; const VRING_USED_ELEMENT_SIZE: usize = 8; + +#[derive(Copy, Clone)] pub enum VringType { Desc, Avail, @@ -191,6 +193,7 @@ impl VirtioPciCommonConfig { } } + #[allow(clippy::needless_pass_by_value)] pub fn write( &mut self, offset: u64, @@ -297,6 +300,7 @@ impl VirtioPciCommonConfig { } } + #[allow(clippy::needless_pass_by_value)] fn read_common_config_dword(&self, offset: u64, device: Arc>) -> u32 { debug!("read_common_config_dword: offset 0x{offset:x}"); match offset { @@ -319,6 +323,7 @@ impl VirtioPciCommonConfig { } } + #[allow(clippy::needless_pass_by_value)] fn write_common_config_dword( &mut self, offset: u64, diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 10bc874087..6576013e89 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -297,7 +297,7 @@ impl VirtioDevice for Blk { let mut handler = self.vu_common.activate( mem, - queues, + &queues, interrupt_cb, self.common.acked_features, backend_req_handler, @@ -316,7 +316,7 @@ impl VirtioDevice for Blk { Thread::VirtioVhostBlock, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.epoll_thread = Some(epoll_threads.remove(0)); diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 726d88367b..d0005af90f 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -277,7 +277,7 @@ impl VirtioDevice for Fs { let mut handler = self.vu_common.activate( mem, - queues, + &queues, interrupt_cb, self.common.acked_features, backend_req_handler, @@ -295,7 +295,7 @@ impl VirtioDevice for Fs { Thread::VirtioVhostFs, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.epoll_thread = Some(epoll_threads.remove(0)); diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 764b0d1c02..05233a0be6 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -183,8 +183,8 @@ pub struct VhostUserEpollHandler { impl VhostUserEpollHandler { pub fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> std::result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event_custom( @@ -221,14 +221,16 @@ impl VhostUserEpollHandler { ))) })?; + let queues = self + .queues + .iter() + .map(|(i, q, e)| (*i, vm_virtio::clone_queue(q), e.try_clone().unwrap())) + .collect::>(); // Initialize the backend vhost_user .reinitialize_vhost_user( self.mem.memory().deref(), - self.queues - .iter() - .map(|(i, q, e)| (*i, vm_virtio::clone_queue(q), e.try_clone().unwrap())) - .collect(), + &queues, self.virtio_interrupt.as_ref(), self.acked_features, self.acked_protocol_features, @@ -305,7 +307,7 @@ impl VhostUserCommon { pub fn activate( &mut self, mem: GuestMemoryAtomic, - queues: Vec<(usize, Queue, EventFd)>, + queues: &[(usize, Queue, EventFd)], interrupt_cb: Arc, acked_features: u64, backend_req_handler: Option>, @@ -325,14 +327,15 @@ impl VhostUserCommon { return Err(ActivateError::BadActivate); } let vu = self.vu.as_ref().unwrap(); + let queues = queues + .iter() + .map(|(i, q, e)| (*i, vm_virtio::clone_queue(q), e.try_clone().unwrap())) + .collect::>(); vu.lock() .unwrap() .setup_vhost_user( &mem.memory(), - queues - .iter() - .map(|(i, q, e)| (*i, vm_virtio::clone_queue(q), e.try_clone().unwrap())) - .collect(), + &queues, interrupt_cb.as_ref(), acked_features, &backend_req_handler, diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index fb79d52d83..e25e8ef112 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -336,7 +336,7 @@ impl VirtioDevice for Net { Thread::VirtioVhostNetCtl, &mut epoll_threads, &self.exit_evt, - move || ctrl_handler.run_ctrl(paused, paused_sync.unwrap()), + move || ctrl_handler.run_ctrl(&paused, paused_sync.as_ref().unwrap()), )?; self.ctrl_queue_epoll_thread = Some(epoll_threads.remove(0)); } @@ -353,7 +353,7 @@ impl VirtioDevice for Net { let mut handler = self.vu_common.activate( mem, - queues, + &queues, interrupt_cb, backend_acked_features, backend_req_handler, @@ -371,7 +371,7 @@ impl VirtioDevice for Net { Thread::VirtioVhostNet, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.epoll_thread = Some(epoll_threads.remove(0)); diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 76a792c875..264635149c 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -156,7 +156,7 @@ impl VhostUserHandle { pub fn setup_vhost_user( &mut self, mem: &GuestMemoryMmap, - queues: Vec<(usize, Queue, EventFd)>, + queues: &[(usize, Queue, EventFd)], virtio_interrupt: &dyn VirtioInterrupt, acked_features: u64, backend_req_handler: &Option>, @@ -340,7 +340,7 @@ impl VhostUserHandle { pub fn reinitialize_vhost_user( &mut self, mem: &GuestMemoryMmap, - queues: Vec<(usize, Queue, EventFd)>, + queues: &[(usize, Queue, EventFd)], virtio_interrupt: &dyn VirtioInterrupt, acked_features: u64, acked_protocol_features: u64, diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 63cfb3a674..506e2dcbab 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -201,8 +201,8 @@ where fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.queue_evts[0].as_raw_fd(), RX_QUEUE_EVENT)?; @@ -463,7 +463,7 @@ where Thread::VirtioVsock, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.common.epoll_threads = Some(epoll_threads); diff --git a/virtio-devices/src/watchdog.rs b/virtio-devices/src/watchdog.rs index 2f76cae37b..6b9f7cc0ac 100644 --- a/virtio-devices/src/watchdog.rs +++ b/virtio-devices/src/watchdog.rs @@ -121,8 +121,8 @@ impl WatchdogEpollHandler { fn run( &mut self, - paused: Arc, - paused_sync: Arc, + paused: &AtomicBool, + paused_sync: &Barrier, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.queue_evt.as_raw_fd(), QUEUE_AVAIL_EVENT)?; @@ -369,7 +369,7 @@ impl VirtioDevice for Watchdog { Thread::VirtioWatchdog, &mut epoll_threads, &self.exit_evt, - move || handler.run(paused, paused_sync.unwrap()), + move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.common.epoll_threads = Some(epoll_threads); diff --git a/vm-device/src/bus.rs b/vm-device/src/bus.rs index 92916a65b1..a029f0cc9e 100644 --- a/vm-device/src/bus.rs +++ b/vm-device/src/bus.rs @@ -147,6 +147,7 @@ impl Bus { None } + #[allow(clippy::needless_pass_by_value)] pub fn insert(&self, device: Arc, base: u64, len: u64) -> Result<()> { if len == 0 { return Err(Error::ZeroSizedRange); diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 56513a6d04..7fca8435f0 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -105,6 +105,7 @@ struct ProcessorGiccAffinity { } bitflags! { + #[derive(Copy, Clone)] pub struct MemAffinityFlags: u32 { const NOFLAGS = 0; const ENABLE = 0b1; diff --git a/vmm/src/api/http/mod.rs b/vmm/src/api/http/mod.rs index a35c9aca07..9ba679a808 100644 --- a/vmm/src/api/http/mod.rs +++ b/vmm/src/api/http/mod.rs @@ -76,6 +76,7 @@ const HTTP_ROOT: &str = "/api/v1"; /// The error message contained in the response is supposed to be user-facing, /// thus insightful and helpful while balancing technical accuracy and /// simplicity. +#[allow(clippy::needless_pass_by_value)] pub fn error_response(error: HttpError, status: StatusCode) -> Response { let mut response = Response::new(Version::Http11, status); diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 694eb961be..a0b090542a 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -369,6 +369,7 @@ pub trait RequestHandler { pub type ApiRequest = Box Result + Send + 'static>; +#[allow(clippy::needless_pass_by_value)] fn get_response( action: &Action, api_evt: EventFd, diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 6d5dcf5da2..3c99eb26f7 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -811,6 +811,7 @@ impl PlatformConfig { } impl MemoryConfig { + #[allow(clippy::needless_pass_by_value)] pub fn parse(memory: &str, memory_zones: Option>) -> Result { let mut parser = OptionParser::new(); parser diff --git a/vmm/src/console_devices.rs b/vmm/src/console_devices.rs index 9f8d18ae7c..70bcabae5d 100644 --- a/vmm/src/console_devices.rs +++ b/vmm/src/console_devices.rs @@ -76,7 +76,7 @@ pub struct ConsoleInfo { fn modify_mode( fd: RawFd, f: F, - original_termios_opt: Arc>>, + original_termios_opt: &Mutex>, ) -> vmm_sys_util::errno::Result<()> { // SAFETY: safe because we check the return value of isatty. if unsafe { isatty(fd) } != 1 { @@ -109,7 +109,7 @@ fn modify_mode( fn set_raw_mode( f: &dyn AsRawFd, - original_termios_opt: Arc>>, + original_termios_opt: &Mutex>, ) -> ConsoleDeviceResult<()> { modify_mode( f.as_raw_fd(), @@ -190,7 +190,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { let (main_fd, sub_fd, path) = create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?; - set_raw_mode(&sub_fd.as_raw_fd(), vmm.original_termios_opt.clone())?; + set_raw_mode(&sub_fd.as_raw_fd(), &vmm.original_termios_opt)?; vmconfig.console.file = Some(path.clone()); vmm.console_resize_pipe = Some(Arc::new( listen_for_sigwinch_on_tty( @@ -221,7 +221,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { @@ -239,7 +239,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { let (main_fd, sub_fd, path) = create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?; - set_raw_mode(&sub_fd.as_raw_fd(), vmm.original_termios_opt.clone())?; + set_raw_mode(&sub_fd.as_raw_fd(), &vmm.original_termios_opt)?; vmconfig.serial.file = Some(path.clone()); ConsoleOutput::Pty(Arc::new(main_fd)) } @@ -255,7 +255,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult ConsoleDeviceResult { let (main_fd, sub_fd, path) = create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?; - set_raw_mode(&sub_fd.as_raw_fd(), vmm.original_termios_opt.clone())?; + set_raw_mode(&sub_fd.as_raw_fd(), &vmm.original_termios_opt)?; vmconfig.debug_console.file = Some(path.clone()); ConsoleOutput::Pty(Arc::new(main_fd)) } ConsoleOutputMode::Tty => { let out = dup_stdout().map_err(|e| ConsoleDeviceError::CreateConsoleDevice(e.into()))?; - set_raw_mode(&out, vmm.original_termios_opt.clone())?; + set_raw_mode(&out, &vmm.original_termios_opt)?; ConsoleOutput::Tty(Arc::new(out)) } ConsoleOutputMode::Socket => { diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index b3d7f71ef0..e501b5c331 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -949,7 +949,7 @@ impl CpuManager { pub fn configure_vcpu( &self, - vcpu: Arc>, + vcpu: &Mutex, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, ) -> Result<()> { let mut vcpu = vcpu.lock().unwrap(); @@ -1432,7 +1432,7 @@ impl CpuManager { cmp::Ordering::Greater => { let vcpus = self.create_vcpus(desired_vcpus, None)?; for vcpu in vcpus { - self.configure_vcpu(vcpu, None)?; + self.configure_vcpu(&vcpu, None)?; } self.activate_vcpus(desired_vcpus, true, None)?; Ok(true) @@ -1543,6 +1543,7 @@ impl CpuManager { }) } + #[allow(clippy::needless_pass_by_value)] pub fn create_madt(&self, #[cfg(target_arch = "aarch64")] vgic: Arc>) -> Sdt { use crate::acpi; // This is also checked in the commandline parsing. diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 245be2c856..93d863c52f 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -15,7 +15,7 @@ use std::io::{self, IsTerminal, Seek, SeekFrom, stdout}; use std::num::Wrapping; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, FromRawFd}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::result; use std::sync::{Arc, Mutex}; #[cfg(not(target_arch = "riscv64"))] @@ -1114,7 +1114,7 @@ fn create_mmio_allocators( start: u64, end: u64, num_pci_segments: u16, - weights: Vec, + weights: &[u32], alignment: u64, ) -> Vec>> { let total_weight: u32 = weights.iter().sum(); @@ -1193,7 +1193,7 @@ impl DeviceManager { start_of_mmio32_area, end_of_mmio32_area, num_pci_segments, - mmio32_aperture_weights, + &mmio32_aperture_weights, 4 << 10, ); @@ -1213,7 +1213,7 @@ impl DeviceManager { start_of_mmio64_area, end_of_mmio64_area, num_pci_segments, - mmio64_aperture_weights, + &mmio64_aperture_weights, 4 << 30, ); @@ -1400,6 +1400,7 @@ impl DeviceManager { self.add_interrupt_controller() } + #[allow(clippy::needless_pass_by_value)] pub fn create_devices( &mut self, console_info: Option, @@ -1470,7 +1471,7 @@ impl DeviceManager { #[cfg(not(target_arch = "riscv64"))] if let Some(tpm) = self.config.clone().lock().unwrap().tpm.as_ref() { - let tpm_dev = self.add_tpm_device(tpm.socket.clone())?; + let tpm_dev = self.add_tpm_device(&tpm.socket)?; self.bus_devices .push(Arc::clone(&tpm_dev) as Arc); } @@ -1644,7 +1645,7 @@ impl DeviceManager { let dev_id = self.add_virtio_pci_device( handle.virtio_device, &mapping, - handle.id, + &handle.id, handle.pci_segment, handle.dma_handler, )?; @@ -1675,7 +1676,7 @@ impl DeviceManager { } if let Some(iommu_device) = iommu_device { - let dev_id = self.add_virtio_pci_device(iommu_device, &None, iommu_id, 0, None)?; + let dev_id = self.add_virtio_pci_device(iommu_device, &None, &iommu_id, 0, None)?; self.iommu_attached_devices = Some((dev_id, iommu_attached_devices)); } } @@ -1790,14 +1791,15 @@ impl DeviceManager { ) -> DeviceManagerResult>> { let id = String::from(IOAPIC_DEVICE_NAME); + let state = state_from_id(self.snapshot.as_ref(), id.as_str()) + .map_err(DeviceManagerError::RestoreGetState)?; // Create IOAPIC let interrupt_controller = Arc::new(Mutex::new( ioapic::Ioapic::new( id.clone(), APIC_START, self.msi_interrupt_manager.as_ref(), - state_from_id(self.snapshot.as_ref(), id.as_str()) - .map_err(DeviceManagerError::RestoreGetState)?, + state.as_ref(), ) .map_err(DeviceManagerError::CreateInterruptController)?, )); @@ -2486,7 +2488,7 @@ impl DeviceManager { #[cfg(not(target_arch = "riscv64"))] fn add_tpm_device( &mut self, - tpm_path: PathBuf, + tpm_path: &Path, ) -> DeviceManagerResult>> { // Create TPM Device let tpm = devices::tpm::Tpm::new(tpm_path.to_str().unwrap()).map_err(|e| { @@ -4057,7 +4059,7 @@ impl DeviceManager { &mut self, virtio_device: Arc>, iommu_mapping: &Option>, - virtio_device_id: String, + virtio_device_id: &str, pci_segment_id: u16, dma_handler: Option>, ) -> DeviceManagerResult { @@ -4065,13 +4067,13 @@ impl DeviceManager { // Add the new virtio-pci node to the device tree. let mut node = device_node!(id); - node.children = vec![virtio_device_id.clone()]; + node.children = vec![virtio_device_id.to_string()]; let (pci_segment_id, pci_device_bdf, resources) = self.pci_resources(&id, pci_segment_id)?; // Update the existing virtio node by setting the parent. - if let Some(node) = self.device_tree.lock().unwrap().get_mut(&virtio_device_id) { + if let Some(node) = self.device_tree.lock().unwrap().get_mut(virtio_device_id) { node.parent = Some(id.clone()); } else { return Err(DeviceManagerError::MissingNode); @@ -4472,15 +4474,15 @@ impl DeviceManager { }) } - pub fn remove_device(&mut self, id: String) -> DeviceManagerResult<()> { + pub fn remove_device(&mut self, id: &str) -> DeviceManagerResult<()> { // The node can be directly a PCI node in case the 'id' refers to a // VFIO device or a virtio-pci one. // In case the 'id' refers to a virtio device, we must find the PCI // node by looking at the parent. let device_tree = self.device_tree.lock().unwrap(); let node = device_tree - .get(&id) - .ok_or(DeviceManagerError::UnknownDeviceId(id.clone()))?; + .get(id) + .ok_or_else(|| DeviceManagerError::UnknownDeviceId(id.to_string()))?; // Release advisory locks by dropping all references. // Linux automatically releases all locks of that file if the last open FD is closed. @@ -4545,7 +4547,7 @@ impl DeviceManager { let nets = config.net.as_deref_mut().unwrap(); let net_dev_cfg = nets .iter_mut() - .find(|net| net.id.as_ref() == Some(&id)) + .find(|net| net.id.as_deref() == Some(id)) // unwrap: the device could not have been removed without an ID .unwrap(); let fds = net_dev_cfg.fds.take().unwrap_or(Vec::new()); @@ -4692,12 +4694,11 @@ impl DeviceManager { if remove_dma_handler { for virtio_mem_device in self.virtio_mem_devices.iter() { + let source = VirtioMemMappingSource::Device(pci_device_bdf.into()); virtio_mem_device .lock() .unwrap() - .remove_dma_mapping_handler(VirtioMemMappingSource::Device( - pci_device_bdf.into(), - )) + .remove_dma_mapping_handler(&source) .map_err(DeviceManagerError::RemoveDmaMappingHandlerVirtioMem)?; } } @@ -4804,7 +4805,7 @@ impl DeviceManager { let bdf = self.add_virtio_pci_device( handle.virtio_device, &mapping, - handle.id.clone(), + &handle.id, handle.pci_segment, handle.dma_handler, )?; @@ -5532,7 +5533,7 @@ mod unit_tests { #[test] fn test_create_mmio_allocators() { - let res = create_mmio_allocators(0x100000, 0x400000, 1, vec![1], 4 << 10); + let res = create_mmio_allocators(0x100000, 0x400000, 1, &[1], 4 << 10); assert_eq!(res.len(), 1); assert_eq!( res[0].lock().unwrap().base(), @@ -5543,7 +5544,7 @@ mod unit_tests { vm_memory::GuestAddress(0x3fffff) ); - let res = create_mmio_allocators(0x100000, 0x400000, 2, vec![1, 1], 4 << 10); + let res = create_mmio_allocators(0x100000, 0x400000, 2, &[1, 1], 4 << 10); assert_eq!(res.len(), 2); assert_eq!( res[0].lock().unwrap().base(), @@ -5562,7 +5563,7 @@ mod unit_tests { vm_memory::GuestAddress(0x3fffff) ); - let res = create_mmio_allocators(0x100000, 0x400000, 2, vec![2, 1], 4 << 10); + let res = create_mmio_allocators(0x100000, 0x400000, 2, &[2, 1], 4 << 10); assert_eq!(res.len(), 2); assert_eq!( res[0].lock().unwrap().base(), diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index e53dc698f5..32aeb0f714 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -131,7 +131,7 @@ fn import_parameter( /// Right now it only supports SNP based isolation. /// We can boot legacy VM with an igvm file without /// any isolation. -/// +#[allow(clippy::needless_pass_by_value)] pub fn load_igvm( mut file: &std::fs::File, memory_manager: Arc>, diff --git a/vmm/src/igvm/mod.rs b/vmm/src/igvm/mod.rs index 06686b7a04..62c32d4e89 100644 --- a/vmm/src/igvm/mod.rs +++ b/vmm/src/igvm/mod.rs @@ -74,7 +74,7 @@ pub enum BootPageAcceptance { /// The startup memory type used to notify a well behaved host that memory should be present before attempting to /// start the guest. #[allow(dead_code)] -#[derive(Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum StartupMemoryType { /// The range is normal memory. Ram, diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 1c1eca79be..84db6a14cb 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -11,7 +11,6 @@ use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::net::{UnixListener, UnixStream}; use std::panic::AssertUnwindSafe; use std::path::PathBuf; -use std::rc::Rc; use std::sync::mpsc::{Receiver, RecvError, SendError, Sender}; use std::sync::{Arc, Mutex}; #[cfg(not(target_arch = "riscv64"))] @@ -545,9 +544,9 @@ pub fn start_vmm_thread( vmm.setup_signal_handler(landlock_enable)?; vmm.control_loop( - Rc::new(api_receiver), + &api_receiver, #[cfg(feature = "guest_debug")] - Rc::new(gdb_receiver), + &gdb_receiver, ) }) .map_err(Error::VmmThreadSpawn)? @@ -674,7 +673,7 @@ impl Vmm { fn signal_handler( mut signals: Signals, - original_termios_opt: Arc>>, + original_termios_opt: &Mutex>, exit_evt: &EventFd, ) { for sig in &Self::HANDLED_SIGNALS { @@ -747,7 +746,7 @@ impl Vmm { } std::panic::catch_unwind(AssertUnwindSafe(|| { - Vmm::signal_handler(signals, original_termios_opt, &exit_evt); + Vmm::signal_handler(signals, original_termios_opt.as_ref(), &exit_evt); })) .map_err(|_| { error!("vmm signal_handler thread panicked"); @@ -862,7 +861,7 @@ impl Vmm { .unwrap() .landlock_enable { - apply_landlock(self.vm_config.as_ref().unwrap().clone()).map_err(|e| { + apply_landlock(self.vm_config.as_ref().unwrap().as_ref()).map_err(|e| { MigratableError::MigrateReceive(anyhow!("Error applying landlock: {e:?}")) })?; } @@ -1097,12 +1096,13 @@ impl Vmm { Ok(true) } + #[allow(clippy::needless_pass_by_value)] fn send_migration( vm: &mut Vm, #[cfg(all(feature = "kvm", target_arch = "x86_64"))] hypervisor: Arc< dyn hypervisor::Hypervisor, >, - send_data_migration: VmSendMigrationData, + send_data_migration: &VmSendMigrationData, ) -> result::Result<(), MigratableError> { // Set up the socket connection let mut socket = Self::send_migration_socket(&send_data_migration.destination_url)?; @@ -1348,7 +1348,7 @@ impl Vmm { .unwrap() .landlock_enable { - apply_landlock(self.vm_config.as_ref().unwrap().clone()) + apply_landlock(self.vm_config.as_ref().unwrap().as_ref()) .map_err(VmError::ApplyLandlock)?; } @@ -1362,8 +1362,8 @@ impl Vmm { fn control_loop( &mut self, - api_receiver: Rc>, - #[cfg(feature = "guest_debug")] gdb_receiver: Rc>, + api_receiver: &Receiver, + #[cfg(feature = "guest_debug")] gdb_receiver: &Receiver, ) -> Result<()> { const EPOLL_EVENTS_LEN: usize = 100; @@ -1468,7 +1468,7 @@ impl Vmm { } } -fn apply_landlock(vm_config: Arc>) -> result::Result<(), LandlockError> { +fn apply_landlock(vm_config: &Mutex) -> result::Result<(), LandlockError> { vm_config.lock().unwrap().apply_landlock()?; Ok(()) } @@ -1490,7 +1490,7 @@ impl RequestHandler for Vmm { .unwrap() .landlock_enable { - apply_landlock(self.vm_config.as_ref().unwrap().clone()) + apply_landlock(self.vm_config.as_ref().unwrap().as_ref()) .map_err(VmError::ApplyLandlock)?; } Ok(()) @@ -1834,7 +1834,7 @@ impl RequestHandler for Vmm { self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; if let Some(ref mut vm) = self.vm { - vm.resize_zone(id, desired_ram) + vm.resize_zone(&id, desired_ram) .inspect_err(|e| error!("Error when resizing zone: {e:?}"))?; Ok(()) } else { @@ -1913,7 +1913,7 @@ impl RequestHandler for Vmm { fn vm_remove_device(&mut self, id: String) -> result::Result<(), VmError> { if let Some(ref mut vm) = self.vm { - vm.remove_device(id) + vm.remove_device(&id) .inspect_err(|e| error!("Error when removing device from the VM: {e:?}"))?; Ok(()) } else if let Some(ref config) = self.vm_config { @@ -2271,7 +2271,7 @@ impl RequestHandler for Vmm { vm, #[cfg(all(feature = "kvm", target_arch = "x86_64"))] self.hypervisor.clone(), - send_data_migration.clone(), + &send_data_migration, ) .map_err(|migration_err| { error!("Migration failed: {migration_err:?}"); diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 8f4ff649b3..24eec5d41b 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -721,7 +721,7 @@ impl MemoryManager { fn fill_saved_regions( &mut self, file_path: PathBuf, - saved_regions: MemoryRangeTable, + saved_regions: &MemoryRangeTable, ) -> Result<(), Error> { if saved_regions.is_empty() { return Ok(()); @@ -1268,7 +1268,7 @@ impl MemoryManager { mm.lock() .unwrap() - .fill_saved_regions(memory_file_path, mem_snapshot.memory_ranges)?; + .fill_saved_regions(memory_file_path, &mem_snapshot.memory_ranges)?; Ok(mm) } else { @@ -1291,7 +1291,7 @@ impl MemoryManager { addr: *mut u8, len: u64, mode: u32, - nodemask: Vec, + nodemask: &[u64], maxnode: u64, flags: u32, ) -> Result<(), io::Error> { @@ -1438,7 +1438,7 @@ impl MemoryManager { // MPOL_BIND is the selected mode as it specifies a strict policy // that restricts memory allocation to the nodes specified in the // nodemask. - Self::mbind(addr, len, mode, nodemask, maxnode, flags) + Self::mbind(addr, len, mode, &nodemask, maxnode, flags) .map_err(Error::ApplyNumaPolicy)?; } diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index c4a1663809..ba9f1f455d 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -25,6 +25,7 @@ use vhost::vhost_kern::vhost_binding::{ VHOST_VDPA_SET_STATUS, VHOST_VDPA_SET_VRING_ENABLE, VHOST_VDPA_SUSPEND, }; +#[derive(Copy, Clone)] pub enum Thread { HttpApi, #[cfg(feature = "dbus_api")] diff --git a/vmm/src/sigwinch_listener.rs b/vmm/src/sigwinch_listener.rs index 970e82fcf4..4cbfeddf57 100644 --- a/vmm/src/sigwinch_listener.rs +++ b/vmm/src/sigwinch_listener.rs @@ -19,7 +19,7 @@ use libc::{ poll, pollfd, setsid, sigemptyset, siginfo_t, signal, sigprocmask, syscall, tcgetpgrp, tcsetpgrp, }; -use seccompiler::{BpfProgram, SeccompAction, apply_filter}; +use seccompiler::{BpfProgramRef, SeccompAction, apply_filter}; use vmm_sys_util::signal::register_signal_handler; use crate::clone3::{CLONE_CLEAR_SIGHAND, clone_args, clone3}; @@ -162,7 +162,7 @@ fn set_foreground_process_group(tty: &File) -> io::Result<()> { Ok(()) } -fn sigwinch_listener_main(seccomp_filter: BpfProgram, tx: File, tty: File) -> ! { +fn sigwinch_listener_main(seccomp_filter: BpfProgramRef, tx: File, tty: File) -> ! { // SAFETY: any references to these file descriptors are // unreachable, because this function never returns. unsafe { @@ -174,7 +174,7 @@ fn sigwinch_listener_main(seccomp_filter: BpfProgram, tx: File, tty: File) -> ! unblock_all_signals().unwrap(); if !seccomp_filter.is_empty() { - apply_filter(&seccomp_filter).unwrap(); + apply_filter(seccomp_filter).unwrap(); } register_signal_handler(SIGWINCH, sigwinch_handler).unwrap(); @@ -242,7 +242,7 @@ unsafe fn clone_clear_sighand() -> io::Result { Ok(r.try_into().unwrap()) } -pub fn start_sigwinch_listener(seccomp_filter: BpfProgram, tty_sub: File) -> io::Result { +pub fn start_sigwinch_listener(seccomp_filter: BpfProgramRef, tty_sub: File) -> io::Result { let mut pipe = [-1; 2]; // SAFETY: FFI call with valid arguments if unsafe { pipe2(pipe.as_mut_ptr(), O_CLOEXEC) } == -1 { @@ -275,7 +275,7 @@ pub fn listen_for_sigwinch_on_tty( let seccomp_filter = get_seccomp_filter(seccomp_action, Thread::PtyForeground, hypervisor_type).unwrap(); - let console_resize_pipe = start_sigwinch_listener(seccomp_filter, pty_sub)?; + let console_resize_pipe = start_sigwinch_listener(&seccomp_filter, pty_sub)?; Ok(console_resize_pipe) } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index fcd6fbfbec..ec455c6ba9 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -528,6 +528,7 @@ pub struct Vm { impl Vm { pub const HANDLED_SIGNALS: [i32; 1] = [SIGWINCH]; + #[allow(clippy::needless_pass_by_value)] #[allow(clippy::too_many_arguments)] pub fn new_from_memory_manager( config: Arc>, @@ -557,7 +558,7 @@ impl Vm { // Create NUMA nodes based on NumaConfig. let numa_nodes = - Self::create_numa_nodes(config.lock().unwrap().numa.clone(), &memory_manager)?; + Self::create_numa_nodes(config.lock().unwrap().numa.as_deref(), &memory_manager)?; #[cfg(feature = "tdx")] let tdx_enabled = config.lock().unwrap().is_tdx_enabled(); @@ -915,7 +916,7 @@ impl Vm { } fn create_numa_nodes( - configs: Option>, + configs: Option<&[NumaConfig]>, memory_manager: &Arc>, ) -> Result { let mm = memory_manager.lock().unwrap(); @@ -1148,6 +1149,7 @@ impl Vm { Ok(cmdline) } + #[allow(clippy::needless_pass_by_value)] #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_firmware( mut firmware: &File, @@ -1162,6 +1164,7 @@ impl Vm { }) } + #[allow(clippy::needless_pass_by_value)] #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_kernel( mut kernel: File, @@ -1197,6 +1200,7 @@ impl Vm { } #[cfg(feature = "igvm")] + #[allow(clippy::needless_pass_by_value)] fn load_igvm( igvm: File, memory_manager: Arc>, @@ -1231,6 +1235,7 @@ impl Vm { /// /// For x86_64, the boot path is the same. #[cfg(target_arch = "x86_64")] + #[allow(clippy::needless_pass_by_value)] fn load_kernel( mut kernel: File, cmdline: Option, @@ -1324,6 +1329,7 @@ impl Vm { } } + #[allow(clippy::needless_pass_by_value)] #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_payload( payload: &PayloadConfig, @@ -1521,7 +1527,7 @@ impl Vm { arch::configure_system( &mem, cmdline.as_cstring().unwrap().to_str().unwrap(), - vcpu_mpidrs, + &vcpu_mpidrs, vcpu_topology, device_info, &initramfs_config, @@ -1722,7 +1728,7 @@ impl Vm { Ok(()) } - pub fn resize_zone(&mut self, id: String, desired_memory: u64) -> Result<()> { + pub fn resize_zone(&mut self, id: &str, desired_memory: u64) -> Result<()> { let memory_config = &mut self.config.lock().unwrap().memory; if let Some(zones) = &mut memory_config.zones { @@ -1733,7 +1739,7 @@ impl Vm { self.memory_manager .lock() .unwrap() - .resize_zone(&id, desired_memory - zone.size) + .resize_zone(id, desired_memory - zone.size) .map_err(Error::MemoryManager)?; // We update the memory zone config regardless of the // actual 'resize-zone' operation result (happened or @@ -1805,16 +1811,16 @@ impl Vm { Ok(pci_device_info) } - pub fn remove_device(&mut self, id: String) -> Result<()> { + pub fn remove_device(&mut self, id: &str) -> Result<()> { self.device_manager .lock() .unwrap() - .remove_device(id.clone()) + .remove_device(id) .map_err(Error::DeviceManager)?; // Update VmConfig by removing the device. This is important to // ensure the device would not be created in case of a reboot. - self.config.lock().unwrap().remove_device(&id); + self.config.lock().unwrap().remove_device(id); self.device_manager .lock() @@ -2409,7 +2415,7 @@ impl Vm { self.cpu_manager .lock() .unwrap() - .configure_vcpu(vcpu.clone(), boot_setup) + .configure_vcpu(&vcpu, boot_setup) .map_err(Error::CpuManager)?; #[cfg(target_arch = "aarch64")] @@ -3562,13 +3568,12 @@ mod unit_tests { let hv = hypervisor::new().unwrap(); let vm = hv.create_vm(HypervisorVmConfig::default()).unwrap(); - let gic = vm - .create_vgic(Gic::create_default_config(1)) - .expect("Cannot create gic"); + let vgic_config = Gic::create_default_config(1); + let gic = vm.create_vgic(&vgic_config).expect("Cannot create gic"); create_fdt( &mem, "console=tty0", - vec![0], + &[0], Some((0, 0, 0, 0)), &dev_info, &gic, From afcb2b285f9ccac96faa2c6338011b2f038bcc1d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 26 Nov 2025 14:04:47 +0100 Subject: [PATCH 0365/1893] tpm: remove mixture of str and Path `impl AsRef` is the most idiomatic way to consume paths in Rust. I removed the mixture. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- devices/src/tpm.rs | 3 ++- tpm/src/emulator.rs | 8 +++++--- tpm/src/socket.rs | 9 ++++++--- vmm/src/device_manager.rs | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs index 86866b2e75..75a0a9e429 100644 --- a/devices/src/tpm.rs +++ b/devices/src/tpm.rs @@ -4,6 +4,7 @@ // use std::cmp; +use std::path::Path; use std::sync::{Arc, Barrier}; use anyhow::anyhow; @@ -227,7 +228,7 @@ pub struct Tpm { } impl Tpm { - pub fn new(path: &str) -> Result { + pub fn new(path: impl AsRef) -> Result { let emulator = Emulator::new(path) .map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {e:?}")))?; let mut tpm = Tpm { diff --git a/tpm/src/emulator.rs b/tpm/src/emulator.rs index 3699c3da7c..b27a069aa0 100644 --- a/tpm/src/emulator.rs +++ b/tpm/src/emulator.rs @@ -86,10 +86,12 @@ impl Emulator { /// /// * `path` - A path to the Unix Domain Socket swtpm is listening on /// - pub fn new(path: &str) -> Result { - if !Path::new(&path).exists() { + pub fn new(path: impl AsRef) -> Result { + let path = path.as_ref(); + if !path.exists() { return Err(Error::InitializeEmulator(anyhow!( - "The input TPM Socket path: {path:?} does not exist" + "The input TPM Socket path: {:?} does not exist", + path.to_str().unwrap() ))); } let mut socket = SocketDev::new(); diff --git a/tpm/src/socket.rs b/tpm/src/socket.rs index 4fe5b6370b..b777681531 100644 --- a/tpm/src/socket.rs +++ b/tpm/src/socket.rs @@ -6,6 +6,7 @@ use std::io::Read; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::UnixStream; +use std::path::Path; use anyhow::anyhow; use log::debug; @@ -58,21 +59,23 @@ impl SocketDev { } } - pub fn init(&mut self, path: &str) -> Result<()> { + pub fn init(&mut self, path: impl AsRef) -> Result<()> { self.connect(path)?; Ok(()) } - pub fn connect(&mut self, socket_path: &str) -> Result<()> { + pub fn connect(&mut self, socket_path: impl AsRef) -> Result<()> { + let socket_path = socket_path.as_ref(); self.state = SocketDevState::Connecting; + let socket_path_s = socket_path.to_str().unwrap(); let s = UnixStream::connect(socket_path).map_err(|e| { Error::ConnectToSocket(anyhow!("Failed to connect to tpm Socket. Error: {e:?}")) })?; self.control_fd = s.as_raw_fd(); self.stream = Some(s); self.state = SocketDevState::Connected; - debug!("Connected to tpm socket path : {socket_path:?}"); + debug!("Connected to tpm socket path : {socket_path_s:?}"); Ok(()) } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 93d863c52f..fac6ef7da0 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2491,7 +2491,7 @@ impl DeviceManager { tpm_path: &Path, ) -> DeviceManagerResult>> { // Create TPM Device - let tpm = devices::tpm::Tpm::new(tpm_path.to_str().unwrap()).map_err(|e| { + let tpm = devices::tpm::Tpm::new(tpm_path).map_err(|e| { DeviceManagerError::CreateTpmDevice(anyhow!("Failed to create TPM Device : {e:?}")) })?; let tpm = Arc::new(Mutex::new(tpm)); From b163fea66a33873e7ca4fa0bde84a95533902369 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 26 Nov 2025 15:24:55 -0800 Subject: [PATCH 0366/1893] hypervisor: update cache when a shared page is revoked During the lifecycle of a confidential VM, the guest may revoke previously shared pages via an attribute-intercept VM exit. When this happens, the host-side cache must be updated so that any subsequent access by the VMM triggers a fresh request for the guest to re-share the page. Signed-off-by: Muminul Islam --- hypervisor/src/mshv/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index d4ececd8e2..6b2d67864f 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -740,6 +740,14 @@ impl cpu::Vcpu for MshvVcpu { let mut gpas = Vec::new(); let ranges = info.ranges; let (gfn_start, gfn_count) = snp::parse_gpa_range(ranges[0]).unwrap(); + + // Update the bitmap(cache) to mark the pages as host inaccessible + self.host_access_pages.rcu(|bitmap| { + let bm = bitmap.clone(); + bm.reset_addr_range(gfn_start as usize, gfn_count as usize); + bm + }); + debug!("Releasing pages: gfn_start: {gfn_start:x?}, gfn_count: {gfn_count:?}"); let gpa_start = gfn_start * HV_PAGE_SIZE as u64; for i in 0..gfn_count { From 3d4324560825047645ae5f30b844f05d4dfdcaf3 Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Thu, 16 Oct 2025 15:01:00 +0200 Subject: [PATCH 0367/1893] vmm: allow comparing commands and extracting response length This will be useful later when we rebuild the live migration code as a state machine. On-behalf-of: SAP julian.stecklina@sap.com Signed-off-by: Julian Stecklina --- vm-migration/src/protocol.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index bc0d60e965..28cdc90838 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -52,7 +52,7 @@ use crate::bitpos_iterator::BitposIteratorExt; // The source can at any time send an "abandon request" to cancel #[repr(u16)] -#[derive(Copy, Clone, Default)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] pub enum Command { #[default] Invalid, @@ -176,6 +176,10 @@ impl Response { self.status } + pub fn length(&self) -> u64 { + self.length + } + pub fn read_from(fd: &mut dyn Read) -> Result { let mut response = Response::default(); fd.read_exact(Self::as_mut_slice(&mut response)) From 1861bc49e77342399adef3dd41b08e7d72615d58 Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Mon, 3 Nov 2025 16:27:12 +0100 Subject: [PATCH 0368/1893] vmm: simplify receiving memory fds ... and nuke some Option<> while I was there. Given that HashMap has a usable default and we end up passing an empty HashMap anyway, just get rid of the Option. On-behalf-of: SAP julian.stecklina@sap.com Signed-off-by: Julian Stecklina --- vmm/src/lib.rs | 54 +++++++++++++++++---------------------- vmm/src/memory_manager.rs | 6 ++--- vmm/src/vm.rs | 2 +- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 84db6a14cb..6b1f487792 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -820,11 +820,30 @@ impl Vmm { }) } + /// Try to receive a file descriptor from a socket. Returns the slot number and the file descriptor. + fn vm_receive_memory_fd( + socket: &mut SocketStream, + ) -> std::result::Result<(u32, File), MigratableError> { + if let SocketStream::Unix(unix_socket) = socket { + let mut buf = [0u8; 4]; + let (_, file) = unix_socket.recv_with_fd(&mut buf).map_err(|e| { + MigratableError::MigrateReceive(anyhow!("Error receiving slot from socket: {e}")) + })?; + + file.ok_or_else(|| MigratableError::MigrateReceive(anyhow!("Failed to receive socket"))) + .map(|file| (u32::from_le_bytes(buf), file)) + } else { + Err(MigratableError::MigrateReceive(anyhow!( + "Unsupported socket type" + ))) + } + } + fn vm_receive_config( &mut self, req: &Request, socket: &mut T, - existing_memory_files: Option>, + existing_memory_files: HashMap, ) -> std::result::Result>, MigratableError> where T: Read + Write, @@ -2126,7 +2145,7 @@ impl RequestHandler for Vmm { let mut started = false; let mut memory_manager: Option>> = None; - let mut existing_memory_files = None; + let mut existing_memory_files = vec![]; loop { let req = Request::read_from(&mut socket)?; match req.command() { @@ -2148,7 +2167,7 @@ impl RequestHandler for Vmm { memory_manager = Some(self.vm_receive_config( &req, &mut socket, - existing_memory_files.take(), + HashMap::from_iter(existing_memory_files.drain(..)), )?); } Command::State => { @@ -2190,34 +2209,9 @@ impl RequestHandler for Vmm { continue; } - match &mut socket { - SocketStream::Unix(unix_socket) => { - let mut buf = [0u8; 4]; - let (_, file) = unix_socket.recv_with_fd(&mut buf).map_err(|e| { - MigratableError::MigrateReceive(anyhow!( - "Error receiving slot from socket: {e}" - )) - })?; - - if existing_memory_files.is_none() { - existing_memory_files = Some(HashMap::default()); - } - - if let Some(ref mut existing_memory_files) = existing_memory_files { - let slot = u32::from_le_bytes(buf); - existing_memory_files.insert(slot, file.unwrap()); - } + existing_memory_files.push(Self::vm_receive_memory_fd(&mut socket)?); - Response::ok().write_to(&mut socket)?; - } - SocketStream::Tcp(_tcp_socket) => { - // For TCP sockets, we cannot transfer file descriptors - warn!( - "MemoryFd command received over TCP socket, which is not supported" - ); - Response::error().write_to(&mut socket)?; - } - } + Response::ok().write_to(&mut socket)?; } Command::Complete => { info!("Complete Command Received"); diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 24eec5d41b..1954c17116 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -995,7 +995,7 @@ impl MemoryManager { phys_bits: u8, #[cfg(feature = "tdx")] tdx_enabled: bool, restore_data: Option<&MemoryManagerSnapshotData>, - existing_memory_files: Option>, + existing_memory_files: HashMap, ) -> Result>, Error> { trace_scoped!("MemoryManager::new"); @@ -1030,7 +1030,7 @@ impl MemoryManager { &data.guest_ram_mappings, &zones, prefault, - existing_memory_files.unwrap_or_default(), + existing_memory_files, config.thp, )?; let guest_memory = @@ -1263,7 +1263,7 @@ impl MemoryManager { #[cfg(feature = "tdx")] false, Some(&mem_snapshot), - None, + Default::default(), )?; mm.lock() diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index ec455c6ba9..ada1b3a14c 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1056,7 +1056,7 @@ impl Vm { #[cfg(feature = "tdx")] tdx_enabled, None, - None, + Default::default(), ) .map_err(Error::MemoryManager)? }; From dbb148b216de354a0b2ccd87cf14014b5a2ab584 Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Thu, 9 Oct 2025 15:48:31 +0200 Subject: [PATCH 0369/1893] vmm: refactor live migration receive into state machine Previously, state that we accumulated during the migration process in the receiver was kept in `mut Option` variables or HashMaps. The problem is that it is unclear in the code when these variables can be safely used. It's also difficult to add new state, such as the state we need to handle the upcoming feature for performing the migration using multiple connections. To solve this, I've refactored the code to use the state machine pattern. Each state carries the data it needs to. Any state that didn't arrive yet (memory_files, memory_manager) cannot be accessed until we are in the proper state. Some benefits that fall out of this: - We now respond to all requests, even invalid ones, at least with an error message. - Any error handling a request will result in an error message being sent. - There is only a single place where responses are sent and thus it's very hard to mess this up in the code. - The main protocol state machine fits on a screen. I would argue that especially the error cases are now much more consistent. There is still a lot to be done. There is still state transfer via self.vm and similar. In an ideal world, this would also be carried by the state machine. I also want to see better handling of payloads, which are still handled all over the place, but this change is already too big. :) Co-authored-by: Philipp Schuster On-behalf-of: SAP julian.stecklina@sap.com On-behalf-of: SAP philipp.schuster@sap.com Signed-off-by: Julian Stecklina Signed-off-by: Philipp Schuster --- vmm/src/lib.rs | 249 +++++++++++++++++++++++++++++-------------------- 1 file changed, 150 insertions(+), 99 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 6b1f487792..7b4fbfe4d0 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -27,7 +27,7 @@ use console_devices::{ConsoleInfo, pre_create_console_devices}; use event_monitor::event; use landlock::LandlockError; use libc::{EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW, tcsetattr, termios}; -use log::{error, info, warn}; +use log::{error, info, trace, warn}; use memory_manager::MemoryManagerSnapshotData; use pci::PciBdf; use seccompiler::{SeccompAction, apply_filter}; @@ -668,6 +668,39 @@ pub struct Vmm { console_info: Option, } +/// The receiver's state machine behind the migration protocol. +enum ReceiveMigrationState { + /// The connection is established and we haven't received any commands yet. + Established, + + /// We received the start command. + Started, + + /// We received file descriptors for memory. This can only happen on UNIX domain sockets. + MemoryFdsReceived(Vec<(u32, File)>), + + /// We received the VM configuration. We keep the memory configuration around to populate guest memory. From this point on, the sender can start sending memory updates. + Configured(Arc>), + + /// Memory is populated and we received the state. The VM is ready to go. + StateReceived, + + /// The migration is successful. + Completed, + + /// The migration couldn't complete, either due to an error or because the sender abandoned the migration. + Aborted, +} + +impl ReceiveMigrationState { + fn finished(&self) -> bool { + matches!( + self, + ReceiveMigrationState::Completed | ReceiveMigrationState::Aborted + ) + } +} + impl Vmm { pub const HANDLED_SIGNALS: [i32; 2] = [SIGTERM, SIGINT]; @@ -839,6 +872,92 @@ impl Vmm { } } + /// Handle a migration command and advance the protocol state machine. + /// + /// **Note**: This function is responsible for consuming any payloads! It also must + /// _not_ write any response to the socket. + fn vm_receive_migration_step( + &mut self, + socket: &mut SocketStream, + state: ReceiveMigrationState, + req: &Request, + _receive_data_migration: &VmReceiveMigrationData, + ) -> std::result::Result { + use ReceiveMigrationState::*; + + let invalid_command = || { + Err(MigratableError::MigrateReceive(anyhow!( + "Can't handle command in current state" + ))) + }; + + let mut configure_vm = + |socket: &mut SocketStream, + memory_files: HashMap| + -> std::result::Result>, MigratableError> { + let memory_manager = self.vm_receive_config(req, socket, memory_files)?; + + Ok(memory_manager) + }; + + let recv_memory_fd = |socket: &mut SocketStream, + mut memory_files: Vec<(u32, File)>| + -> std::result::Result, MigratableError> { + let (slot, file) = Self::vm_receive_memory_fd(socket)?; + + memory_files.push((slot, file)); + Ok(memory_files) + }; + + if req.command() == Command::Abandon { + info!("Abandon Command Received"); + return Ok(Aborted); + } + + match state { + Established => match req.command() { + Command::Start => Ok(Started), + _ => invalid_command(), + }, + Started => match req.command() { + Command::MemoryFd => recv_memory_fd(socket, Vec::new()).map(MemoryFdsReceived), + Command::Config => configure_vm(socket, Default::default()).map(Configured), + _ => invalid_command(), + }, + MemoryFdsReceived(memory_files) => match req.command() { + Command::MemoryFd => recv_memory_fd(socket, memory_files).map(MemoryFdsReceived), + Command::Config => { + configure_vm(socket, HashMap::from_iter(memory_files)).map(Configured) + } + _ => invalid_command(), + }, + Configured(memory_manager) => match req.command() { + Command::Memory => { + self.vm_receive_memory(req, socket, &mut memory_manager.lock().unwrap())?; + Ok(Configured(memory_manager)) + } + Command::State => { + self.vm_receive_state(req, socket, memory_manager.clone())?; + Ok(StateReceived) + } + _ => invalid_command(), + }, + StateReceived => match req.command() { + Command::Complete => { + // The unwrap is safe, because the state machine makes sure we called + // vm_receive_state before, which creates the VM. + let vm = self.vm.as_mut().unwrap(); + vm.resume()?; + Ok(Completed) + } + _ => invalid_command(), + }, + Completed | Aborted => { + unreachable!("Performed a step on the finished state machine") + } + } + } + fn vm_receive_config( &mut self, req: &Request, @@ -846,7 +965,7 @@ impl Vmm { existing_memory_files: HashMap, ) -> std::result::Result>, MigratableError> where - T: Read + Write, + T: Read, { // Read in config data along with memory manager data let mut data: Vec = Vec::new(); @@ -926,8 +1045,6 @@ impl Vmm { )) })?; - Response::ok().write_to(socket)?; - Ok(memory_manager) } @@ -938,7 +1055,7 @@ impl Vmm { mm: Arc>, ) -> std::result::Result<(), MigratableError> where - T: Read + Write, + T: Read, { // Read in state data let mut data: Vec = Vec::new(); @@ -991,13 +1108,10 @@ impl Vmm { // Create VM vm.restore().map_err(|e| { - Response::error().write_to(socket).ok(); MigratableError::MigrateReceive(anyhow!("Failed restoring the Vm: {e}")) })?; self.vm = Some(vm); - Response::ok().write_to(socket)?; - Ok(()) } @@ -1008,18 +1122,13 @@ impl Vmm { memory_manager: &mut MemoryManager, ) -> std::result::Result<(), MigratableError> where - T: Read + ReadVolatile + Write, + T: Read + ReadVolatile, { // Read table let table = MemoryRangeTable::read_from(socket, req.length())?; // And then read the memory itself - memory_manager - .receive_memory_regions(&table, socket) - .inspect_err(|_| { - Response::error().write_to(socket).ok(); - })?; - Response::ok().write_to(socket)?; + memory_manager.receive_memory_regions(&table, socket)?; Ok(()) } @@ -2143,95 +2252,37 @@ impl RequestHandler for Vmm { // Accept the connection and get the socket let mut socket = Vmm::receive_migration_socket(&receive_data_migration.receiver_url)?; - let mut started = false; - let mut memory_manager: Option>> = None; - let mut existing_memory_files = vec![]; - loop { - let req = Request::read_from(&mut socket)?; - match req.command() { - Command::Invalid => info!("Invalid Command Received"), - Command::Start => { - info!("Start Command Received"); - started = true; - - Response::ok().write_to(&mut socket)?; - } - Command::Config => { - info!("Config Command Received"); - - if !started { - warn!("Migration not started yet"); - Response::error().write_to(&mut socket)?; - continue; - } - memory_manager = Some(self.vm_receive_config( - &req, - &mut socket, - HashMap::from_iter(existing_memory_files.drain(..)), - )?); - } - Command::State => { - info!("State Command Received"); + let mut state = ReceiveMigrationState::Established; - if !started { - warn!("Migration not started yet"); - Response::error().write_to(&mut socket)?; - continue; - } - if let Some(mm) = memory_manager.take() { - self.vm_receive_state(&req, &mut socket, mm)?; - } else { - warn!("Configuration not sent yet"); - Response::error().write_to(&mut socket)?; - } - } - Command::Memory => { - info!("Memory Command Received"); + while !state.finished() { + let req = Request::read_from(&mut socket)?; + trace!("Command {:?} received", req.command()); - if !started { - warn!("Migration not started yet"); - Response::error().write_to(&mut socket)?; - continue; - } - if let Some(mm) = memory_manager.as_ref() { - self.vm_receive_memory(&req, &mut socket, &mut mm.lock().unwrap())?; - } else { - warn!("Configuration not sent yet"); - Response::error().write_to(&mut socket)?; - } + let (response, new_state) = match self.vm_receive_migration_step( + &mut socket, + state, + &req, + &receive_data_migration, + ) { + Ok(next_state) => (Response::ok(), next_state), + Err(err) => { + warn!( + "Migration aborted as migration command {:?} failed: {}", + req.command(), + err + ); + (Response::error(), ReceiveMigrationState::Aborted) } - Command::MemoryFd => { - info!("MemoryFd Command Received"); - - if !started { - warn!("Migration not started yet"); - Response::error().write_to(&mut socket)?; - continue; - } + }; - existing_memory_files.push(Self::vm_receive_memory_fd(&mut socket)?); + state = new_state; + assert_eq!(response.length(), 0); + response.write_to(&mut socket)?; + } - Response::ok().write_to(&mut socket)?; - } - Command::Complete => { - info!("Complete Command Received"); - if let Some(ref mut vm) = self.vm.as_mut() { - vm.resume()?; - Response::ok().write_to(&mut socket)?; - } else { - warn!("VM not created yet"); - Response::error().write_to(&mut socket)?; - } - break; - } - Command::Abandon => { - info!("Abandon Command Received"); - self.vm = None; - self.vm_config = None; - Response::ok().write_to(&mut socket).ok(); - break; - } - } + if let ReceiveMigrationState::Aborted = state { + self.vm = None; + self.vm_config = None; } Ok(()) From a91235dab12fc02fe76d48328d2c384fe190dbb7 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Nov 2025 11:13:55 +0100 Subject: [PATCH 0370/1893] vm-migration: better describe migration protocol Reflect the latest migration protocol as mermaid diagrams in the (code) documentation. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vm-migration/src/protocol.rs | 133 +++++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 39 deletions(-) diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index 28cdc90838..3ae226ece2 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -3,6 +3,77 @@ // SPDX-License-Identifier: Apache-2.0 // +//! # Migration Protocol +//! +//! ## Cross-Host Migration +//! +//! A traditional network-based live migration where all resources are +//! transmitted over the wire. Externally-provided FDs must be opened and +//! managed by the management software on the destination side. +//! +//! **Supported migration modes**: +//! - TCP (currently one single connection) +//! +//! The following mermaid sequence diagram shows a brief overview: +//! +//! +//! ```mermaid +//! sequenceDiagram +//! Source<<->>Destination: Establish connection +//! Source->>Destination: Start +//! Destination-->>Source: OK +//! Source->>Destination: Config +//! Note right of Destination: Payload: VM Config +//! Destination-->>Source: OK +//! Note right of Source: Start Dirty Logging +//! loop Dirty Memory Ranges (until handover decision was made) +//! Source->>Destination: Memory +//! Note right of Destination: Payload: Memory Range Table +//! Note right of Destination: Payload: Memory Content +//! Destination-->>Source: OK +//! Note right of Source: VM is paused after last OK +//! end +//! Source->>Destination: Memory +//! Note right of Destination: Payload: Final Memory Range Table +//! Note right of Destination: Payload: Final Memory Content +//! Destination-->>Source: OK +//! Source->>Destination: State +//! Note right of Destination: Final VM State (vCPU, devices) +//! Destination-->>Source: OK +//! Source->>Destination: Complete +//! Destination-->>Source: OK +//! ``` +//! +//! ## Local Migration +//! +//! A simplified migration taking a few shortcuts and only working on the +//! same host. The VM memory is not transferred over the wire but instead +//! passed as memory FD. +//! +//! The following mermaid sequence diagram shows a brief overview: +//! +//! +//! ```mermaid +//! sequenceDiagram +//! Source<<->>Destination: Establish connection +//! Source->>Destination: Start +//! Destination-->>Source: OK +//! loop For each Memory FD +//! Source->>Destination: Memory FD (1/n) +//! Note right of Destination: Payload: (slot: u32, fd: u32) +//! Destination-->>Source: OK +//! end +//! Source->>Destination: Config +//! Note right of Destination: Payload: VM Config +//! Destination-->>Source: OK +//! Note right of Source: VM is paused +//! Source->>Destination: State +//! Note right of Destination: Payload: Final VM State (vCPU, devices) +//! Destination-->>Source: OK +//! Source->>Destination: Complete +//! Destination-->>Source: OK +//! ``` + use std::io::{Read, Write}; use itertools::Itertools; @@ -12,45 +83,29 @@ use vm_memory::ByteValued; use crate::MigratableError; use crate::bitpos_iterator::BitposIteratorExt; -// Migration protocol -// 1: Source establishes communication with destination (file socket or TCP connection.) -// (The establishment is out of scope.) -// 2: Source -> Dest : send "start command" -// 3: Dest -> Source : sends "ok response" when read to accept state data -// 4: Source -> Dest : sends "config command" followed by config data, length -// in command is length of config data -// 5: Dest -> Source : sends "ok response" when ready to accept memory data -// 6: Source -> Dest : send "memory command" followed by table of u64 pairs (GPA, size) -// followed by the memory described in those pairs. -// !! length is size of table i.e. 16 * number of ranges !! -// 7: Dest -> Source : sends "ok response" when ready to accept more memory data -// 8..(n-4): Repeat steps 6 and 7 until source has no more memory to send -// (n-3): Source -> Dest : sends "state command" followed by state data, length -// in command is length of config data -// (n-2): Dest -> Source : sends "ok response" -// (n-1): Source -> Dest : send "complete command" -// n: Dest -> Source: sends "ok response" -// -// "Local version": (Handing FDs across socket for memory) -// 1: Source establishes communication with destination (file socket or TCP connection.) -// (The establishment is out of scope.) -// 2: Source -> Dest : send "start command" -// 3: Dest -> Source : sends "ok response" when read to accept state data -// 4: Source -> Dest : sends "config command" followed by config data, length -// in command is length of config data -// 5: Dest -> Source : sends "ok response" when ready to accept memory data -// 6: Source -> Dest : send "memory fd command" followed by u16 slot ID and FD for memory -// 7: Dest -> Source : sends "ok response" when received -// 8..(n-4): Repeat steps 6 and 7 until source has no more memory to send -// (n-3): Source -> Dest : sends "state command" followed by state data, length -// in command is length of config data -// (n-2): Dest -> Source : sends "ok response" -// (n-1): Source -> Dest : send "complete command" -// n: Dest -> Source: sends "ok response" -// -// The destination can at any time send an "error response" to cancel -// The source can at any time send an "abandon request" to cancel - +/// The commands of the [live-migration protocol]. +/// +/// ### Sender State Machine +/// +/// TODO refactor sender into state machine and add diagram +/// +/// ### Receiver State Machine +/// +/// +/// ```mermaid +/// stateDiagram-v2 +/// direction TB +/// [*] --> Started: Start +/// Started --> MemoryFdsReceived: MemoryFd +/// MemoryFdsReceived --> MemoryFdsReceived: MemoryFd +/// Started --> Configured: Config +/// MemoryFdsReceived --> Configured: Config +/// Configured --> Configured: Memory +/// Configured --> StateReceived: State +/// StateReceived --> Completed: Complete +/// ``` +/// +/// [live-migration protocol]: super::protocol #[repr(u16)] #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] pub enum Command { From 4abebc9e562f74e18a27cd4e7ddb6b92a71b9d16 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 26 Nov 2025 12:28:05 +0000 Subject: [PATCH 0371/1893] hypervisor: Don't create temporary vector for boot MSRs The MSRs are constant at boot time so rather than creating a vector in the boot_msr_entries() method instead reaturn a reference to static MSR array data. Signed-off-by: Rob Bradford --- arch/src/x86_64/regs.rs | 2 +- hypervisor/src/cpu.rs | 2 +- hypervisor/src/kvm/mod.rs | 5 ++--- hypervisor/src/mshv/mod.rs | 5 ++--- vmm/src/cpu.rs | 2 +- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/arch/src/x86_64/regs.rs b/arch/src/x86_64/regs.rs index f02be41bcf..c93f39520b 100644 --- a/arch/src/x86_64/regs.rs +++ b/arch/src/x86_64/regs.rs @@ -82,7 +82,7 @@ pub fn setup_fpu(vcpu: &dyn hypervisor::Vcpu) -> Result<()> { /// /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. pub fn setup_msrs(vcpu: &dyn hypervisor::Vcpu) -> Result<()> { - vcpu.set_msrs(&vcpu.boot_msr_entries()) + vcpu.set_msrs(vcpu.boot_msr_entries()) .map_err(Error::SetModelSpecificRegisters)?; Ok(()) diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index 23a1632c1c..4bc348a98d 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -558,7 +558,7 @@ pub trait Vcpu: Send + Sync { /// /// Return the list of initial MSR entries for a VCPU /// - fn boot_msr_entries(&self) -> Vec; + fn boot_msr_entries(&self) -> &'static [MsrEntry]; #[cfg(target_arch = "x86_64")] /// diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 74ee19a30f..0398f01365 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -2732,10 +2732,10 @@ impl cpu::Vcpu for KvmVcpu { /// /// Return the list of initial MSR entries for a VCPU /// - fn boot_msr_entries(&self) -> Vec { + fn boot_msr_entries(&self) -> &'static [MsrEntry] { use crate::arch::x86::{MTRR_ENABLE, MTRR_MEM_TYPE_WB, msr_index}; - [ + &[ msr!(msr_index::MSR_IA32_SYSENTER_CS), msr!(msr_index::MSR_IA32_SYSENTER_ESP), msr!(msr_index::MSR_IA32_SYSENTER_EIP), @@ -2751,7 +2751,6 @@ impl cpu::Vcpu for KvmVcpu { ), msr_data!(msr_index::MSR_MTRRdefType, MTRR_ENABLE | MTRR_MEM_TYPE_WB), ] - .to_vec() } #[cfg(target_arch = "aarch64")] diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 6b2d67864f..fd2ddd1270 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -1449,10 +1449,10 @@ impl cpu::Vcpu for MshvVcpu { /// /// Return the list of initial MSR entries for a VCPU /// - fn boot_msr_entries(&self) -> Vec { + fn boot_msr_entries(&self) -> &'static [MsrEntry] { use crate::arch::x86::{MTRR_ENABLE, MTRR_MEM_TYPE_WB, msr_index}; - [ + &[ msr!(msr_index::MSR_IA32_SYSENTER_CS), msr!(msr_index::MSR_IA32_SYSENTER_ESP), msr!(msr_index::MSR_IA32_SYSENTER_EIP), @@ -1463,7 +1463,6 @@ impl cpu::Vcpu for MshvVcpu { msr!(msr_index::MSR_SYSCALL_MASK), msr_data!(msr_index::MSR_MTRRdefType, MTRR_ENABLE | MTRR_MEM_TYPE_WB), ] - .to_vec() } /// diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index e501b5c331..dfb4078095 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -3088,7 +3088,7 @@ mod unit_tests { // tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we // expect. let entry_vec = vcpu.boot_msr_entries(); - assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]); + assert_eq!(entry_vec[9], msrs.as_slice()[0]); } #[test] From 85556951a6964fb416f0d5aa4df4d1cb8eac1e89 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Sat, 29 Nov 2025 20:07:24 +0000 Subject: [PATCH 0372/1893] block: qcow2: Flush the buffer explicitly when writing pointer table Previously the code relies on the implicit flush when BufWriter is dropped. That's not safe. Per BufWriter's document: ``` It is critical to call flush before BufWriter is dropped. Though dropping will attempt to flush the contents of the buffer, any errors that happen in the process of dropping will be ignored. Calling flush ensures that the buffer is empty and thus dropping will not even attempt file operations. ``` Signed-off-by: Wei Liu --- block/src/qcow/qcow_raw_file.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index ceebfd1a9c..d3a9ad7abf 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -80,6 +80,7 @@ impl QcowRawFile { }; buffer.write_u64::(val)?; } + buffer.flush()?; Ok(()) } From 6897c2a462dc255302554586bcb8cddfcdca435e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 27 Nov 2025 09:39:30 +0100 Subject: [PATCH 0373/1893] block: qcow: Set OFLAG_COPIED bit in L1 entries for spec compliance The OFLAG_COPIED bit (bit 63) indicates a cluster's refcount is exactly 1 and doesn't need copy-on-write. This bit must be set in L1 entries when their referenced L2 clusters have refcount=1. Previously, L1 entries were always written as raw addresses without the OFLAG_COPIED bit, violating the QCOW2 specification and causing qemu-img check to report errors like `ERROR OFLAG_COPIED L2 cluster: l1_index=X .... refcount=1` The implementation queries each L2 cluster's refcount in sync_caches() and sets OFLAG_COPIED appropriately when writing the L1 table. This ensures QCOW2 images are specification compliant and maintain correct COW semantics to avoid data corruption. Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 251c1b7724..46a938418d 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -207,6 +207,11 @@ fn l2_entry_make_std(cluster_addr: u64) -> u64 { (cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG } +// Make L1 entry with optional flags +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) +} + /// Contains the information from the header of a qcow file. #[derive(Clone, Debug)] pub struct QcowHeader { @@ -1571,11 +1576,25 @@ impl QcowFile { // Push L1 table and refcount table last as all the clusters they point to are now // guaranteed to be valid. let mut sync_required = if self.l1_table.dirty() { - self.raw_file.write_pointer_table( - self.header.l1_table_offset, - self.l1_table.get_values(), - 0, - )?; + // Build L1 table with OFLAG_COPIED bits set correctly based on L2 cluster refcounts + let l1_active: Vec = self + .l1_table + .get_values() + .iter() + .map(|&l2_addr| { + if l2_addr == 0 { + Ok(0) + } else { + let refcount = self + .refcounts + .get_cluster_refcount(&mut self.raw_file, l2_addr) + .map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?; + Ok(l1_entry_make(l2_addr, refcount == 1)) + } + }) + .collect::>>()?; + self.raw_file + .write_pointer_table(self.header.l1_table_offset, &l1_active, 0)?; self.l1_table.mark_clean(); true } else { From deaf660a529c6a9c3a8e05d73067602b24ed7430 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 7 Oct 2025 16:46:18 -0700 Subject: [PATCH 0374/1893] vmm: simplify VM creation API Create HypervisorVmConfig early and pass the struct to VM creation API in the vmm crate. Getting rid of multiple conditional parameter. Signed-off-by: Muminul Islam --- vmm/src/lib.rs | 21 +++++++++++++++------ vmm/src/vm.rs | 26 ++------------------------ 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 7b4fbfe4d0..afb4d5215e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -210,6 +210,20 @@ pub enum Error { #[error("Error applying landlock")] ApplyLandlock(#[source] LandlockError), } + +impl From<&VmConfig> for hypervisor::HypervisorVmConfig { + fn from(_value: &VmConfig) -> Self { + hypervisor::HypervisorVmConfig { + #[cfg(feature = "tdx")] + tdx_enabled: _value.platform.as_ref().map(|p| p.tdx).unwrap_or(false), + #[cfg(feature = "sev_snp")] + sev_snp_enabled: _value.is_sev_snp_enabled(), + #[cfg(feature = "sev_snp")] + mem_size: _value.memory.total_size(), + } + } +} + pub type Result = result::Result; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -1006,12 +1020,7 @@ impl Vmm { let vm = Vm::create_hypervisor_vm( self.hypervisor.as_ref(), - #[cfg(feature = "tdx")] - false, - #[cfg(feature = "sev_snp")] - false, - #[cfg(feature = "sev_snp")] - config.lock().unwrap().memory.total_size(), + (&*self.vm_config.as_ref().unwrap().lock().unwrap()).into(), ) .map_err(|e| { MigratableError::MigrateReceive(anyhow!( diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index ada1b3a14c..79dca313c8 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1009,21 +1009,9 @@ impl Vm { vm_config.lock().unwrap().is_tdx_enabled() }; - #[cfg(feature = "sev_snp")] - let sev_snp_enabled = if snapshot.is_some() { - false - } else { - vm_config.lock().unwrap().is_sev_snp_enabled() - }; - let vm = Self::create_hypervisor_vm( hypervisor.as_ref(), - #[cfg(feature = "tdx")] - tdx_enabled, - #[cfg(feature = "sev_snp")] - sev_snp_enabled, - #[cfg(feature = "sev_snp")] - vm_config.lock().unwrap().memory.total_size(), + vm_config.as_ref().lock().unwrap().deref().into(), )?; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] @@ -1083,19 +1071,9 @@ impl Vm { pub fn create_hypervisor_vm( hypervisor: &dyn hypervisor::Hypervisor, - #[cfg(feature = "tdx")] tdx_enabled: bool, - #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, - #[cfg(feature = "sev_snp")] mem_size: u64, + config: HypervisorVmConfig, ) -> Result> { hypervisor.check_required_extensions().unwrap(); - let config = HypervisorVmConfig { - #[cfg(feature = "tdx")] - tdx_enabled, - #[cfg(feature = "sev_snp")] - sev_snp_enabled, - #[cfg(feature = "sev_snp")] - mem_size, - }; let vm = hypervisor.create_vm(config).unwrap(); From 0d5c914bdd86a79f284921f7f12ce5cb35879ee7 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 24 Sep 2025 15:29:35 -0700 Subject: [PATCH 0375/1893] build: use latest 0.6.5 mshv crates Use mshv-{ioctls, bindings) with the latest versions that have the nested MSHV support. Signed-off-by: Muminul Islam --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- fuzz/Cargo.lock | 4 ++-- fuzz/Cargo.toml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66e62e1f46..5310e31b29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,9 +1271,9 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mshv-bindings" -version = "0.6.0" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805cf329582f770f62cc612716a04c14815276ae266b6298375a672d3c5a5184" +checksum = "66f415da68542aca92b33f55ac3e93031dc30a2941952b99679258f7e0527353" dependencies = [ "libc", "num_enum", @@ -1285,9 +1285,9 @@ dependencies = [ [[package]] name = "mshv-ioctls" -version = "0.6.0" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aefaab4c067cf5226a917227640d835327b25b71a8d465f815f74f490344e10a" +checksum = "e52a2a02c4107e08f46ba9dfc4e0f4461dffd44fbeca3e5631b4a047d15376c9" dependencies = [ "libc", "mshv-bindings", diff --git a/Cargo.toml b/Cargo.toml index 54fc855451..aa256aceb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,8 +113,8 @@ 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.0" -mshv-ioctls = "0.6.0" +mshv-bindings = "0.6.5" +mshv-ioctls = "0.6.5" seccompiler = "0.5.0" vfio-bindings = { version = "0.6.0", default-features = false } vfio-ioctls = { version = "0.5.1", default-features = false } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index ee1692f068..f613bf9df6 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -702,9 +702,9 @@ dependencies = [ [[package]] name = "mshv-bindings" -version = "0.6.0" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805cf329582f770f62cc612716a04c14815276ae266b6298375a672d3c5a5184" +checksum = "66f415da68542aca92b33f55ac3e93031dc30a2941952b99679258f7e0527353" dependencies = [ "libc", "num_enum", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 319a4ef321..7caeddbbd6 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -25,7 +25,7 @@ libc = "0.2.177" libfuzzer-sys = "0.4.10" linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } -mshv-bindings = "0.6.0" +mshv-bindings = "0.6.5" net_util = { path = "../net_util" } seccompiler = "0.5.0" virtio-devices = { path = "../virtio-devices" } From f10d0bb33df6db255df5126aeb567cf58809e21d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 24 Sep 2025 15:41:52 -0700 Subject: [PATCH 0376/1893] vmm: add nested option to CPU config Add an option in the CLI to enable nested support. Adding an option --cpu nested=on|off to the CPU argument to enable nested support for Microsoft Hypervisor. By default it is enabled for both KVM and MSHV. Signed-off-by: Muminul Islam --- fuzz/fuzz_targets/http_api.rs | 1 + src/main.rs | 4 +++- vmm/src/config.rs | 9 ++++++++- vmm/src/lib.rs | 3 ++- vmm/src/vm_config.rs | 7 +++++++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index e9965ceddf..6c00216a0d 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -132,6 +132,7 @@ impl RequestHandler for StubApiRequestHandler { max_phys_bits: 46, affinity: None, features: CpuFeatures::default(), + nested: true, }, memory: MemoryConfig { size: 536_870_912, diff --git a/src/main.rs b/src/main.rs index a5ff044f99..10d73b5940 100644 --- a/src/main.rs +++ b/src/main.rs @@ -213,7 +213,8 @@ fn get_cli_options_sorted( topology=:::,\ kvm_hyperv=on|off,max_phys_bits=,\ affinity=,\ - features=", + features=,\ + nested=on|off", ) .default_value(default_vcpus) .group("vm-config"), @@ -962,6 +963,7 @@ mod unit_tests { max_phys_bits: 46, affinity: None, features: CpuFeatures::default(), + nested: true, }, memory: MemoryConfig { size: 536_870_912, diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 3c99eb26f7..c5a74d5e46 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -598,7 +598,8 @@ impl CpusConfig { .add("kvm_hyperv") .add("max_phys_bits") .add("affinity") - .add("features"); + .add("features") + .add("nested"); parser.parse(cpus).map_err(Error::ParseCpus)?; let boot_vcpus: u32 = parser @@ -653,6 +654,11 @@ impl CpusConfig { }?; } + let nested = parser + .convert::("nested") + .map_err(Error::ParseCpus)? + .is_none_or(|toggle| toggle.0); + Ok(CpusConfig { boot_vcpus, max_vcpus, @@ -661,6 +667,7 @@ impl CpusConfig { max_phys_bits, affinity, features, + nested, }) } } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index afb4d5215e..f4b46dc4b5 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -215,7 +215,7 @@ impl From<&VmConfig> for hypervisor::HypervisorVmConfig { fn from(_value: &VmConfig) -> Self { hypervisor::HypervisorVmConfig { #[cfg(feature = "tdx")] - tdx_enabled: _value.platform.as_ref().map(|p| p.tdx).unwrap_or(false), + tdx_enabled: _value.platform.as_ref().is_some_and(|p| p.tdx), #[cfg(feature = "sev_snp")] sev_snp_enabled: _value.is_sev_snp_enabled(), #[cfg(feature = "sev_snp")] @@ -2397,6 +2397,7 @@ mod unit_tests { max_phys_bits: 46, affinity: None, features: CpuFeatures::default(), + nested: true, }, memory: MemoryConfig { size: 536_870_912, diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 662a9b4099..9c28e536da 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -69,6 +69,8 @@ pub struct CpusConfig { pub affinity: Option>, #[serde(default)] pub features: CpuFeatures, + #[serde(default = "default_cpusconfig_nested")] + pub nested: bool, } pub const DEFAULT_VCPUS: u32 = 1; @@ -83,6 +85,7 @@ impl Default for CpusConfig { max_phys_bits: DEFAULT_MAX_PHYS_BITS, affinity: None, features: CpuFeatures::default(), + nested: true, } } } @@ -177,6 +180,10 @@ fn default_memoryconfig_thp() -> bool { true } +fn default_cpusconfig_nested() -> bool { + true +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct MemoryConfig { pub size: u64, From d8360ddc8e14e0b62ce8ca0ad6a083ea148c0874 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 7 Oct 2025 16:25:48 -0700 Subject: [PATCH 0377/1893] hypervisor: vmm: passing nested argument from VMM to hypervisor This patch connects the VMM and hypervisor to fully enable nested support for MSHV. Signed-off-by: Muminul Islam --- hypervisor/src/lib.rs | 1 + hypervisor/src/mshv/mod.rs | 42 ++++++++++++++++++++++++++++++++++---- vmm/src/lib.rs | 1 + 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 1899d282b1..77e1f9a438 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -170,6 +170,7 @@ pub struct HypervisorVmConfig { pub sev_snp_enabled: bool, #[cfg(feature = "sev_snp")] pub mem_size: u64, + pub nested: bool, } #[derive(Copy, Clone)] diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index fd2ddd1270..90173d011f 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -14,7 +14,10 @@ use log::{debug, warn}; use mshv_bindings::*; #[cfg(target_arch = "x86_64")] use mshv_ioctls::InterruptRequest; -use mshv_ioctls::{Mshv, NoDatamatch, VcpuFd, VmFd, VmType, set_registers_64}; +use mshv_ioctls::{ + Mshv, NoDatamatch, VcpuFd, VmFd, VmType, make_default_partition_create_arg, + make_default_synthetic_features_mask, set_registers_64, +}; use vfio_ioctls::VfioDeviceFd; use vm::DataMatch; #[cfg(feature = "sev_snp")] @@ -292,10 +295,37 @@ impl hypervisor::Hypervisor for MshvHypervisor { VmType::Normal }; } - + let mut create_args = make_default_partition_create_arg(mshv_vm_type); + let mut disable_proc_features = hv_partition_processor_features::default(); + // SAFETY: Accessing a union element from bindgen generated bindings. + unsafe { + for i in 0..create_args.pt_num_cpu_fbanks { + disable_proc_features.as_uint64[i as usize] = create_args.pt_cpu_fbanks[i as usize]; + } + #[cfg(target_arch = "x86_64")] + { + // Modify create_args based on user configuration + // For now we only handle nested virtualization, but more features can be added here + if _config.nested { + create_args.pt_flags |= 1 << MSHV_PT_BIT_NESTED_VIRTUALIZATION; + disable_proc_features + .__bindgen_anon_1 + .set_nested_virt_support(0u64); + } else { + disable_proc_features + .__bindgen_anon_1 + .set_nested_virt_support(1u64); + } + } + // Modified feature bit fields are written back to create_args + for i in 0..create_args.pt_num_cpu_fbanks { + create_args.pt_cpu_fbanks[i as usize] = disable_proc_features.as_uint64[i as usize]; + } + } + let synthetic_features_mask = make_default_synthetic_features_mask(); let fd: VmFd; loop { - match self.mshv.create_vm_with_type(mshv_vm_type) { + match self.mshv.create_vm_with_args(&create_args) { Ok(res) => fd = res, Err(e) => { if e.errno() == libc::EINTR { @@ -309,7 +339,11 @@ impl hypervisor::Hypervisor for MshvHypervisor { } break; } - + fd.set_partition_property( + hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES, + synthetic_features_mask, + ) + .map_err(|e| hypervisor::HypervisorError::SetPartitionProperty(e.into()))?; let vm_fd = Arc::new(fd); #[cfg(target_arch = "x86_64")] diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index f4b46dc4b5..0298efc06c 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -220,6 +220,7 @@ impl From<&VmConfig> for hypervisor::HypervisorVmConfig { sev_snp_enabled: _value.is_sev_snp_enabled(), #[cfg(feature = "sev_snp")] mem_size: _value.memory.total_size(), + nested: _value.cpus.nested, } } } From 658a7f91751514c407fa835dc76ae7aad7b5c13a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Mon, 27 Oct 2025 16:28:21 -0700 Subject: [PATCH 0378/1893] arch: vmm: disable nested virtualization if needed User can now disable nested virtualization for Intel and AMD if configured by the CLI. Signed-off-by: Muminul Islam --- arch/src/x86_64/mod.rs | 15 +++++++++++++++ vmm/src/config.rs | 8 ++++++++ vmm/src/cpu.rs | 3 +++ 3 files changed, 26 insertions(+) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 17bae5f5a8..41ea7514e3 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -44,6 +44,8 @@ pub const MAX_SUPPORTED_CPUS_LEGACY: u32 = 254; #[cfg(feature = "kvm")] const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit. const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit. +const VMX_ECX_BIT: u8 = 5; // VMX for Intel +const SVM_ECX_BIT: u8 = 2; // SVM for AMD const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit. const INVARIANT_TSC_EDX_BIT: u8 = 8; // Invariant TSC bit on 0x8000_0007 EDX const AMX_BF16: u8 = 22; // AMX tile computation on bfloat16 numbers @@ -806,6 +808,7 @@ pub fn generate_common_cpuid( Ok(cpuid) } +#[allow(clippy::too_many_arguments)] pub fn configure_vcpu( vcpu: &dyn hypervisor::Vcpu, id: u32, @@ -814,6 +817,7 @@ pub fn configure_vcpu( kvm_hyperv: bool, cpu_vendor: CpuVendor, topology: (u16, u16, u16, u16), + nested: bool, ) -> super::Result<()> { let x2apic_id = get_x2apic_id(id, Some(topology)); @@ -832,6 +836,17 @@ 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); + } + break; + } + if entry.function == 0x8000_0001 { + if !nested { + // Disable the nested virtualization for AMD + entry.ecx &= !(1 << SVM_ECX_BIT); + } break; } } diff --git a/vmm/src/config.rs b/vmm/src/config.rs index c5a74d5e46..3e41a83baa 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -659,6 +659,14 @@ impl CpusConfig { .map_err(Error::ParseCpus)? .is_none_or(|toggle| toggle.0); + // Nested virtualization is always turned on for aarch64 and riscv64 + // TODO: revisit this when nested support can be turned of on these architectures + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + if !nested { + return Err(Error::ParseCpus(OptionParserError::InvalidValue( + "nested=off is not supported on aarch64 and riscv64 architectures".to_string(), + ))); + } Ok(CpusConfig { boot_vcpus, max_vcpus, diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index dfb4078095..b219265f23 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -455,6 +455,7 @@ impl Vcpu { #[cfg(target_arch = "x86_64")] cpuid: Vec, #[cfg(target_arch = "x86_64")] kvm_hyperv: bool, #[cfg(target_arch = "x86_64")] topology: (u16, u16, u16, u16), + #[cfg(target_arch = "x86_64")] nested: bool, ) -> Result<()> { #[cfg(target_arch = "aarch64")] { @@ -475,6 +476,7 @@ impl Vcpu { kvm_hyperv, self.vendor, topology, + nested, ) .map_err(Error::VcpuConfiguration)?; @@ -995,6 +997,7 @@ impl CpuManager { self.cpuid.clone(), self.config.kvm_hyperv, topology, + self.config.nested, )?; #[cfg(target_arch = "aarch64")] From 5fb50edb5541ebbc6110b65160fa19f6f22d3bb0 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Mon, 1 Dec 2025 12:02:36 -0800 Subject: [PATCH 0379/1893] docs: update guide for nested CPU feature This patch updates the documentation to reflect the newly added nested CPU feature option in the CLI. Signed-off-by: Muminul Islam --- docs/cpu.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/cpu.md b/docs/cpu.md index 5dd797315b..56e7f3aec6 100644 --- a/docs/cpu.md +++ b/docs/cpu.md @@ -18,11 +18,12 @@ struct CpusConfig { max_phys_bits: u8, affinity: Option>, features: CpuFeatures, + nested: bool, } ``` ``` ---cpus boot=,max=,topology=:::,kvm_hyperv=on|off,max_phys_bits=,affinity=,features= +--cpus boot=,max=,topology=:::,kvm_hyperv=on|off,max_phys_bits=,affinity=,features=,nested=on|off ``` ### `boot` @@ -209,3 +210,14 @@ _Example_ ``` In this example the amx CPU feature will be enabled for the VMM. + + +### `nested` + +Enable nested virtualization (default on). Nested virtualization is needed to access hardware virtualization by this guest. This option can only be changed on x86-64. + +_Example_ + +``` +--cpus nested=on +``` \ No newline at end of file From 01383538f3e16684d8c4ca58756e36cbe2a907ec Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 4 Dec 2025 11:35:45 +0100 Subject: [PATCH 0380/1893] hypervisor: Introduce an enable AMX tile state components method Currently when the user configures AMX the corresponding state components get dynamically enabled directly inside the function body of vmm::cpu::CpuManager::new. With our ongoing work on CPU templates/profiles, there will (likely) be one more binary crate for producing CPU profiles that also needs to do this (without creating a CpuManager) and it may also be the case that we will need to call this function prior to `CpuManager::new` during live migrations. We thus add a method for enabling the AMX tile state components on the hypervisor trait that may be called wherever necessary. We argue that this is beneficial for code clarity independently of the upcoming CPU templates/profiles PR that we are working on. The astute reader will notice that the logic introduced here is not 100% the same as what is done inside the vmm::cpu::Cpumanager::new method. We claim that our approach is more in-line with the official documentation. Signed-off-by: Oliver Anderson On-behalf-of: SAP oliver.anderson@sap.com --- hypervisor/src/arch/x86/mod.rs | 82 ++++++++++++++++++++++++++++++++++ hypervisor/src/hypervisor.rs | 22 +++++++++ 2 files changed, 104 insertions(+) diff --git a/hypervisor/src/arch/x86/mod.rs b/hypervisor/src/arch/x86/mod.rs index fbe2cee2cb..b1b2957f62 100644 --- a/hypervisor/src/arch/x86/mod.rs +++ b/hypervisor/src/arch/x86/mod.rs @@ -13,6 +13,10 @@ use core::fmt; +use thiserror::Error; + +use crate::CpuVendor; + #[cfg(all(feature = "mshv_emulator", target_arch = "x86_64"))] pub mod emulator; pub mod gdt; @@ -319,3 +323,81 @@ impl Default for XsaveState { unsafe { ::std::mem::zeroed() } } } + +const ARCH_GET_XCOMP_SUPP: usize = 0x1021; +const ARCH_REQ_XCOMP_GUEST_PERM: usize = 0x1025; +const ARCH_XCOMP_TILECFG: usize = 17; +const ARCH_XCOMP_TILEDATA: usize = 18; + +/// Checks whether the host supports AMX. +/// +/// Returns `Ok` if AMX is supported on the host and `Err` otherwise. +pub(crate) fn amx_supported(cpu_vendor: CpuVendor) -> Result<(), AmxGuestSupportError> { + if !matches!(cpu_vendor, CpuVendor::Intel) { + return Err(AmxGuestSupportError::VendorDoesNotSupportAmx); + } + // We make a syscall to get information about which dynamically enabled + // XSAVE state components are supported. The corresponding state + // component bits will get set in `features` + let mut features: usize = 0; + // SAFETY: Syscall with valid parameters + let result = + unsafe { libc::syscall(libc::SYS_arch_prctl, ARCH_GET_XCOMP_SUPP, &raw mut features) }; + // Ensure that both the TILECFG and TILEDATA state components are supported + let mask = (1 << ARCH_XCOMP_TILECFG) | (1 << ARCH_XCOMP_TILEDATA); + if result != 0 { + return Err(AmxGuestSupportError::AmxNotSupported { errno: result }); + } + + if (features & mask) == mask { + Ok(()) + } else { + Err(AmxGuestSupportError::InvalidAmxTileFeatureCheck { features }) + } +} + +/// Asks the kernel to provide AMX support for guests. +pub(crate) fn request_guest_amx_support() -> Result<(), AmxGuestSupportError> { + // Make a syscall to request permission for guests to use the TILECFG + // and TILEDATA state components. Note that as per the kernel + // [documentation](https://docs.kernel.org/arch/x86/xstate.html#dynamic-features-for-virtual-machines) + // we need to pass in the number of the highest XSTATE component which is required for + // the facility to work which in this case is TILEDATA. + // + // This syscall will alter the size of `kvm_xsave` when KVM is used as the hypervisor. + // + // SAFETY: Syscall with valid parameters + let result = unsafe { + libc::syscall( + libc::SYS_arch_prctl, + ARCH_REQ_XCOMP_GUEST_PERM, + ARCH_XCOMP_TILEDATA, + ) + }; + if result == 0 { + Ok(()) + } else { + // Unwrap is OK because we verified that `result` is not zero + Err(AmxGuestSupportError::AmxGuestTileRequest { errno: result }) + } +} + +/// Error that may be returned when attempting to enable AMX state components for guests +#[derive(Debug, Error)] +pub enum AmxGuestSupportError { + /// Attempted to enable AMX on a CPU from a vendor that is not known to support AMX features. + #[error("The host CPU's vendor does not support AMX features. Only Intel provides such CPUs.")] + VendorDoesNotSupportAmx, + /// Unable to verify that the host supports AMX. + #[error("The host does not support AMX tile state components: errno={errno}")] + AmxNotSupported { errno: i64 }, + /// The syscall to check for AMX tile state support succeeded, but the returned + /// features did not match our expectations. + #[error( + "Could not verify AMX support. These are the supported features that were reported: features={features}" + )] + InvalidAmxTileFeatureCheck { features: usize }, + /// The request to enable AMX related state components for guests failed. + #[error("Failed to enable AMX tile state components for guests: errno={errno}")] + AmxGuestTileRequest { errno: i64 }, +} diff --git a/hypervisor/src/hypervisor.rs b/hypervisor/src/hypervisor.rs index 5db46d9908..fdc551c28e 100644 --- a/hypervisor/src/hypervisor.rs +++ b/hypervisor/src/hypervisor.rs @@ -89,6 +89,13 @@ pub enum HypervisorError { /// #[error("Unsupported VmType")] UnsupportedVmType(), + + /// + /// The attempt to enable AMX tile state components failed + /// + #[cfg(target_arch = "x86_64")] + #[error("Failed to enable AMX tile state components")] + CouldNotEnableAmxStateComponents(#[source] crate::arch::x86::AmxGuestSupportError), } /// @@ -166,4 +173,19 @@ pub trait Hypervisor: Send + Sync { } } } + + /// This function enables the AMX related TILECFG and TILEDATA state components for guests. + /// + /// # Background + /// AMX uses a concept of tiles which are small 2D blocks of data stored in registers on the CPU, + /// where the TILECFG state component defines the shape and size of each tile (rows and columns), + /// and the TILEDATA state component holds the actual elements of these tiles used by matrix operations. + #[cfg(target_arch = "x86_64")] + fn enable_amx_state_components(&self) -> Result<()> { + let cpu_vendor = self.get_cpu_vendor(); + crate::arch::x86::amx_supported(cpu_vendor) + .map_err(HypervisorError::CouldNotEnableAmxStateComponents)?; + crate::arch::x86::request_guest_amx_support() + .map_err(HypervisorError::CouldNotEnableAmxStateComponents) + } } From 5ec1e66e22ad736f1ed0dba5fabd7340fea33de1 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 4 Dec 2025 12:51:46 +0100 Subject: [PATCH 0381/1893] vmm: Refactor amx tile state component enabling logic We enable AMX tile state components via the hypervisor (as introduced in the previous commit) instead of doing this inline in the body of `CpuManager::new`. Signed-off-by: Oliver Anderson On-behalf-of: SAP oliver.anderson@sap.com --- vmm/src/cpu.rs | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index b219265f23..e7c8a699fd 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -791,38 +791,9 @@ impl CpuManager { #[cfg(target_arch = "x86_64")] if config.features.amx { - const ARCH_GET_XCOMP_GUEST_PERM: usize = 0x1024; - const ARCH_REQ_XCOMP_GUEST_PERM: usize = 0x1025; - const XFEATURE_XTILEDATA: usize = 18; - const XFEATURE_XTILEDATA_MASK: usize = 1 << XFEATURE_XTILEDATA; - - // SAFETY: the syscall is only modifying kernel internal - // data structures that the kernel is itself expected to safeguard. - let amx_tile = unsafe { - libc::syscall( - libc::SYS_arch_prctl, - ARCH_REQ_XCOMP_GUEST_PERM, - XFEATURE_XTILEDATA, - ) - }; - - if amx_tile != 0 { - return Err(Error::AmxEnable(anyhow!("Guest AMX usage not supported"))); - } - let mut mask: usize = 0; - // SAFETY: Syscall with valid parameters. We use a raw mutable pointer to - // the `mask` place in order to ensure that we do not violate Rust's - // aliasing rules. - let result = unsafe { - libc::syscall( - libc::SYS_arch_prctl, - ARCH_GET_XCOMP_GUEST_PERM, - &raw mut mask, - ) - }; - if result != 0 || (mask & XFEATURE_XTILEDATA_MASK) != XFEATURE_XTILEDATA_MASK { - return Err(Error::AmxEnable(anyhow!("Guest AMX usage not supported"))); - } + hypervisor + .enable_amx_state_components() + .map_err(|e| Error::AmxEnable(e.into()))?; } let proximity_domain_per_cpu: BTreeMap = { From 801059c2d52be8efd03a050662f287527ee06521 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 4 Dec 2025 10:34:53 +0100 Subject: [PATCH 0382/1893] scripts: Update custom image to Ubuntu Noble Numbat Updating the helper script to create a VFIO custom image from Ubuntu 22.04 to Ubuntu 24.04. Signed-off-by: Sebastien Boeuf --- scripts/build-custom-image.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/build-custom-image.sh b/scripts/build-custom-image.sh index a138eff373..68536db6e5 100644 --- a/scripts/build-custom-image.sh +++ b/scripts/build-custom-image.sh @@ -7,13 +7,13 @@ set -ex mkdir -p custom-image pushd custom-image || exit -wget -N https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img -export IMAGE_NAME_BASE=jammy-server-cloudimg-amd64 +wget -N https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img +export IMAGE_NAME_BASE=noble-server-cloudimg-amd64 qemu-img convert -p -f qcow2 -O raw $IMAGE_NAME_BASE.img $IMAGE_NAME_BASE.raw if [ -n "$VFIO_CUSTOM_IMAGE" ]; then - qemu-img resize -f raw "$IMAGE_NAME_BASE.raw" 5G + qemu-img resize -f raw "$IMAGE_NAME_BASE.raw" 10G sudo sgdisk -e "$IMAGE_NAME_BASE.raw" - sudo parted "$IMAGE_NAME_BASE.raw" resizepart 1 5369MB + sudo parted "$IMAGE_NAME_BASE.raw" resizepart 1 10737MB fi mkdir -p mnt export ROOTFS=/dev/mapper/$(sudo kpartx -v -a $IMAGE_NAME_BASE.raw | grep "p1 " | cut -f 3 -d " ") @@ -28,7 +28,7 @@ touch extra_commands if [ -n "$VFIO_CUSTOM_IMAGE" ]; then cat >extra_commands < Date: Fri, 5 Dec 2025 13:02:45 +0100 Subject: [PATCH 0383/1893] ci: dco: rename misleading job title "Signed-off-by" is the only variant that is accepted. So we should remove the inconsistency to prevent: - user forgets this at all - CI complains - user adds "Signed-Off-By" - CI still complains because of the wrong format Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- .github/workflows/dco.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dco.yaml b/.github/workflows/dco.yaml index 98412e5e22..655c0b5e2f 100644 --- a/.github/workflows/dco.yaml +++ b/.github/workflows/dco.yaml @@ -3,7 +3,7 @@ on: [pull_request, merge_group] jobs: check: - name: DCO Check ("Signed-Off-By") + name: DCO Check ("Signed-off-by") runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 From 9e2e85a48fc52092c02c7e15e6fdc6f1dc8df552 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 27 Nov 2025 09:43:17 +0100 Subject: [PATCH 0384/1893] block: qcow: Fix refcount leak when refcount blocks are replaced When a refcount block is evicted from cache and replaced with a new one, the old refcount block cluster was added to unref_clusters but its refcount was never decremented to 0 on disk. This left the cluster with refcount=1 while no metadata referenced it, causing errors in qemu-img check `Leaked cluster X refcount=1 reference=0` This fix recursively calls set_cluster_refcount(freed_cluster, 0) to properly decrement the freed refcount block's refcount on disk. The recursion handles cascading replacements where freeing one refcount block may trigger the replacement of another. Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 46a938418d..400e30a829 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1518,7 +1518,9 @@ impl QcowFile { refcount_set = true; } Ok(Some(freed_cluster)) => { - unref_clusters.push(freed_cluster); + // Recursively set the freed refcount block's refcount to 0 + let mut freed = self.set_cluster_refcount(freed_cluster, 0)?; + unref_clusters.append(&mut freed); refcount_set = true; } Err(refcount::Error::EvictingRefCounts(e)) => { From efad6578d1ee3d094359c56f14f1f5c0d8ec81a0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 30 Nov 2025 20:14:35 +0100 Subject: [PATCH 0385/1893] block: qcow: Fix refcount leak when converting compressed clusters When converting a compressed cluster to standard during write operations, the old compressed cluster's refcount was never decremented, causing leak warnings by `qemu-img check ..` `Leaked cluster X refcount=N reference=M` Additionally, compressed data can span multiple physical clusters, not just one. The compressed cluster address and size are encoded in the L2 entry, and the data may cross cluster boundaries. The proper handling is implemented as follows: - Extract compressed cluster address and size before overwriting L2 entry - Identify all clusters occupied by the compressed data - Decrement refcount for each cluster in the range Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 25 +++++++++++++++++++++++++ block/src/qcow/qcow_raw_file.rs | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 400e30a829..6effc468fb 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1204,6 +1204,10 @@ impl QcowFile { let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; let cluster_addr = if l2_entry_is_compressed(l2_entry) { // Writing to compressed cluster. + + let (compressed_cluster_addr, compressed_cluster_size) = + l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); + // Allocate new cluster, decompress into new cluster, then use // offset of new cluster. let decompressed_cluster = self.decompress_l2_cluster(l2_entry)?; @@ -1216,6 +1220,27 @@ impl QcowFile { if nwritten != decompressed_cluster.len() { return Err(std::io::Error::from_raw_os_error(EIO)); } + + // Decrement refcount for each cluster spanned by the old compressed data + let compressed_clusters_end = self.raw_file.cluster_address( + compressed_cluster_addr // Start of compressed data + + compressed_cluster_size as u64 // Add size to get end address + + self.raw_file.cluster_size() + - 1, // Catch possibly partially used last cluster + ); + let mut addr = self.raw_file.cluster_address(compressed_cluster_addr); + while addr < compressed_clusters_end { + let refcount = self + .refcounts + .get_cluster_refcount(&mut self.raw_file, addr) + .map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?; + if refcount > 0 { + let mut newly_unref = self.set_cluster_refcount(addr, refcount - 1)?; + self.unref_clusters.append(&mut newly_unref); + } + addr += self.raw_file.cluster_size(); + } + cluster_addr } else if l2_entry_is_empty(l2_entry) { let initial_data = if let Some(backing) = self.backing_file.as_mut() { diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index d3a9ad7abf..1d9dbc0b52 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -136,6 +136,11 @@ impl QcowRawFile { address & self.cluster_mask } + /// Returns the base address of the cluster containing `address`. + pub fn cluster_address(&self, address: u64) -> u64 { + address & !self.cluster_mask + } + /// Zeros out a cluster in the file. pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> { let cluster_size = self.cluster_size as usize; From f1ffd795e01ff53c7a4a724fa730dabdd8af49ed Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 3 Dec 2025 14:32:15 +0100 Subject: [PATCH 0386/1893] block: qcow: tests: Update combo_write_read for cluster leak fixes Freed clusters correctly have refcount=0. Remove the assertion that expected no clusters with zero refcount, as it was validating the buggy behavior. Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 6effc468fb..4540d0e7e3 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -2882,8 +2882,6 @@ mod unit_tests { assert_eq!(orig, read); } } - - assert_eq!(qcow_file.first_zero_refcount().unwrap(), None); }); } From 248e786363a88b25520bf54a3cb71deb4aa71953 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 28 Nov 2025 14:15:00 +0100 Subject: [PATCH 0387/1893] tests: qcow: Adjust namings for zstd compresed backing file Signed-off-by: Anatol Belski --- scripts/run_integration_tests_aarch64.sh | 8 ++++---- scripts/run_integration_tests_x86_64.sh | 8 ++++---- tests/integration.rs | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index a21a282baf..35115461c1 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -82,11 +82,11 @@ update_workloads() { popd || exit fi - JAMMY_OS_QCOW2_IMAGE_BACKING_FILE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0-backing.qcow2" - JAMMY_OS_QCOW2_BACKING_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW2_IMAGE_BACKING_FILE_NAME" - if [ ! -f "$JAMMY_OS_QCOW2_BACKING_FILE_IMAGE" ]; then + JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0-backing-zstd.qcow2" + JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE_NAME" + if [ ! -f "$JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit - time qemu-img create -f qcow2 -b "$JAMMY_OS_QCOW2_ZSTD_FILE_IMAGE" -F qcow2 $JAMMY_OS_QCOW2_IMAGE_BACKING_FILE_NAME + time qemu-img create -f qcow2 -b "$JAMMY_OS_QCOW2_ZSTD_FILE_IMAGE" -F qcow2 $JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE_NAME popd || exit fi diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 68ab92ee2d..84cda3b01b 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -80,13 +80,13 @@ if [ ! -f "$JAMMY_OS_QCOW_ZSTD_FILE_IMAGE" ]; then popd || exit fi -JAMMY_OS_QCOW_BACKING_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-backing.qcow2" -JAMMY_OS_QCOW_BACKING_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_FILE_IMAGE_NAME" -if [ ! -f "$JAMMY_OS_QCOW_BACKING_FILE_IMAGE" ]; then +JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-backing-zstd.qcow2" +JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE" ]; then pushd "$WORKLOADS_DIR" || exit time qemu-img create -f qcow2 \ -b "$JAMMY_OS_QCOW_ZSTD_FILE_IMAGE" \ - -F qcow2 $JAMMY_OS_QCOW_BACKING_FILE_IMAGE_NAME + -F qcow2 $JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE_NAME popd || exit fi diff --git a/tests/integration.rs b/tests/integration.rs index 3d2edea217..3757d2a252 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -45,8 +45,8 @@ mod x86_64 { "jammy-server-cloudimg-amd64-custom-20241017-0-zlib.qcow2"; pub const JAMMY_IMAGE_NAME_QCOW2_ZSTD: &str = "jammy-server-cloudimg-amd64-custom-20241017-0-zstd.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_FILE: &str = - "jammy-server-cloudimg-amd64-custom-20241017-0-backing.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-backing-zstd.qcow2"; pub const WINDOWS_IMAGE_NAME: &str = "windows-server-2022-amd64-2.raw"; pub const OVMF_NAME: &str = "CLOUDHV.fd"; pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'IO-APIC.*ttyS0' /proc/interrupts || true"; @@ -68,8 +68,8 @@ mod aarch64 { "jammy-server-cloudimg-arm64-custom-20220329-0-zlib.qcow2"; pub const JAMMY_IMAGE_NAME_QCOW2_ZSTD: &str = "jammy-server-cloudimg-arm64-custom-20220329-0-zstd.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_FILE: &str = - "jammy-server-cloudimg-arm64-custom-20220329-0-backing.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-backing-zstd.qcow2"; pub const WINDOWS_IMAGE_NAME: &str = "windows-11-iot-enterprise-aarch64.raw"; pub const OVMF_NAME: &str = "CLOUDHV_EFI.fd"; pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'GICv3.*uart-pl011' /proc/interrupts || true"; @@ -3542,9 +3542,9 @@ mod common_parallel { } #[test] - fn test_virtio_block_qcow2_backing_file() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_FILE, false, false); - disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_BACKING_FILE); + fn test_virtio_block_qcow2_backing_zstd_file() { + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE, false, false); + disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE); } #[test] From e6dd429a646b080f2a729d2aaaf8ed44e822843b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 28 Nov 2025 14:26:30 +0100 Subject: [PATCH 0388/1893] tests: qcow: Add testing for uncompressed backing file Signed-off-by: Anatol Belski --- scripts/run_integration_tests_aarch64.sh | 10 ++++++++++ scripts/run_integration_tests_x86_64.sh | 10 ++++++++++ tests/integration.rs | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 35115461c1..c13d7b9ad5 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -90,6 +90,16 @@ update_workloads() { popd || exit fi + JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0-backing-uncompressed.qcow2" + JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE_NAME" + if [ ! -f "$JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img create -f qcow2 \ + -b "$JAMMY_OS_QCOW2_UNCOMPRESSED_IMAGE" \ + -F qcow2 $JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE_NAME + popd || exit + fi + ALPINE_MINIROOTFS_URL="http://dl-cdn.alpinelinux.org/alpine/v3.11/releases/aarch64/alpine-minirootfs-3.11.3-aarch64.tar.gz" ALPINE_MINIROOTFS_TARBALL="$WORKLOADS_DIR/alpine-minirootfs-aarch64.tar.gz" if [ ! -f "$ALPINE_MINIROOTFS_TARBALL" ]; then diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 84cda3b01b..32983672bb 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -90,6 +90,16 @@ if [ ! -f "$JAMMY_OS_QCOW_BACKING_ZSTD_FILE_IMAGE" ]; then popd || exit fi +JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-backing-uncompressed.qcow2" +JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img create -f qcow2 \ + -b "$JAMMY_OS_IMAGE" \ + -F qcow2 $JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE_NAME + popd || exit +fi + ALPINE_MINIROOTFS_URL="http://dl-cdn.alpinelinux.org/alpine/v3.11/releases/x86_64/alpine-minirootfs-3.11.3-x86_64.tar.gz" ALPINE_MINIROOTFS_TARBALL="$WORKLOADS_DIR/alpine-minirootfs-x86_64.tar.gz" if [ ! -f "$ALPINE_MINIROOTFS_TARBALL" ]; then diff --git a/tests/integration.rs b/tests/integration.rs index 3757d2a252..b0cc2ce88e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -47,6 +47,8 @@ mod x86_64 { "jammy-server-cloudimg-amd64-custom-20241017-0-zstd.qcow2"; pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE: &str = "jammy-server-cloudimg-amd64-custom-20241017-0-backing-zstd.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-backing-uncompressed.qcow2"; pub const WINDOWS_IMAGE_NAME: &str = "windows-server-2022-amd64-2.raw"; pub const OVMF_NAME: &str = "CLOUDHV.fd"; pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'IO-APIC.*ttyS0' /proc/interrupts || true"; @@ -70,6 +72,8 @@ mod aarch64 { "jammy-server-cloudimg-arm64-custom-20220329-0-zstd.qcow2"; pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE: &str = "jammy-server-cloudimg-arm64-custom-20220329-0-backing-zstd.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-backing-uncompressed.qcow2"; pub const WINDOWS_IMAGE_NAME: &str = "windows-11-iot-enterprise-aarch64.raw"; pub const OVMF_NAME: &str = "CLOUDHV_EFI.fd"; pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'GICv3.*uart-pl011' /proc/interrupts || true"; @@ -3547,6 +3551,12 @@ mod common_parallel { disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE); } + #[test] + fn test_virtio_block_qcow2_backing_uncompressed_file() { + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE, false, false); + disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE); + } + #[test] fn test_virtio_block_vhd() { let mut workload_path = dirs::home_dir().unwrap(); From f56adb8a5ac4cf451b5ea56261b671479e8be155 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 28 Nov 2025 17:36:33 +0100 Subject: [PATCH 0389/1893] tests: Fix path handling for `qemu-img check` The image passed for the guest construction is copied. Previously, check-img has been checking the unchanged image from the workspace dir, which is supposed to be error free. Signed-off-by: Anatol Belski --- tests/integration.rs | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index b0cc2ce88e..942a02da72 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3396,7 +3396,12 @@ mod common_parallel { handle_child_output(r, &output); } - fn _test_virtio_block(image_name: &str, disable_io_uring: bool, disable_aio: bool) { + fn _test_virtio_block( + image_name: &str, + disable_io_uring: bool, + disable_aio: bool, + verify_os_disk: bool, + ) { let disk_config = UbuntuDiskConfig::new(image_name.to_string()); let guest = Guest::new(Box::new(disk_config)); @@ -3479,21 +3484,25 @@ mod common_parallel { let output = cloud_child.wait_with_output().unwrap(); handle_child_output(r, &output); + + if verify_os_disk { + disk_check_consistency(guest.disk_config.disk(DiskType::OperatingSystem).unwrap()); + } } #[test] fn test_virtio_block_io_uring() { - _test_virtio_block(FOCAL_IMAGE_NAME, false, true); + _test_virtio_block(FOCAL_IMAGE_NAME, false, true, false); } #[test] fn test_virtio_block_aio() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, false); + _test_virtio_block(FOCAL_IMAGE_NAME, true, false, false); } #[test] fn test_virtio_block_sync() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, true); + _test_virtio_block(FOCAL_IMAGE_NAME, true, true, false); } /// Uses `qemu-img check` to verify disk image consistency. @@ -3529,32 +3538,32 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2, false, false); - disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2, false, false, true); } #[test] fn test_virtio_block_qcow2_zlib() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZLIB, false, false); - disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_ZLIB); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZLIB, false, false, true); } #[test] fn test_virtio_block_qcow2_zstd() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZSTD, false, false); - disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_ZSTD); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZSTD, false, false, true); } #[test] fn test_virtio_block_qcow2_backing_zstd_file() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE, false, false); - disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE, false, false, true); } #[test] fn test_virtio_block_qcow2_backing_uncompressed_file() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE, false, false); - disk_check_consistency(JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE); + _test_virtio_block( + JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE, + false, + false, + true, + ); } #[test] @@ -3579,7 +3588,7 @@ mod common_parallel { .output() .expect("Expect generating VHD image from RAW image"); - _test_virtio_block(FOCAL_IMAGE_NAME_VHD, false, false); + _test_virtio_block(FOCAL_IMAGE_NAME_VHD, false, false, false); } #[test] @@ -3603,8 +3612,7 @@ mod common_parallel { .output() .expect("Expect generating dynamic VHDx image from RAW image"); - _test_virtio_block(FOCAL_IMAGE_NAME_VHDX, false, false); - disk_check_consistency(FOCAL_IMAGE_NAME_VHDX); + _test_virtio_block(FOCAL_IMAGE_NAME_VHDX, false, false, true); } #[test] From c940f6642f4d4fce61108f4c8a8221ec61ffdd63 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 2 Dec 2025 15:19:53 +0100 Subject: [PATCH 0390/1893] block: qcow: Refactor refcount update into helper method Add set_cluster_refcount_track_freed() helper to consolidate the common pattern of setting a cluster refcount and tracking freed refblocks. This reduces code duplication and improves readability. Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 4540d0e7e3..f19a52ce90 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1235,8 +1235,7 @@ impl QcowFile { .get_cluster_refcount(&mut self.raw_file, addr) .map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?; if refcount > 0 { - let mut newly_unref = self.set_cluster_refcount(addr, refcount - 1)?; - self.unref_clusters.append(&mut newly_unref); + self.set_cluster_refcount_track_freed(addr, refcount - 1)?; } addr += self.raw_file.cluster_size(); } @@ -1262,8 +1261,7 @@ impl QcowFile { }; for (addr, count) in set_refcounts { - let mut newly_unref = self.set_cluster_refcount(addr, count)?; - self.unref_clusters.append(&mut newly_unref); + self.set_cluster_refcount_track_freed(addr, count)?; } Ok(cluster_addr + self.raw_file.cluster_offset(address)) @@ -1331,8 +1329,7 @@ impl QcowFile { fn append_data_cluster(&mut self, initial_data: Option>) -> std::io::Result { let new_addr: u64 = self.get_new_cluster(initial_data)?; // The cluster refcount starts at one indicating it is used but doesn't need COW. - let mut newly_unref = self.set_cluster_refcount(new_addr, 1)?; - self.unref_clusters.append(&mut newly_unref); + self.set_cluster_refcount_track_freed(new_addr, 1)?; Ok(new_addr) } @@ -1437,8 +1434,7 @@ impl QcowFile { } let new_refcount = refcount - 1; - let mut newly_unref = self.set_cluster_refcount(cluster_addr, new_refcount)?; - self.unref_clusters.append(&mut newly_unref); + self.set_cluster_refcount_track_freed(cluster_addr, new_refcount)?; // Rewrite the L2 entry to remove the cluster mapping. // unwrap is safe as we just checked/inserted this entry. @@ -1523,6 +1519,17 @@ impl QcowFile { Ok(new_cluster) } + // Set the refcount for a cluster and add any unreferenced clusters to the unref list. + fn set_cluster_refcount_track_freed( + &mut self, + address: u64, + refcount: u16, + ) -> std::io::Result<()> { + let mut newly_unref = self.set_cluster_refcount(address, refcount)?; + self.unref_clusters.append(&mut newly_unref); + Ok(()) + } + // Set the refcount for a cluster with the given address. // Returns a list of any refblocks that can be reused, this happens when a refblock is moved, // the old location can be reused. From f795081bf9badb4d0ca6b74e65d35c8836331a18 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 4 Dec 2025 18:35:43 -0800 Subject: [PATCH 0391/1893] hypervisor: mshv: retrieve MSRs list correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MSR enumeration logic in MSHV does not accurately account for the capabilities of the underlying hardware and hypervisor. Previously, integration tests passed because only the default MSRs—universally supported across platforms—were involved. However, recent feature additions in MSHV have introduced MSRs that are not supported on Intel hardware, causing failures. This patch retrieves the MSRs from the VM specific API call that filters out the unsupported MSRs. Signed-off-by: Muminul Islam --- hypervisor/src/mshv/mod.rs | 53 +++++++++++++++++--------------------- hypervisor/src/vm.rs | 6 +++++ 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 90173d011f..40d8796e9a 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -10,6 +10,10 @@ use std::num::NonZeroUsize; use std::sync::{Arc, RwLock}; use anyhow::anyhow; +#[cfg(target_arch = "x86_64")] +use arc_swap::ArcSwap; +#[cfg(feature = "sev_snp")] +use log::info; use log::{debug, warn}; use mshv_bindings::*; #[cfg(target_arch = "x86_64")] @@ -22,8 +26,6 @@ use vfio_ioctls::VfioDeviceFd; use vm::DataMatch; #[cfg(feature = "sev_snp")] use vm_memory::bitmap::AtomicBitmap; -#[cfg(feature = "sev_snp")] -use {arc_swap::ArcSwap, log::info}; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::regs::{ @@ -219,18 +221,6 @@ pub struct MshvHypervisor { mshv: Mshv, } -impl MshvHypervisor { - #[cfg(target_arch = "x86_64")] - /// - /// Retrieve the list of MSRs supported by MSHV. - /// - fn get_msr_list(&self) -> hypervisor::Result> { - self.mshv - .get_msr_index_list() - .map_err(|e| hypervisor::HypervisorError::GetMsrList(e.into())) - } -} - impl MshvHypervisor { /// Create a hypervisor based on Mshv #[allow(clippy::new_ret_no_self)] @@ -348,20 +338,9 @@ impl hypervisor::Hypervisor for MshvHypervisor { #[cfg(target_arch = "x86_64")] { - let msr_list = self.get_msr_list()?; - let mut msrs: Vec = vec![ - MsrEntry { - ..Default::default() - }; - msr_list.len() - ]; - for (pos, index) in msr_list.iter().enumerate() { - msrs[pos].index = *index; - } - Ok(Arc::new(MshvVm { fd: vm_fd, - msrs, + msrs: ArcSwap::new(Vec::::new().into()), dirty_log_slots: Arc::new(RwLock::new(HashMap::new())), #[cfg(feature = "sev_snp")] sev_snp_enabled: mshv_vm_type == VmType::Snp, @@ -1713,7 +1692,7 @@ impl MshvVcpu { pub struct MshvVm { fd: Arc, #[cfg(target_arch = "x86_64")] - msrs: Vec, + msrs: ArcSwap>, dirty_log_slots: Arc>>, #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, @@ -1845,7 +1824,7 @@ impl vm::Vm for MshvVm { #[cfg(target_arch = "x86_64")] cpuid: Vec::new(), #[cfg(target_arch = "x86_64")] - msrs: self.msrs.clone(), + msrs: self.msrs.load().as_ref().clone(), vm_ops, vm_fd: self.fd.clone(), #[cfg(feature = "sev_snp")] @@ -2454,7 +2433,23 @@ impl vm::Vm for MshvVm { 1u64, ) .map_err(|e| vm::HypervisorVmError::InitializeVm(e.into()))?; - + #[cfg(target_arch = "x86_64")] + { + let msr_list = self + .fd + .get_msr_index_list() + .map_err(|e| vm::HypervisorVmError::GetMsrList(e.into()))?; + let mut msrs: Vec = vec![ + MsrEntry { + ..Default::default() + }; + msr_list.len() + ]; + for (pos, index) in msr_list.iter().enumerate() { + msrs[pos].index = *index; + } + self.msrs.store(Arc::new(msrs)); + } Ok(()) } } diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 5b56923b19..9d7e60a8be 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -257,6 +257,12 @@ pub enum HypervisorVmError { /// #[error("Failed to initialize VM")] InitializeVm(#[source] anyhow::Error), + #[cfg(all(target_arch = "x86_64", feature = "mshv"))] + /// + /// Failed to retrieve list of MSRs. + /// + #[error("Failed to get the list of supported MSRs")] + GetMsrList(#[source] anyhow::Error), } /// /// Result type for returning from a function From 7c8372452b44512e92228984ff8127830587a9e8 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 4 Dec 2025 19:00:04 -0800 Subject: [PATCH 0392/1893] tests: disable live-upgrade tests for MSHV These tests are expected to fail. See: #7542 Signed-off-by: Muminul Islam --- tests/integration.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 942a02da72..613109997f 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -11310,21 +11310,25 @@ mod live_migration { } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_upgrade_basic() { _test_live_migration(true, false); } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_upgrade_local() { _test_live_migration(true, true); } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_upgrade_watchdog() { _test_live_migration_watchdog(true, false); } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_upgrade_watchdog_local() { _test_live_migration_watchdog(true, true); } @@ -11351,31 +11355,37 @@ mod live_migration { } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_upgrade_balloon() { _test_live_migration_balloon(true, false); } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_upgrade_balloon_local() { _test_live_migration_balloon(true, true); } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_migration_numa() { _test_live_migration_numa(false, false); } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_migration_numa_local() { _test_live_migration_numa(false, true); } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_upgrade_numa() { _test_live_migration_numa(true, false); } #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7542")] fn test_live_upgrade_numa_local() { _test_live_migration_numa(true, true); } From f0dfa2a5ef67fc8e81c5bc98d9a413f951b895eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:38:01 +0000 Subject: [PATCH 0393/1893] build: Bump the non-rust-vmm group across 2 directories with 16 updates Bumps the non-rust-vmm group with 12 updates in the / directory: | Package | From | To | | --- | --- | --- | | [serde_with](https://github.com/jonasbb/serde_with) | `3.16.0` | `3.16.1` | | [flume](https://github.com/zesterer/flume) | `0.11.1` | `0.12.0` | | [libc](https://github.com/rust-lang/libc) | `0.2.177` | `0.2.178` | | [log](https://github.com/rust-lang/log) | `0.4.28` | `0.4.29` | | [uuid](https://github.com/uuid-rs/uuid) | `1.18.1` | `1.19.0` | | [zerocopy](https://github.com/google/zerocopy) | `0.8.30` | `0.8.31` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.47` | `1.2.49` | | [endi](https://github.com/zeenix/endi) | `1.1.0` | `1.1.1` | | [simd-adler32](https://github.com/mcountryman/simd-adler32) | `0.3.7` | `0.3.8` | | [toml_edit](https://github.com/toml-rs/toml) | `0.23.7` | `0.23.9` | | [tracing](https://github.com/tokio-rs/tracing) | `0.1.41` | `0.1.43` | | [winnow](https://github.com/winnow-rs/winnow) | `0.7.13` | `0.7.14` | 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.16.0` | `3.16.1` | | [flume](https://github.com/zesterer/flume) | `0.11.1` | `0.12.0` | | [libc](https://github.com/rust-lang/libc) | `0.2.177` | `0.2.178` | | [log](https://github.com/rust-lang/log) | `0.4.28` | `0.4.29` | | [uuid](https://github.com/uuid-rs/uuid) | `1.18.1` | `1.19.0` | | [zerocopy](https://github.com/google/zerocopy) | `0.8.30` | `0.8.31` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.47` | `1.2.49` | | [simd-adler32](https://github.com/mcountryman/simd-adler32) | `0.3.7` | `0.3.8` | | [toml_edit](https://github.com/toml-rs/toml) | `0.23.7` | `0.23.9` | | [winnow](https://github.com/winnow-rs/winnow) | `0.7.13` | `0.7.14` | Updates `serde_with` from 3.16.0 to 3.16.1 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.16.0...v3.16.1) Updates `flume` from 0.11.1 to 0.12.0 - [Changelog](https://github.com/zesterer/flume/blob/master/CHANGELOG.md) - [Commits](https://github.com/zesterer/flume/commits) Updates `libc` from 0.2.177 to 0.2.178 - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.178/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.177...0.2.178) Updates `log` from 0.4.28 to 0.4.29 - [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.28...0.4.29) Updates `uuid` from 1.18.1 to 1.19.0 - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/v1.18.1...v1.19.0) Updates `zerocopy` from 0.8.30 to 0.8.31 - [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.30...v0.8.31) Updates `cc` from 1.2.47 to 1.2.49 - [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.47...cc-v1.2.49) Updates `endi` from 1.1.0 to 1.1.1 - [Release notes](https://github.com/zeenix/endi/releases) - [Commits](https://github.com/zeenix/endi/compare/1.1.0...1.1.1) Updates `serde_with_macros` from 3.16.0 to 3.16.1 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.16.0...v3.16.1) Updates `simd-adler32` from 0.3.7 to 0.3.8 - [Changelog](https://github.com/mcountryman/simd-adler32/blob/main/CHANGELOG.md) - [Commits](https://github.com/mcountryman/simd-adler32/commits) Updates `toml_edit` from 0.23.7 to 0.23.9 - [Commits](https://github.com/toml-rs/toml/compare/v0.23.7...v0.23.9) Updates `tracing` from 0.1.41 to 0.1.43 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.41...tracing-0.1.43) Updates `tracing-attributes` from 0.1.30 to 0.1.31 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-attributes-0.1.30...tracing-attributes-0.1.31) Updates `tracing-core` from 0.1.34 to 0.1.35 - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-core-0.1.34...tracing-core-0.1.35) Updates `winnow` from 0.7.13 to 0.7.14 - [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md) - [Commits](https://github.com/winnow-rs/winnow/compare/v0.7.13...v0.7.14) Updates `zerocopy-derive` from 0.8.30 to 0.8.31 - [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.30...v0.8.31) Updates `serde_with` from 3.16.0 to 3.16.1 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.16.0...v3.16.1) Updates `flume` from 0.11.1 to 0.12.0 - [Changelog](https://github.com/zesterer/flume/blob/master/CHANGELOG.md) - [Commits](https://github.com/zesterer/flume/commits) Updates `libc` from 0.2.177 to 0.2.178 - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.178/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.177...0.2.178) Updates `log` from 0.4.28 to 0.4.29 - [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.28...0.4.29) Updates `uuid` from 1.18.1 to 1.19.0 - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/v1.18.1...v1.19.0) Updates `zerocopy` from 0.8.30 to 0.8.31 - [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.30...v0.8.31) Updates `cc` from 1.2.47 to 1.2.49 - [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.47...cc-v1.2.49) Updates `serde_with_macros` from 3.16.0 to 3.16.1 - [Release notes](https://github.com/jonasbb/serde_with/releases) - [Commits](https://github.com/jonasbb/serde_with/compare/v3.16.0...v3.16.1) Updates `simd-adler32` from 0.3.7 to 0.3.8 - [Changelog](https://github.com/mcountryman/simd-adler32/blob/main/CHANGELOG.md) - [Commits](https://github.com/mcountryman/simd-adler32/commits) Updates `toml_edit` from 0.23.7 to 0.23.9 - [Commits](https://github.com/toml-rs/toml/compare/v0.23.7...v0.23.9) Updates `winnow` from 0.7.13 to 0.7.14 - [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md) - [Commits](https://github.com/winnow-rs/winnow/compare/v0.7.13...v0.7.14) Updates `zerocopy-derive` from 0.8.30 to 0.8.31 - [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.30...v0.8.31) --- updated-dependencies: - dependency-name: serde_with dependency-version: 3.16.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: flume dependency-version: 0.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: libc dependency-version: 0.2.178 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: log dependency-version: 0.4.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: uuid dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: zerocopy dependency-version: 0.8.31 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.49 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: endi dependency-version: 1.1.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with_macros dependency-version: 3.16.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: simd-adler32 dependency-version: 0.3.8 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: toml_edit dependency-version: 0.23.9 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: tracing dependency-version: 0.1.43 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: tracing-attributes dependency-version: 0.1.31 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: tracing-core dependency-version: 0.1.35 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: winnow dependency-version: 0.7.14 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: zerocopy-derive dependency-version: 0.8.31 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with dependency-version: 3.16.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: flume dependency-version: 0.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: libc dependency-version: 0.2.178 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: log dependency-version: 0.4.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: uuid dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-rust-vmm - dependency-name: zerocopy dependency-version: 0.8.31 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: cc dependency-version: 1.2.49 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: serde_with_macros dependency-version: 3.16.1 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: simd-adler32 dependency-version: 0.3.8 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: toml_edit dependency-version: 0.23.9 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: winnow dependency-version: 0.7.14 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm - dependency-name: zerocopy-derive dependency-version: 0.8.31 dependency-type: indirect update-type: version-update:semver-patch dependency-group: non-rust-vmm ... Signed-off-by: dependabot[bot] --- Cargo.lock | 80 ++++++++++++++++------------------- Cargo.toml | 12 +++--- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 102 ++++++++++++++++++++++----------------------- fuzz/Cargo.toml | 2 +- 5 files changed, 95 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5310e31b29..4919fa77dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,9 +365,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.47" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "jobserver", @@ -609,9 +609,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "endi" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] name = "enumflags2" @@ -720,6 +720,9 @@ name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +dependencies = [ + "getrandom 0.2.15", +] [[package]] name = "fdt" @@ -745,13 +748,13 @@ dependencies = [ [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ + "fastrand", "futures-core", "futures-sink", - "nanorand", "spin", ] @@ -1153,9 +1156,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" @@ -1219,9 +1222,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "managed" @@ -1295,15 +1298,6 @@ dependencies = [ "vmm-sys-util", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom 0.2.15", -] - [[package]] name = "net_gen" version = "0.1.0" @@ -1942,9 +1936,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "serde_core", "serde_with_macros", @@ -1952,9 +1946,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling", "proc-macro2", @@ -1993,9 +1987,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "slab" @@ -2147,9 +2141,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" dependencies = [ "indexmap", "toml_datetime", @@ -2190,9 +2184,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2201,9 +2195,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -2212,9 +2206,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", ] @@ -2244,14 +2238,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "getrandom 0.3.4", "js-sys", "rand", - "serde", + "serde_core", "wasm-bindgen", ] @@ -2746,9 +2740,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -2822,18 +2816,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index aa256aceb5..2e25dec144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -135,7 +135,7 @@ igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates serde = "1.0.228" serde_json = "1.0.145" -serde_with = { version = "3.16.0", default-features = false } +serde_with = { version = "3.16.1", default-features = false } # other crates anyhow = "1.0.99" @@ -147,15 +147,15 @@ dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" epoll = "4.4.0" -flume = "0.11.1" +flume = "0.12.0" itertools = "0.14.0" -libc = "0.2.177" -log = "0.4.28" +libc = "0.2.178" +log = "0.4.29" signal-hook = "0.3.18" thiserror = "2.0.17" -uuid = { version = "1.18.1" } +uuid = { version = "1.19.0" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.30", default-features = false } +zerocopy = { version = "0.8.31", default-features = false } [workspace.lints.clippy] # Any clippy lint (group) in alphabetical order: diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 882824fd2c..28fcbb1ad7 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.30", features = [ +zerocopy = { version = "0.8.31", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index f613bf9df6..63aa99b8e4 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -168,9 +168,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.47" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "jobserver", @@ -385,10 +385,20 @@ version = "0.1.0" dependencies = [ "flume", "libc", + "log", "serde", "serde_json", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "fdt" version = "0.1.5" @@ -413,13 +423,13 @@ dependencies = [ [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ + "fastrand", "futures-core", "futures-sink", - "nanorand", "spin", ] @@ -474,20 +484,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] [[package]] @@ -575,7 +585,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] @@ -631,9 +641,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libfuzzer-sys" @@ -665,9 +675,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "managed" @@ -714,15 +724,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom 0.2.16", -] - [[package]] name = "net_gen" version = "0.1.0" @@ -735,7 +736,7 @@ name = "net_util" version = "0.1.0" dependencies = [ "epoll", - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", "log", "net_gen", @@ -920,7 +921,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -1017,9 +1018,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "serde_core", "serde_with_macros", @@ -1027,9 +1028,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling", "proc-macro2", @@ -1068,9 +1069,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "smallvec" @@ -1155,9 +1156,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" dependencies = [ "indexmap", "toml_datetime", @@ -1210,11 +1211,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", "rand", "wasm-bindgen", @@ -1452,12 +1453,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -1557,36 +1558,33 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.10.0", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zerocopy" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 7caeddbbd6..6cbcfa9050 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.177" +libc = "0.2.178" libfuzzer-sys = "0.4.10" linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } From 562af123d5eb080772f3c0273708a11472bc5ad5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 8 Dec 2025 20:01:32 +0100 Subject: [PATCH 0394/1893] block: qcow: Add missing flush in write_refcount_block The BufWriter must be flushed explicitly to handle errors properly. Without explicit flush, errors during the implicit drop flush are ignored. This is the same issue fixed for write_pointer_table in commit 85556951a. Signed-off-by: Anatol Belski --- block/src/qcow/qcow_raw_file.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index 1d9dbc0b52..18a1c771ec 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -102,6 +102,7 @@ impl QcowRawFile { for count in table { buffer.write_u16::(*count)?; } + buffer.flush()?; Ok(()) } From 265d21b0c45d1d1fc536adabc43bb67e651b9338 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 27 Nov 2025 16:24:56 +0100 Subject: [PATCH 0395/1893] misc: improve test idempotence (unrelated improvement) Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- virtio-devices/src/vsock/unix/muxer.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index ee2264476f..edce5b1e03 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -871,6 +871,7 @@ impl VsockMuxer { #[cfg(test)] mod unit_tests { use std::cmp::min; + use std::fs; use std::io::Write; use std::path::{Path, PathBuf}; @@ -921,6 +922,8 @@ mod unit_tests { ) .unwrap(); let uds_path = format!("test_vsock_{name}.sock"); + // Clear in case it is still there from a previous run + let _ = fs::remove_file(&uds_path); let muxer = VsockMuxer::new(PEER_CID, uds_path).unwrap(); Self { @@ -1049,6 +1052,9 @@ mod unit_tests { } impl LocalListener { fn new + Clone>(path: P) -> Self { + // Clear in case it is still there from a previous run + let _ = fs::remove_file(path.as_ref()); + let path_buf = path.as_ref().to_path_buf(); let sock = UnixListener::bind(path).unwrap(); sock.set_nonblocking(true).unwrap(); From f104bcdb6bdb2ad92cf5895ffa54b82552c7d742 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 27 Nov 2025 16:45:17 +0100 Subject: [PATCH 0396/1893] build: remove useless build scripts (copy & paste artifact) This will also prevent some useless rebuilds. Using `--verbose` we can observe that the build.rs causes frequent useless rebuilds - having less is a good thing. They come from the dependency of `build.rs` to the local git repository. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- performance-metrics/Cargo.toml | 1 - vhost_user_block/Cargo.toml | 1 - vhost_user_net/Cargo.toml | 1 - 3 files changed, 3 deletions(-) diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index 531c6abe99..472f1159b3 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -1,6 +1,5 @@ [package] authors = ["The Cloud Hypervisor Authors"] -build = "../build.rs" edition.workspace = true name = "performance-metrics" version = "0.1.0" diff --git a/vhost_user_block/Cargo.toml b/vhost_user_block/Cargo.toml index e674c96eac..051210cb43 100644 --- a/vhost_user_block/Cargo.toml +++ b/vhost_user_block/Cargo.toml @@ -1,6 +1,5 @@ [package] authors = ["The Cloud Hypervisor Authors"] -build = "../build.rs" edition.workspace = true name = "vhost_user_block" version = "0.1.0" diff --git a/vhost_user_net/Cargo.toml b/vhost_user_net/Cargo.toml index 849ad5426e..2edf00db95 100644 --- a/vhost_user_net/Cargo.toml +++ b/vhost_user_net/Cargo.toml @@ -1,6 +1,5 @@ [package] authors = ["The Cloud Hypervisor Authors"] -build = "../build.rs" edition.workspace = true name = "vhost_user_net" version = "0.1.0" From 3d049765bddb2433e068bc6ca8131a501740f3bd Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 27 Nov 2025 19:38:11 +0100 Subject: [PATCH 0397/1893] build: decouple Cargo.toml: split crate and workspace definitions TL;DR: cargo clippy|check|... now runs on whole workspace by default. ## Steps - add new workspace member `cloud-hypervisor` - move `./src` to new workspace member - move `./tests` to new workspace member - move relevant parts from Cargo.toml to new workspace member - kept necessary parts in main Cargo.toml, such as profile configurations ## About The main Cargo.toml historically mixes workspace and crate definitions for cloud-hypervisor and ch-remote. This makes it hard to read and requires `--workspace` to run cargo clippy or cargo test on all workspace members, which is counter-intuitive. This patch separates the workspace from the crate definition in the main Cargo.toml file. After this, cargo clippy, cargo test, etc., work on the whole workspace naturally, giving a smoother developer experience. The Cargo.toml without a package definition is also called a virtual workspace or virtual manifest by Cargo [0]. Backporting is not a concern: CHV no longer backports, but the affected files are rarely modified anyway. [0] https://doc.rust-lang.org/cargo/reference/workspaces.html#virtual-workspace Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- Cargo.toml | 74 ++----------------- cloud-hypervisor/Cargo.toml | 69 +++++++++++++++++ build.rs => cloud-hypervisor/build.rs | 0 .../src}/bin/ch-remote.rs | 0 {src => cloud-hypervisor/src}/lib.rs | 0 {src => cloud-hypervisor/src}/main.rs | 0 {src => cloud-hypervisor/src}/test_util.rs | 0 .../tests}/integration.rs | 0 tests/readme.md | 1 + 9 files changed, 75 insertions(+), 69 deletions(-) create mode 100644 cloud-hypervisor/Cargo.toml rename build.rs => cloud-hypervisor/build.rs (100%) rename {src => cloud-hypervisor/src}/bin/ch-remote.rs (100%) rename {src => cloud-hypervisor/src}/lib.rs (100%) rename {src => cloud-hypervisor/src}/main.rs (100%) rename {src => cloud-hypervisor/src}/test_util.rs (100%) rename {tests => cloud-hypervisor/tests}/integration.rs (100%) create mode 100644 tests/readme.md diff --git a/Cargo.toml b/Cargo.toml index 2e25dec144..b738dde5bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,6 @@ -[package] -authors = ["The Cloud Hypervisor Authors"] -build = "build.rs" -default-run = "cloud-hypervisor" -description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV" -edition = "2024" -homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" -license = "Apache-2.0 AND BSD-3-Clause" -name = "cloud-hypervisor" -version = "49.0.0" -# 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. -rust-version = "1.89.0" +# Cloud Hypervisor Workspace +# +# The main crate producing the binaries is in `./cloud-hypervisor`. [profile.release] codegen-units = 1 @@ -28,62 +13,12 @@ debug = true inherits = "release" strip = false -[dependencies] -anyhow = { workspace = true } -api_client = { path = "api_client" } -clap = { workspace = true, features = ["string"] } -dhat = { workspace = true, optional = true } -env_logger = { workspace = true } -epoll = { workspace = true } -event_monitor = { path = "event_monitor" } -hypervisor = { path = "hypervisor" } -libc = { workspace = true } -log = { workspace = true, features = ["std"] } -option_parser = { path = "option_parser" } -seccompiler = { workspace = true } -serde_json = { workspace = true } -signal-hook = { workspace = true } -thiserror = { workspace = true } -tpm = { path = "tpm" } -tracer = { path = "tracer" } -vm-memory = { workspace = true } -vmm = { path = "vmm" } -vmm-sys-util = { workspace = true } -zbus = { version = "5.7.1", optional = true } - -[dev-dependencies] -dirs = { workspace = true } -net_util = { path = "net_util" } -serde_json = { workspace = true } -test_infra = { path = "test_infra" } -wait-timeout = { workspace = true } - -# Please adjust `vmm::feature_list()` accordingly when changing the -# feature list below -[features] -dbus_api = ["vmm/dbus_api", "zbus"] -default = ["io_uring", "kvm"] -dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling -fw_cfg = ["vmm/fw_cfg"] -guest_debug = ["vmm/guest_debug"] -igvm = ["mshv", "vmm/igvm"] -io_uring = ["vmm/io_uring"] -ivshmem = ["vmm/ivshmem"] -kvm = ["vmm/kvm"] -mshv = ["vmm/mshv"] -pvmemcontrol = ["vmm/pvmemcontrol"] -sev_snp = ["igvm", "mshv", "vmm/sev_snp"] -tdx = ["vmm/tdx"] -tracing = ["tracer/tracing", "vmm/tracing"] - -[lints] -workspace = true - [workspace] members = [ "api_client", "arch", "block", + "cloud-hypervisor", "devices", "event_monitor", "hypervisor", @@ -106,6 +41,7 @@ members = [ "vmm", ] package.edition = "2024" +resolver = "3" [workspace.dependencies] # rust-vmm crates diff --git a/cloud-hypervisor/Cargo.toml b/cloud-hypervisor/Cargo.toml new file mode 100644 index 0000000000..579bae8a27 --- /dev/null +++ b/cloud-hypervisor/Cargo.toml @@ -0,0 +1,69 @@ +[package] +authors = ["The Cloud Hypervisor Authors"] +build = "build.rs" +default-run = "cloud-hypervisor" +description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV" +edition = "2024" +homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" +license = "Apache-2.0 AND BSD-3-Clause" +name = "cloud-hypervisor" +version = "49.0.0" +# 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. +rust-version = "1.89.0" + +[dependencies] +anyhow = { workspace = true } +api_client = { path = "../api_client" } +clap = { workspace = true, features = ["string"] } +dhat = { workspace = true, optional = true } +env_logger = { workspace = true } +epoll = { workspace = true } +event_monitor = { path = "../event_monitor" } +hypervisor = { path = "../hypervisor" } +libc = { workspace = true } +log = { workspace = true, features = ["std"] } +option_parser = { path = "../option_parser" } +seccompiler = { workspace = true } +serde_json = { workspace = true } +signal-hook = { workspace = true } +thiserror = { workspace = true } +tpm = { path = "../tpm" } +tracer = { path = "../tracer" } +vm-memory = { workspace = true } +vmm = { path = "../vmm" } +vmm-sys-util = { workspace = true } +zbus = { version = "5.7.1", optional = true } + +[dev-dependencies] +dirs = { workspace = true } +net_util = { path = "../net_util" } +serde_json = { workspace = true } +test_infra = { path = "../test_infra" } +wait-timeout = { workspace = true } + +# Please adjust `vmm::feature_list()` accordingly when changing the +# feature list below +[features] +dbus_api = ["vmm/dbus_api", "zbus"] +default = ["io_uring", "kvm"] +dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling +fw_cfg = ["vmm/fw_cfg"] +guest_debug = ["vmm/guest_debug"] +igvm = ["mshv", "vmm/igvm"] +io_uring = ["vmm/io_uring"] +ivshmem = ["vmm/ivshmem"] +kvm = ["vmm/kvm"] +mshv = ["vmm/mshv"] +pvmemcontrol = ["vmm/pvmemcontrol"] +sev_snp = ["igvm", "mshv", "vmm/sev_snp"] +tdx = ["vmm/tdx"] +tracing = ["tracer/tracing", "vmm/tracing"] + +[lints] +workspace = true diff --git a/build.rs b/cloud-hypervisor/build.rs similarity index 100% rename from build.rs rename to cloud-hypervisor/build.rs diff --git a/src/bin/ch-remote.rs b/cloud-hypervisor/src/bin/ch-remote.rs similarity index 100% rename from src/bin/ch-remote.rs rename to cloud-hypervisor/src/bin/ch-remote.rs diff --git a/src/lib.rs b/cloud-hypervisor/src/lib.rs similarity index 100% rename from src/lib.rs rename to cloud-hypervisor/src/lib.rs diff --git a/src/main.rs b/cloud-hypervisor/src/main.rs similarity index 100% rename from src/main.rs rename to cloud-hypervisor/src/main.rs diff --git a/src/test_util.rs b/cloud-hypervisor/src/test_util.rs similarity index 100% rename from src/test_util.rs rename to cloud-hypervisor/src/test_util.rs diff --git a/tests/integration.rs b/cloud-hypervisor/tests/integration.rs similarity index 100% rename from tests/integration.rs rename to cloud-hypervisor/tests/integration.rs diff --git a/tests/readme.md b/tests/readme.md new file mode 100644 index 0000000000..9bff6a994a --- /dev/null +++ b/tests/readme.md @@ -0,0 +1 @@ +The integration tests have been moved to `./cloud-hypervisor/tests`. From 70146ee7afb2a27d6ed462381900e7a5224b0021 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 28 Nov 2025 08:32:32 +0100 Subject: [PATCH 0398/1893] test_infra: Adapt to workspace + make code more robust Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- test_infra/src/lib.rs | 58 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 96cfe3659b..2cd8527c7d 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -12,7 +12,7 @@ use std::io::{Read, Seek, SeekFrom, Write}; use std::net::{TcpListener, TcpStream}; use std::os::unix::fs::PermissionsExt; use std::os::unix::io::{AsRawFd, FromRawFd}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{Child, Command, ExitStatus, Output, Stdio}; use std::str::FromStr; use std::time::Duration; @@ -249,6 +249,39 @@ impl Drop for WindowsDiskConfig { } } +/// Returns the workspace root directory. +/// +/// As we don't have packages in the workspace root, +/// we walk up until we found the main Cargo.toml file. +fn workspace_root() -> PathBuf { + // The directory of the current crate (integration test). + let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + // Currently we have one level of nesting and we probably never change it. + let max_levels = 2; + + eprintln!( + "Looking for workspace root: starting with dir={}", + dir.to_str().unwrap() + ); + + // walk up + for _ in 0..max_levels { + dir = dir.parent().unwrap().to_path_buf(); + eprintln!("Checking parent dir: {}", dir.to_str().unwrap()); + let maybe_manifest_file = dir.join("Cargo.toml"); + if maybe_manifest_file.exists() { + let content = fs::read_to_string(&maybe_manifest_file).unwrap(); + if content.contains("[workspace]") && content.contains("Cloud Hypervisor Workspace") { + eprintln!("INFO: Found workspace root: {}", dir.to_str().unwrap()); + return dir; + } + } + } + + panic!("Could not find workspace root"); +} + impl DiskConfig for UbuntuDiskConfig { fn prepare_cloudinit(&self, tmp_dir: &TempDir, network: &GuestNetworkConfig) -> String { let cloudinit_file_path = @@ -259,15 +292,16 @@ impl DiskConfig for UbuntuDiskConfig { fs::create_dir_all(&cloud_init_directory) .expect("Expect creating cloud-init directory to succeed"); - let source_file_dir = std::env::current_dir() - .unwrap() + let source_file_dir = workspace_root() .join("test_data") .join("cloud-init") .join("ubuntu") .join("ci"); ["meta-data"].iter().for_each(|x| { - rate_limited_copy(source_file_dir.join(x), cloud_init_directory.join(x)) + let source_file = source_file_dir.join(x); + let cloud_init = cloud_init_directory.join(x); + rate_limited_copy(source_file, cloud_init) .expect("Expect copying cloud-init meta-data to succeed"); }); @@ -1429,11 +1463,19 @@ impl<'a> GuestCommand<'a> { } } +/// Returns the absolute path into the workspaces target directory to locate the desired +/// executable. +/// +/// # Arguments +/// - `cmd`: workspace binary, e.g. `ch-remote` or `cloud-hypervisor` pub fn clh_command(cmd: &str) -> String { - env::var("BUILD_TARGET").map_or( - format!("target/x86_64-unknown-linux-gnu/release/{cmd}"), - |target| format!("target/{target}/release/{cmd}"), - ) + let workspace_root = workspace_root(); + let rustc_target = env::var("BUILD_TARGET").unwrap_or("x86_64-unknown-linux-gnu".to_string()); + let target_artifact_dir = format!("target/{rustc_target}/release"); + let target_cmd_path = format!("{target_artifact_dir}/{cmd}"); + + let full_path = workspace_root.join(&target_cmd_path); + String::from(full_path.to_str().unwrap()) } pub fn parse_iperf3_output(output: &[u8], sender: bool, bandwidth: bool) -> Result { From 9979e562b29878057fab19e0775455908d989ef3 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 4 Dec 2025 13:53:15 +0100 Subject: [PATCH 0399/1893] docs: adjust contributing.md: fix missing --all Unrelated but necessary to also always format code for all architectures and all features. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db1b4bfed4..c907d29ee5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,7 +48,7 @@ have specific features that you regularly develop against. ```sh #!/bin/sh -cargo +nightly fmt -- --check || exit 1 +cargo +nightly fmt --all -- --check || exit 1 cargo check --locked --all --all-targets --tests || exit 1 cargo clippy --locked --all --all-targets --tests -- -D warnings || exit 1 ``` From 3191ff26e14abbfba41a06fd710e5201a8935b7e Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 4 Dec 2025 13:54:09 +0100 Subject: [PATCH 0400/1893] docs: adjust contributing.md: remove need for --all (--workspace) It is no longer needed to add `--all` (which is an alias for `--workspace`). The documentation says "Commands run in the workspace root will run against all workspace members by default" [0]. We however still need `--tests` as this activates the building of tests in `/tests` directories. [0] https://doc.rust-lang.org/cargo/reference/workspaces.html#virtual-workspace Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- CONTRIBUTING.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c907d29ee5..157098201c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,11 +21,11 @@ convention and enforce it through the Continuous Integration (CI) process callin ```sh # We currently rely on nightly-only formatting features -cargo +nightly fmt --all -cargo check --all --all-targets --tests -cargo clippy --all --all-targets --tests +cargo +nightly fmt --all +cargo check --all-targets --tests +cargo clippy --all-targets --tests # Please note that this will not execute integration tests. -cargo test --all --all-targets --tests +cargo test --all-targets --tests # To lint your last three commits gitlint --commits "HEAD~3..HEAD" @@ -49,8 +49,8 @@ have specific features that you regularly develop against. #!/bin/sh cargo +nightly fmt --all -- --check || exit 1 -cargo check --locked --all --all-targets --tests || exit 1 -cargo clippy --locked --all --all-targets --tests -- -D warnings || exit 1 +cargo check --locked --all-targets --tests || exit 1 +cargo clippy --locked --all-targets --tests -- -D warnings || exit 1 ``` You will need to `chmod +x .git/hooks/pre-commit` to have it run on every From 0be7d1bd826fd8668cf38f1a5f15f79fd0a1581b Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 27 Nov 2025 19:56:38 +0100 Subject: [PATCH 0401/1893] ci: replace `cargo rustc` -> `cargo build` to support virtual manifests `cargo rustc` is incompatible with virtual manifests, so the CI needs to use cargo build instead. However, passing `RUSTFLAGS="-D warnings"` via the environment would propagate to all dependencies, and some of them currently fail to build under ``-D warnings` due to issues like [0]: ``` error: creating a mutable reference to mutable static --> src/temp.rs:97:5 | 97 | DIRS.pop() | ^^^^^^^^^^ mutable reference to mutable static ``` To resolve this, apply ``-D warnings` only to the `cargo clippy` commands (which apply to our workspace only) and avoid enforcing it for the entire cargo build. [0]: https://github.com/cloud-hypervisor/cloud-hypervisor/actions/runs/19962283528/job/57245376263?pr=7525 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- .github/workflows/build.yaml | 24 +++++++++---------- .github/workflows/preview-riscv64-build.yaml | 4 ++-- .../workflows/preview-riscv64-modules.yaml | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c7147fc3f4..8528d54299 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -35,40 +35,40 @@ jobs: target: ${{ matrix.target }} - name: Build (default features) - run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings + run: cargo build --locked --bin cloud-hypervisor - name: Build (kvm) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm" - name: Build (default features + tdx) - run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --features "tdx" - name: Build (default features + dbus_api) - run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --features "dbus_api" - name: Build (default features + guest_debug) - run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --features "guest_debug" - name: Build (default features + pvmemcontrol) - run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --features "pvmemcontrol" - name: Build (default features + fw_cfg) - run: cargo rustc --locked --bin cloud-hypervisor --features "fw_cfg" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --features "fw_cfg" - name: Build (default features + ivshmem) - run: cargo rustc --locked --bin cloud-hypervisor --features "ivshmem" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --features "ivshmem" - name: Build (mshv) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv" - name: Build (sev_snp) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" - name: Build (igvm) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings + run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "igvm" - name: Build (mshv + kvm) - run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings + 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 }} diff --git a/.github/workflows/preview-riscv64-build.yaml b/.github/workflows/preview-riscv64-build.yaml index baed1741d6..e5fbf5ee27 100644 --- a/.github/workflows/preview-riscv64-build.yaml +++ b/.github/workflows/preview-riscv64-build.yaml @@ -21,10 +21,10 @@ jobs: run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0 - name: Build test (kvm) - run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked --no-default-features --features "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" + 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)" diff --git a/.github/workflows/preview-riscv64-modules.yaml b/.github/workflows/preview-riscv64-modules.yaml index ffb4fd4b67..1ae1c63758 100644 --- a/.github/workflows/preview-riscv64-modules.yaml +++ b/.github/workflows/preview-riscv64-modules.yaml @@ -27,7 +27,7 @@ jobs: 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 rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings + 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 From 8de24b5f08edcaa74e463e97ab96175f7cb293f6 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Tue, 9 Dec 2025 14:41:14 +0100 Subject: [PATCH 0402/1893] vmm: Fix clippy lints This PR fixes some clippy lints in the vmm crate. Signed-off-by: Oliver Anderson On-behalf-of: SAP oliver.anderson@sap.com --- vmm/src/lib.rs | 33 +++++++++++++++------------------ vmm/src/memory_manager.rs | 6 ++++-- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 0298efc06c..f34585a685 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1615,26 +1615,23 @@ impl RequestHandler for Vmm { fn vm_create(&mut self, config: Box) -> result::Result<(), VmError> { // We only store the passed VM config. // The VM will be created when being asked to boot it. - if self.vm_config.is_none() { - self.vm_config = Some(Arc::new(Mutex::new(*config))); - self.console_info = - Some(pre_create_console_devices(self).map_err(VmError::CreateConsoleDevices)?); + if self.vm_config.is_some() { + return Err(VmError::VmAlreadyCreated); + } - if self - .vm_config - .as_ref() - .unwrap() - .lock() - .unwrap() - .landlock_enable - { - apply_landlock(self.vm_config.as_ref().unwrap().as_ref()) - .map_err(VmError::ApplyLandlock)?; - } - Ok(()) - } else { - Err(VmError::VmAlreadyCreated) + self.vm_config = Some(Arc::new(Mutex::new(*config))); + self.console_info = + Some(pre_create_console_devices(self).map_err(VmError::CreateConsoleDevices)?); + + if self + .vm_config + .as_ref() + .is_some_and(|config| config.lock().unwrap().landlock_enable) + { + apply_landlock(self.vm_config.as_ref().unwrap().as_ref()) + .map_err(VmError::ApplyLandlock)?; } + Ok(()) } fn vm_boot(&mut self) -> result::Result<(), VmError> { diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 1954c17116..8b0cdff6c1 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -408,8 +408,10 @@ fn memory_zone_get_align_size(zone: &MemoryZoneConfig) -> Result { } // The `hugepages` is enabled and the `hugepage_size` is specified, just use it directly. - if zone.hugepages && zone.hugepage_size.is_some() { - return Ok(zone.hugepage_size.unwrap()); + if let Some(hugepage_size) = zone.hugepage_size + && zone.hugepages + { + return Ok(hugepage_size); } // There are two scenarios here: From 41a8dcd9bae0b83d48ca2fb456a05483f0ceed05 Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Sat, 6 Dec 2025 12:55:26 -0600 Subject: [PATCH 0403/1893] block: allow VIRTIO_BLK_T_GET_ID for read-only devices https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7294 adjusted the checks for read-only requests made to virtio-blk devices and started rejecting VIRTIO_BLK_T_GET_ID requests. These requests do not perform any writes and are needed in order to access device serials from within the guest. Signed-off-by: Connor Brewster --- block/src/lib.rs | 2 +- virtio-devices/src/block.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 1996c9ba6f..212eab6703 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -136,7 +136,7 @@ pub enum ExecuteError { Read(#[source] GuestMemoryError), #[error("Failed to read_exact")] ReadExact(#[source] io::Error), - #[error("Can't execute an operation other than `read` on a read-only device")] + #[error("Can't execute an operation other than `read` or `get_id` on a read-only device")] ReadOnly, #[error("Failed to seek")] Seek(#[source] io::Error), diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 70ec6f9de8..c358cdf746 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -158,7 +158,9 @@ fn has_feature(features: u64, feature_flag: u64) -> bool { impl BlockEpollHandler { fn check_request(features: u64, request_type: RequestType) -> result::Result<(), ExecuteError> { - if has_feature(features, VIRTIO_BLK_F_RO.into()) && request_type != RequestType::In { + if has_feature(features, VIRTIO_BLK_F_RO.into()) + && !(request_type == RequestType::In || request_type == RequestType::GetDeviceId) + { // For virtio spec compliance // "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request // if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data." From 4592f37bcf86d2df6defba60a39c8adad86ba00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20N=C3=BCrnberger?= Date: Tue, 9 Dec 2025 19:58:02 +0100 Subject: [PATCH 0404/1893] tests: integration: expect some ksm merged pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our CI test execution system finds some mergeable pages in the system directly on boot since a recent update. Remove the broken assumption in the test_memory_mergeable integration test and only check if the guest VMs influence the shared pages count. Signed-off-by: Stefan Nürnberger --- cloud-hypervisor/tests/integration.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 613109997f..022c9e4efa 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1955,9 +1955,8 @@ fn test_memory_mergeable(mergeable: bool) { "mergeable=off" }; - // We are assuming the rest of the system in our CI is not using mergeable memory + // We assume the number of shared pages in the rest of the system to be constant let ksm_ps_init = get_ksm_pages_shared(); - assert!(ksm_ps_init == 0); let disk_config1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest1 = Guest::new(Box::new(disk_config1)); @@ -2010,8 +2009,8 @@ fn test_memory_mergeable(mergeable: bool) { // We are expecting the number of shared pages to increase as the number of VM increases assert!(ksm_ps_guest1 < ksm_ps_guest2); } else { - assert!(ksm_ps_guest1 == 0); - assert!(ksm_ps_guest2 == 0); + assert!(ksm_ps_guest1 == ksm_ps_init); + assert!(ksm_ps_guest2 == ksm_ps_init); } }); From 6bda6541be7b3052da6d74d2dd6908ca7e6326d9 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 10 Dec 2025 06:48:50 +0100 Subject: [PATCH 0405/1893] vmm: cleanup &Mutex parameters In [0] we refactored some Arc> parameters to &Mutex> to satisfy clippy's needless_pass_by_value lint. Nevertheless, this is also not so idiomatic, so as a follow-up, we put the responsibility to lock objects to the caller side (only where this is not strictly needed by the callee). While on it, I also tried to pass vm_config directly into pre_create_console_devices() which would clean up some code, but then we have interleaving mutable and immutable borrows of the Vmm, which are denied by the borrow checker. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/console_devices.rs | 20 ++++++++++---------- vmm/src/cpu.rs | 7 +++---- vmm/src/lib.rs | 15 ++++++++------- vmm/src/vm.rs | 7 +++---- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/vmm/src/console_devices.rs b/vmm/src/console_devices.rs index 70bcabae5d..76655d6c16 100644 --- a/vmm/src/console_devices.rs +++ b/vmm/src/console_devices.rs @@ -16,7 +16,7 @@ use std::os::fd::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::net::UnixListener; use std::path::PathBuf; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::{io, result}; use libc::{TCSANOW, cfmakeraw, isatty, tcgetattr, tcsetattr, termios}; @@ -76,7 +76,7 @@ pub struct ConsoleInfo { fn modify_mode( fd: RawFd, f: F, - original_termios_opt: &Mutex>, + original_termios_opt: &mut Option, ) -> vmm_sys_util::errno::Result<()> { // SAFETY: safe because we check the return value of isatty. if unsafe { isatty(fd) } != 1 { @@ -91,7 +91,6 @@ fn modify_mode( if ret < 0 { return vmm_sys_util::errno::errno_result(); } - let mut original_termios_opt = original_termios_opt.lock().unwrap(); if original_termios_opt.is_none() { original_termios_opt.replace(termios); } @@ -109,7 +108,7 @@ fn modify_mode( fn set_raw_mode( f: &dyn AsRawFd, - original_termios_opt: &Mutex>, + original_termios_opt: &mut Option, ) -> ConsoleDeviceResult<()> { modify_mode( f.as_raw_fd(), @@ -179,6 +178,7 @@ fn dup_stdout() -> vmm_sys_util::errno::Result { pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { let vm_config = vmm.vm_config.as_mut().unwrap().clone(); let mut vmconfig = vm_config.lock().unwrap(); + let mut original_termios_opt = vmm.original_termios_opt.lock().unwrap(); let console_info = ConsoleInfo { console_main_fd: match vmconfig.console.mode { @@ -190,7 +190,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { let (main_fd, sub_fd, path) = create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?; - set_raw_mode(&sub_fd.as_raw_fd(), &vmm.original_termios_opt)?; + set_raw_mode(&sub_fd.as_raw_fd(), &mut original_termios_opt)?; vmconfig.console.file = Some(path.clone()); vmm.console_resize_pipe = Some(Arc::new( listen_for_sigwinch_on_tty( @@ -221,7 +221,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { @@ -239,7 +239,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { let (main_fd, sub_fd, path) = create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?; - set_raw_mode(&sub_fd.as_raw_fd(), &vmm.original_termios_opt)?; + set_raw_mode(&sub_fd.as_raw_fd(), &mut original_termios_opt)?; vmconfig.serial.file = Some(path.clone()); ConsoleOutput::Pty(Arc::new(main_fd)) } @@ -255,7 +255,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult ConsoleDeviceResult { let (main_fd, sub_fd, path) = create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?; - set_raw_mode(&sub_fd.as_raw_fd(), &vmm.original_termios_opt)?; + set_raw_mode(&sub_fd.as_raw_fd(), &mut original_termios_opt)?; vmconfig.debug_console.file = Some(path.clone()); ConsoleOutput::Pty(Arc::new(main_fd)) } ConsoleOutputMode::Tty => { let out = dup_stdout().map_err(|e| ConsoleDeviceError::CreateConsoleDevice(e.into()))?; - set_raw_mode(&out, &vmm.original_termios_opt)?; + set_raw_mode(&out, &mut original_termios_opt)?; ConsoleOutput::Tty(Arc::new(out)) } ConsoleOutputMode::Socket => { diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index e7c8a699fd..ea030fb0cf 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -922,11 +922,9 @@ impl CpuManager { pub fn configure_vcpu( &self, - vcpu: &Mutex, + vcpu: &mut Vcpu, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, ) -> Result<()> { - let mut vcpu = vcpu.lock().unwrap(); - #[cfg(feature = "sev_snp")] if self.sev_snp_enabled { if let Some((kernel_entry_point, _)) = boot_setup { @@ -1406,7 +1404,8 @@ impl CpuManager { cmp::Ordering::Greater => { let vcpus = self.create_vcpus(desired_vcpus, None)?; for vcpu in vcpus { - self.configure_vcpu(&vcpu, None)?; + let mut vcpu = vcpu.lock().unwrap(); + self.configure_vcpu(&mut vcpu, None)?; } self.activate_vcpus(desired_vcpus, true, None)?; Ok(true) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index f34585a685..cb98fd1e0a 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1014,7 +1014,8 @@ impl Vmm { .unwrap() .landlock_enable { - apply_landlock(self.vm_config.as_ref().unwrap().as_ref()).map_err(|e| { + let mut config = self.vm_config.as_ref().unwrap().lock().unwrap(); + apply_landlock(&mut config).map_err(|e| { MigratableError::MigrateReceive(anyhow!("Error applying landlock: {e:?}")) })?; } @@ -1486,8 +1487,8 @@ impl Vmm { .unwrap() .landlock_enable { - apply_landlock(self.vm_config.as_ref().unwrap().as_ref()) - .map_err(VmError::ApplyLandlock)?; + let mut config = self.vm_config.as_ref().unwrap().lock().unwrap(); + apply_landlock(&mut config).map_err(VmError::ApplyLandlock)?; } // Now we can restore the rest of the VM. @@ -1606,8 +1607,8 @@ impl Vmm { } } -fn apply_landlock(vm_config: &Mutex) -> result::Result<(), LandlockError> { - vm_config.lock().unwrap().apply_landlock()?; +fn apply_landlock(vm_config: &mut VmConfig) -> result::Result<(), LandlockError> { + vm_config.apply_landlock()?; Ok(()) } @@ -1628,8 +1629,8 @@ impl RequestHandler for Vmm { .as_ref() .is_some_and(|config| config.lock().unwrap().landlock_enable) { - apply_landlock(self.vm_config.as_ref().unwrap().as_ref()) - .map_err(VmError::ApplyLandlock)?; + let mut config = self.vm_config.as_ref().unwrap().lock().unwrap(); + apply_landlock(&mut config).map_err(VmError::ApplyLandlock)?; } Ok(()) } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 79dca313c8..721c490b8e 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2390,16 +2390,15 @@ impl Vm { for vcpu in vcpus { let guest_memory = &self.memory_manager.lock().as_ref().unwrap().guest_memory(); let boot_setup = entry_point.map(|e| (e, guest_memory)); + let mut vcpu = vcpu.lock().unwrap(); self.cpu_manager .lock() .unwrap() - .configure_vcpu(&vcpu, boot_setup) + .configure_vcpu(&mut vcpu, boot_setup) .map_err(Error::CpuManager)?; #[cfg(target_arch = "aarch64")] - vcpu.lock() - .unwrap() - .set_gic_redistributor_addr(redist_addr[2], redist_addr[3]) + vcpu.set_gic_redistributor_addr(redist_addr[2], redist_addr[3]) .map_err(Error::CpuManager)?; } From f57b9b2359b866e9b54696b318c3ae276291dbae Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 4 Dec 2025 11:48:37 +0100 Subject: [PATCH 0406/1893] tests: Re-enable test_vdpa_net Creating a vdpa_net with an associated MAC address and setting the right amount of queues in order to fix the integration test related to vdpa_net. Fixes: #5756 Signed-off-by: Sebastien Boeuf --- cloud-hypervisor/tests/integration.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 022c9e4efa..04638f79d8 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7258,7 +7258,6 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] - #[ignore = "See #5756"] fn test_vdpa_net() { // Before trying to run the test, verify the vdpa_sim_net module is correctly loaded. if !exec_host_command_status("lsmod | grep vdpa_sim_net").success() { @@ -7277,7 +7276,7 @@ mod common_parallel { .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() .default_net() - .args(["--vdpa", "path=/dev/vhost-vdpa-2,num_queues=2"]) + .args(["--vdpa", "path=/dev/vhost-vdpa-2,num_queues=3"]) .capture_output() .spawn() .unwrap(); @@ -7296,6 +7295,9 @@ mod common_parallel { 1 ); + guest + .ssh_command("sudo ip link set dev ens6 address 00:e8:ca:33:ba:06") + .unwrap(); guest .ssh_command("sudo ip addr add 172.16.1.2/24 dev ens6") .unwrap(); From 9dc923f3797c18112813ce691d228919badc8837 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 7 Dec 2025 20:13:31 +0100 Subject: [PATCH 0407/1893] seccomp: Allow fcntl in virtio-block thread Add fcntl to virtio_block_thread_rules to allow try_clone() on file descriptors. The try_clone() method uses fcntl(fd, F_DUPFD_CLOEXEC) to duplicate file descriptors, which is needed for efficient QCOW2 L1 table sync that avoids temporary allocations. Signed-off-by: Anatol Belski --- virtio-devices/src/seccomp_filters.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs index 9c8cc3e7de..07601b68a8 100644 --- a/virtio-devices/src/seccomp_filters.rs +++ b/virtio-devices/src/seccomp_filters.rs @@ -99,6 +99,7 @@ fn virtio_balloon_thread_rules() -> Vec<(i64, Vec)> { fn virtio_block_thread_rules() -> Vec<(i64, Vec)> { vec![ (libc::SYS_fallocate, vec![]), + (libc::SYS_fcntl, vec![]), (libc::SYS_fdatasync, vec![]), (libc::SYS_fsync, vec![]), (libc::SYS_ftruncate, vec![]), From be5b14ef3affe9de5b0ad798146e31632c7e0b6f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 8 Dec 2025 15:48:02 +0100 Subject: [PATCH 0408/1893] block: qcow: Implement Deref for VecCache Add Deref implementation for VecCache to allow direct slice operations without explicitly calling get_values(). This enables cleaner code patterns like cache.iter() instead of cache.get_values().iter(). Signed-off-by: Anatol Belski --- block/src/qcow/vec_cache.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/block/src/qcow/vec_cache.rs b/block/src/qcow/vec_cache.rs index d0c3acf086..0423bb6a53 100644 --- a/block/src/qcow/vec_cache.rs +++ b/block/src/qcow/vec_cache.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use std::collections::hash_map::IterMut; use std::io; -use std::ops::{Index, IndexMut}; +use std::ops::{Deref, Index, IndexMut}; use std::slice::SliceIndex; /// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so @@ -85,6 +85,14 @@ impl IndexMut for VecCache { } } +impl Deref for VecCache { + type Target = [T]; + + fn deref(&self) -> &[T] { + &self.vec + } +} + #[derive(Clone, Debug)] pub struct CacheMap { capacity: usize, From 5c5f33050cb38e941636aaf891d1ce24ec21ad4b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 10 Dec 2025 10:06:07 +0100 Subject: [PATCH 0409/1893] block: qcow: Refactor pointer table writes to use iterators Refactor write_pointer_table to accept iterators instead of requiring materialized vectors, eliminating temporary allocations in L1 table sync operations. Changes: - Modified write_pointer_table() to take Iterator and dereference internally before passing owned values to the callback - Added write_pointer_table_direct() convenience wrapper for cases without value transformation - Updated sync_caches() to use l1_table.iter() directly instead of .get_values().iter().copied() - Implemented Deref for VecCache to enable direct .iter() Performance impact: - Eliminates L1 table allocation during sync (~2KB per 100GB disk) - L2 and refcount table writes already used slices, no change there - Zero performance overhead: iterator dereferencing is equivalent to .copied() and optimizes identically The L1 sync previously collected entries into a Vec to apply the OFLAG_COPIED flag. The new iterator+callback pattern computes this on-the-fly, avoiding the allocation entirely. Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 31 +++++++++---------- block/src/qcow/qcow_raw_file.rs | 53 +++++++++++++++++++++++---------- block/src/qcow/refcount.rs | 7 ++--- 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index f19a52ce90..4600ec1188 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -773,7 +773,7 @@ impl QcowFile { let raw_file = &mut self.raw_file; self.l2_cache .insert(l1_index, table, |index, evicted| { - raw_file.write_pointer_table(l1_table[index], evicted.get_values(), 0) + raw_file.write_pointer_table_direct(l1_table[index], evicted.iter()) }) .map_err(Error::EvictingCache)?; } @@ -998,7 +998,7 @@ impl QcowFile { // Rewrite the top-level refcount table. raw_file - .write_pointer_table(header.refcount_table_offset, ref_table, 0) + .write_pointer_table_direct(header.refcount_table_offset, ref_table.iter()) .map_err(Error::WritingHeader)?; // Rewrite the header again, now with lazy refcounts disabled. @@ -1513,7 +1513,7 @@ impl QcowFile { let l1_table = &self.l1_table; let raw_file = &mut self.raw_file; self.l2_cache.insert(l1_index, l2_table, |index, evicted| { - raw_file.write_pointer_table(l1_table[index], evicted.get_values(), 0) + raw_file.write_pointer_table_direct(l1_table[index], evicted.iter()) })?; } Ok(new_cluster) @@ -1596,7 +1596,7 @@ impl QcowFile { let addr = self.l1_table[*l1_index]; if addr != 0 { self.raw_file - .write_pointer_table(addr, l2_table.get_values(), 0)?; + .write_pointer_table_direct(addr, l2_table.iter())?; } else { return Err(std::io::Error::from_raw_os_error(EINVAL)); } @@ -1610,25 +1610,22 @@ impl QcowFile { // Push L1 table and refcount table last as all the clusters they point to are now // guaranteed to be valid. let mut sync_required = if self.l1_table.dirty() { - // Build L1 table with OFLAG_COPIED bits set correctly based on L2 cluster refcounts - let l1_active: Vec = self - .l1_table - .get_values() - .iter() - .map(|&l2_addr| { + // Write L1 table with OFLAG_COPIED bits + let refcounts = &mut self.refcounts; + self.raw_file.write_pointer_table( + self.header.l1_table_offset, + self.l1_table.iter(), + |raw_file, l2_addr| { if l2_addr == 0 { Ok(0) } else { - let refcount = self - .refcounts - .get_cluster_refcount(&mut self.raw_file, l2_addr) + let refcount = refcounts + .get_cluster_refcount(raw_file, l2_addr) .map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?; Ok(l1_entry_make(l2_addr, refcount == 1)) } - }) - .collect::>>()?; - self.raw_file - .write_pointer_table(self.header.l1_table_offset, &l1_active, 0)?; + }, + )?; self.l1_table.mark_clean(); true } else { diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index 18a1c771ec..1b869b4596 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -61,24 +61,47 @@ impl QcowRawFile { self.read_pointer_table(offset, count, mask) } - /// Writes `table` of u64 pointers to `offset` in the file. - /// `non_zero_flags` will be ORed with all non-zero values in `table`. - /// writing. - pub fn write_pointer_table( + /// Internal helper for creating a buffered writer for pointer tables. + #[inline] + fn setup_pointer_table_writer( &mut self, offset: u64, - table: &[u64], - non_zero_flags: u64, - ) -> io::Result<()> { + entries: &impl Iterator, + ) -> io::Result> { self.file.seek(SeekFrom::Start(offset))?; - let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file); - for addr in table { - let val = if *addr == 0 { - 0 - } else { - *addr | non_zero_flags - }; - buffer.write_u64::(val)?; + let my_file = self.file.try_clone()?; + let capacity = entries.size_hint().0 * size_of::(); + Ok(BufWriter::with_capacity(capacity, my_file)) + } + + /// Writes a pointer table to `offset` in the file. + /// Entries are computed on-the-fly by the callback. + pub fn write_pointer_table<'a, T: Copy + 'a>( + &mut self, + offset: u64, + entries: impl Iterator, + mut f: impl FnMut(&mut QcowRawFile, T) -> io::Result, + ) -> io::Result<()> { + let mut buffer = self.setup_pointer_table_writer(offset, &entries)?; + + for addr in entries { + let entry = f(self, *addr)?; + buffer.write_u64::(entry)?; + } + buffer.flush()?; + Ok(()) + } + + /// Writes a pointer table directly without transforming values. + pub fn write_pointer_table_direct<'a>( + &mut self, + offset: u64, + entries: impl Iterator, + ) -> io::Result<()> { + let mut buffer = self.setup_pointer_table_writer(offset, &entries)?; + + for &entry in entries { + buffer.write_u64::(entry)?; } buffer.flush()?; Ok(()) diff --git a/block/src/qcow/refcount.rs b/block/src/qcow/refcount.rs index 0ec1ab5f4f..7ef33c1b32 100644 --- a/block/src/qcow/refcount.rs +++ b/block/src/qcow/refcount.rs @@ -156,11 +156,8 @@ impl RefCount { /// Returns true if the table changed since the previous `flush_table()` call. pub fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result { if self.ref_table.dirty() { - raw_file.write_pointer_table( - self.refcount_table_offset, - self.ref_table.get_values(), - 0, - )?; + raw_file + .write_pointer_table_direct(self.refcount_table_offset, self.ref_table.iter())?; self.ref_table.mark_clean(); Ok(true) } else { From 4b4954ff86d737dc6b876b937f89eca64dee46e8 Mon Sep 17 00:00:00 2001 From: Songqian Li Date: Wed, 3 Dec 2025 17:35:10 +0800 Subject: [PATCH 0410/1893] hypervisor: AMX state snapshot and restore support The TILE data state of AMX may require 8KB+ space, calling the legacy KVM_GET_XSAVE will encounter an error since KVM_GET_XSAVE only can get 4KB space. This patch adds KVM_GET_XSAVE2 support to allow snapping more data. Fixes: #7533 Signed-off-by: Songqian Li --- hypervisor/src/arch/x86/mod.rs | 11 +++- hypervisor/src/kvm/mod.rs | 86 ++++++++++++++++++++++++++++++-- hypervisor/src/kvm/x86_64/mod.rs | 67 ++++++++++++++++++++++--- 3 files changed, 150 insertions(+), 14 deletions(-) diff --git a/hypervisor/src/arch/x86/mod.rs b/hypervisor/src/arch/x86/mod.rs index b1b2957f62..78e4d7cc5d 100644 --- a/hypervisor/src/arch/x86/mod.rs +++ b/hypervisor/src/arch/x86/mod.rs @@ -315,12 +315,19 @@ pub struct MsrEntry { pub struct XsaveState { #[serde_as(as = "[_; 1024usize]")] pub region: [u32; 1024usize], + + // extra data to support xsave2 + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub extra: Vec, } impl Default for XsaveState { fn default() -> Self { - // SAFETY: this is plain old data structure - unsafe { ::std::mem::zeroed() } + Self { + // SAFETY: this is plain old data structure + region: unsafe { std::mem::zeroed() }, + extra: Vec::new(), + } } } diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 0398f01365..259009151e 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -107,11 +107,13 @@ use kvm_bindings::{ use kvm_bindings::{KVM_REG_RISCV_CORE, kvm_riscv_core}; #[cfg(feature = "tdx")] use kvm_bindings::{KVM_X86_DEFAULT_VM, KVM_X86_SW_PROTECTED_VM, KVMIO, kvm_run__bindgen_ty_1}; +#[cfg(target_arch = "x86_64")] +use kvm_bindings::{Xsave as xsave2, kvm_xsave2}; pub use kvm_ioctls::{Cap, Kvm, VcpuExit}; use thiserror::Error; use vfio_ioctls::VfioDeviceFd; #[cfg(target_arch = "x86_64")] -use vmm_sys_util::ioctl_io_nr; +use vmm_sys_util::{fam::FamStruct, ioctl_io_nr}; #[cfg(feature = "tdx")] use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_iowr_nr}; pub use {kvm_bindings, kvm_ioctls}; @@ -120,6 +122,9 @@ pub use {kvm_bindings, kvm_ioctls}; use crate::RegList; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::regs; +#[cfg(target_arch = "x86_64")] +use crate::kvm::x86_64::XsaveStateError; + #[cfg(target_arch = "x86_64")] ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a); @@ -564,6 +569,17 @@ impl vm::Vm for KvmVm { .fd .create_vcpu(id as u64) .map_err(|e| vm::HypervisorVmError::CreateVcpu(e.into()))?; + + #[cfg(target_arch = "x86_64")] + // Safety: `xsave_size` will not change after vcpu creation because: + // 1. `xsave_size` depends on cpuid + // 2. The only factor that affects cpuid is xsave permission, obtained via + // `ARCH_GET_XCOMP_GUEST_PERM` + // 3. This permission is already acquired before vcpu creation + // Therefore, cpuid remains unchanged after vcpu creation, and so does `xsave_size`. + // + // First vCPU allocation locks the permissions of `ARCH_GET_XCOMP_GUEST_PERM`. + let xsave_size = self.fd.check_extension_int(Cap::Xsave2); let vcpu = KvmVcpu { fd, #[cfg(target_arch = "x86_64")] @@ -571,6 +587,8 @@ impl vm::Vm for KvmVm { vm_ops, #[cfg(target_arch = "x86_64")] hyperv_synic: AtomicBool::new(false), + #[cfg(target_arch = "x86_64")] + xsave_size, }; Ok(Box::new(vcpu)) } @@ -1332,6 +1350,8 @@ pub struct KvmVcpu { vm_ops: Option>, #[cfg(target_arch = "x86_64")] hyperv_synic: AtomicBool, + #[cfg(target_arch = "x86_64")] + xsave_size: i32, } /// Implementation of Vcpu trait for KVM @@ -2329,7 +2349,11 @@ impl cpu::Vcpu for KvmVcpu { let mp_state = self.get_mp_state()?.into(); let regs = self.get_regs()?; let sregs = self.get_sregs()?; - let xsave = self.get_xsave()?; + let xsave = if self.xsave_size > 0 { + self.get_xsave2()? + } else { + self.get_xsave()? + }; let xcrs = self.get_xcrs()?; let lapic_state = self.get_lapic()?; let fpu = self.get_fpu()?; @@ -2566,7 +2590,11 @@ impl cpu::Vcpu for KvmVcpu { self.set_mp_state(state.mp_state.into())?; self.set_regs(&state.regs.into())?; self.set_sregs(&state.sregs.into())?; - self.set_xsave(&state.xsave)?; + if self.xsave_size > 0 { + self.set_xsave2(&state.xsave)?; + } else { + self.set_xsave(&state.xsave)?; + } self.set_xcrs(&state.xcrs)?; self.set_lapic(&state.lapic_state)?; self.set_fpu(&state.fpu)?; @@ -2856,7 +2884,10 @@ impl KvmVcpu { /// X86 specific call that sets the vcpu's current "xsave struct". /// fn set_xsave(&self, xsave: &XsaveState) -> cpu::Result<()> { - let xsave: kvm_bindings::kvm_xsave = (*xsave).clone().into(); + let xsave: kvm_bindings::kvm_xsave = (*xsave) + .clone() + .try_into() + .map_err(|e: XsaveStateError| cpu::HypervisorCpuError::GetXsaveState(e.into()))?; // SAFETY: Here we trust the kernel not to read past the end of the kvm_xsave struct // when calling the kvm-ioctl library function. unsafe { @@ -2866,6 +2897,53 @@ impl KvmVcpu { } } + #[cfg(target_arch = "x86_64")] + /// X86 specific call that returns the vcpu's current "xsave struct" using the extended + /// xsave2 interface which supports larger state buffers (>4KB) for features like Intel AMX. + /// + /// This method requires KVM_CAP_XSAVE2 capability and uses KVM_GET_XSAVE2 ioctl. + /// The xsave parameter must be allocated with sufficient size based on the value + /// returned by KVM_CHECK_EXTENSION(KVM_CAP_XSAVE2). + pub fn get_xsave2(&self) -> cpu::Result { + assert!( + self.xsave_size > 0, + "'xsave_size' must be initialized via 'KVM_CAP_XSAVE2' first" + ); + let fam_size = (self.xsave_size as usize - size_of::()) + .div_ceil(size_of::<::Entry>()); + let mut xsave = + xsave2::new(fam_size).map_err(|e| cpu::HypervisorCpuError::GetXsaveState(e.into()))?; + // SAFETY: The caller guarantees that xsave is allocated with enough space + unsafe { + self.fd + .get_xsave2(&mut xsave) + .map_err(|e| cpu::HypervisorCpuError::GetXsaveState(e.into()))?; + } + Ok((&xsave).into()) + } + + #[cfg(target_arch = "x86_64")] + /// X86 specific call that sets the vcpu's current "xsave struct" using the extended + /// xsave2 interface which supports larger state buffers (>4KB) for features like Intel AMX. + /// + /// This method uses KVM_SET_XSAVE ioctl but with extended buffer support when + /// KVM_CAP_XSAVE2 is available. + pub fn set_xsave2(&self, xsave_state: &XsaveState) -> cpu::Result<()> { + assert!( + self.xsave_size > 0, + "'xsave_size' must be initialized via 'KVM_CAP_XSAVE2' first" + ); + let xsave = xsave_state + .to_xsave2() + .map_err(|e| cpu::HypervisorCpuError::SetXsaveState(e.into()))?; + // SAFETY: The caller guarantees that xsave contains valid data + unsafe { + self.fd + .set_xsave2(&xsave) + .map_err(|e| cpu::HypervisorCpuError::SetXsaveState(e.into())) + } + } + #[cfg(target_arch = "x86_64")] /// /// X86 specific call that returns the vcpu's current "xcrs". diff --git a/hypervisor/src/kvm/x86_64/mod.rs b/hypervisor/src/kvm/x86_64/mod.rs index 39db4a9940..e338346c3f 100644 --- a/hypervisor/src/kvm/x86_64/mod.rs +++ b/hypervisor/src/kvm/x86_64/mod.rs @@ -8,14 +8,17 @@ // // +use log::error; use serde::{Deserialize, Serialize}; +use thiserror::Error; /// /// Export generically-named wrappers of kvm-bindings for Unix-based platforms /// pub use { kvm_bindings::CpuId, kvm_bindings::KVM_CPUID_FLAG_SIGNIFCANT_INDEX, kvm_bindings::MsrList, - kvm_bindings::Msrs as MsrEntries, kvm_bindings::kvm_cpuid_entry2, kvm_bindings::kvm_dtable, - kvm_bindings::kvm_fpu, kvm_bindings::kvm_lapic_state, kvm_bindings::kvm_mp_state as MpState, + kvm_bindings::Msrs as MsrEntries, kvm_bindings::Xsave as xsave2, + kvm_bindings::kvm_cpuid_entry2, kvm_bindings::kvm_dtable, kvm_bindings::kvm_fpu, + kvm_bindings::kvm_lapic_state, kvm_bindings::kvm_mp_state as MpState, kvm_bindings::kvm_msr_entry, kvm_bindings::kvm_regs, kvm_bindings::kvm_segment, kvm_bindings::kvm_sregs, kvm_bindings::kvm_vcpu_events as VcpuEvents, kvm_bindings::kvm_xcrs as ExtendedControlRegisters, kvm_bindings::kvm_xsave, @@ -294,17 +297,65 @@ impl From for kvm_msr_entry { } } +#[derive(Error, Debug)] +pub enum XsaveStateError { + #[error("kvm_xsave extra field is not empty")] + XsaveExtraFieldNotEmpty, +} + impl From for XsaveState { - fn from(s: kvm_xsave) -> Self { - Self { region: s.region } + fn from(value: kvm_xsave) -> Self { + // Check if kvm_xsave struct size is larger than region size, indicating extra data exists + assert_eq!( + size_of_val(&value), + size_of_val(&value.region), + "kvm_xsave extra field is not empty" + ); + Self { + region: value.region, + extra: Vec::new(), + } } } -impl From for kvm_xsave { - fn from(s: XsaveState) -> Self { - Self { - region: s.region, +impl TryFrom for kvm_xsave { + type Error = XsaveStateError; + fn try_from(value: XsaveState) -> Result { + if !value.extra.is_empty() { + error!("XsaveState extra field is not empty"); + return Err(XsaveStateError::XsaveExtraFieldNotEmpty); + } + Ok(Self { + region: value.region, extra: Default::default(), + }) + } +} + +impl From<&xsave2> for XsaveState { + fn from(xsave: &xsave2) -> Self { + // SAFETY: `xsave` is a valid reference with properly initialized FAM structure. + let region = unsafe { + let ptr = xsave.as_fam_struct_ptr(); + (*ptr).xsave.region + }; + Self { + region, + extra: xsave.as_slice().to_vec(), + } + } +} + +impl XsaveState { + pub fn to_xsave2(&self) -> Result { + let mut xsave = xsave2::new(self.extra.len())?; + // SAFETY: `xsave` was just created via `Xsave::new()` with valid allocated memory. + unsafe { + let ptr = xsave.as_mut_fam_struct_ptr(); + (*ptr).xsave.region = self.region; } + let extra_slice = xsave.as_mut_slice(); + extra_slice.copy_from_slice(&self.extra); + Ok(xsave) } } From bcc6ac68e962bed0478adf1b8402088367a015f6 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 3 Dec 2025 00:57:15 +0100 Subject: [PATCH 0411/1893] seccomp: Add KVM_GET_XSAVE2 for amx state Signed-off-by: Oliver Anderson Signed-off-by: Songqian Li --- vmm/src/seccomp_filters.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index ba9f1f455d..8c0071fc74 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -378,6 +378,7 @@ fn create_vmm_ioctl_seccomp_rule_kvm() -> Result, BackendError> const KVM_GET_TSC_KHZ: u64 = 0xaea3; const KVM_GET_XCRS: u64 = 0x8188_aea6; const KVM_GET_XSAVE: u64 = 0x9000_aea4; + const KVM_GET_XSAVE2: u64 = 0x9000_aecf; const KVM_KVMCLOCK_CTRL: u64 = 0xaead; const KVM_SET_CLOCK: u64 = 0x4030_ae7b; const KVM_SET_CPUID2: u64 = 0x4008_ae90; @@ -406,6 +407,7 @@ fn create_vmm_ioctl_seccomp_rule_kvm() -> Result, BackendError> and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_TSC_KHZ)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_XCRS,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_XSAVE,)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_XSAVE2,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_KVMCLOCK_CTRL)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_CLOCK)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_CPUID2)?], From 0ddb032cab710de65b485c881dfa0505941594b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 23:30:55 +0000 Subject: [PATCH 0412/1893] build: Bump actions/upload-artifact from 5 to 6 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 411684b758..18317aeae5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -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@v5 + uses: actions/upload-artifact@v6 with: name: Artifacts for ${{ matrix.platform.target }} path: | @@ -80,7 +80,7 @@ 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@v5 + uses: actions/upload-artifact@v6 with: path: cloud-hypervisor-${{ github.event.ref }}.tar.xz name: cloud-hypervisor-${{ github.event.ref }}.tar.xz From 53092359b45fe3778833dc6b1297b7ed034a0ce6 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 12 Dec 2025 09:06:40 +0100 Subject: [PATCH 0413/1893] block: rename DiskFile::size() -> DiskFile::logical_size() This better reflects the actual usage. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- block/src/async_io.rs | 7 ++++++- block/src/fixed_vhd.rs | 2 +- block/src/fixed_vhd_async.rs | 12 ++++++++---- block/src/fixed_vhd_sync.rs | 6 +++--- block/src/lib.rs | 13 +++++++++---- block/src/qcow/mod.rs | 2 +- block/src/qcow/raw_file.rs | 2 +- block/src/qcow_sync.rs | 4 ++-- block/src/raw_async.rs | 2 +- block/src/raw_async_aio.rs | 2 +- block/src/raw_sync.rs | 2 +- block/src/vhdx/mod.rs | 2 +- block/src/vhdx_sync.rs | 4 ++-- virtio-devices/src/block.rs | 4 ++-- 14 files changed, 39 insertions(+), 25 deletions(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index e4d8aaa256..fb60fde71d 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -56,7 +56,12 @@ impl AsRawFd for BorrowedDiskFd<'_> { /// This allows abstracting over raw image formats as well as structured /// image formats. pub trait DiskFile: Send { - fn size(&mut self) -> DiskFileResult; + /// Returns the logical disk size a guest will see. + /// + /// For raw formats, this is equal to the 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; fn new_async_io(&self, ring_depth: u32) -> DiskFileResult>; fn topology(&mut self) -> DiskTopology { DiskTopology::default() diff --git a/block/src/fixed_vhd.rs b/block/src/fixed_vhd.rs index 379005ae28..35364906a1 100644 --- a/block/src/fixed_vhd.rs +++ b/block/src/fixed_vhd.rs @@ -75,7 +75,7 @@ impl Seek for FixedVhd { } impl BlockBackend for FixedVhd { - fn size(&self) -> std::result::Result { + fn logical_size(&self) -> Result { Ok(self.size) } } diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index ac02e21bf3..d56c1fafca 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -23,14 +23,18 @@ impl FixedVhdDiskAsync { } impl DiskFile for FixedVhdDiskAsync { - fn size(&mut self) -> DiskFileResult { - Ok(self.0.size().unwrap()) + fn logical_size(&mut self) -> DiskFileResult { + Ok(self.0.logical_size().unwrap()) } fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { Ok(Box::new( - FixedVhdAsync::new(self.0.as_raw_fd(), ring_depth, self.0.size().unwrap()) - .map_err(DiskFileError::NewAsyncIo)?, + FixedVhdAsync::new( + self.0.as_raw_fd(), + ring_depth, + self.0.logical_size().unwrap(), + ) + .map_err(DiskFileError::NewAsyncIo)?, ) as Box) } diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index c125710698..07702c56e7 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -23,13 +23,13 @@ impl FixedVhdDiskSync { } impl DiskFile for FixedVhdDiskSync { - fn size(&mut self) -> DiskFileResult { - Ok(self.0.size().unwrap()) + fn logical_size(&mut self) -> DiskFileResult { + Ok(self.0.logical_size().unwrap()) } fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { Ok(Box::new( - FixedVhdSync::new(self.0.as_raw_fd(), self.0.size().unwrap()) + FixedVhdSync::new(self.0.as_raw_fd(), self.0.logical_size().unwrap()) .map_err(DiskFileError::NewAsyncIo)?, ) as Box) } diff --git a/block/src/lib.rs b/block/src/lib.rs index 212eab6703..756163afe2 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -80,8 +80,8 @@ pub enum Error { DetectImageType(#[source] std::io::Error), #[error("Failure in fixed vhd")] FixedVhdError(#[source] std::io::Error), - #[error("Getting a block's metadata fails for any reason")] - GetFileMetadata, + #[error("Getting a block's metadata failed")] + GetFileMetadata(#[source] std::io::Error), #[error("The requested operation would cause a seek beyond disk end")] InvalidOffset, #[error("Failure in qcow")] @@ -96,7 +96,7 @@ pub enum Error { fn build_device_id(disk_path: &Path) -> result::Result { let blk_metadata = match disk_path.metadata() { - Err(_) => return Err(Error::GetFileMetadata), + Err(e) => return Err(Error::GetFileMetadata(e)), Ok(m) => m, }; // This is how kvmtool does it. @@ -834,7 +834,12 @@ pub fn detect_image_type(f: &mut File) -> std::io::Result { } pub trait BlockBackend: Read + Write + Seek + Send + Debug { - fn size(&self) -> Result; + /// Returns the logical disk size a guest will see. + /// + /// For raw formats, this is equal to the 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(&self) -> Result; } #[derive(Debug)] diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 4600ec1188..0293d97b1b 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1808,7 +1808,7 @@ impl SeekHole for QcowFile { } impl BlockBackend for QcowFile { - fn size(&self) -> std::result::Result { + fn logical_size(&self) -> std::result::Result { Ok(self.virtual_size()) } } diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index 232871472a..060fcaa2e0 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -354,7 +354,7 @@ impl SeekHole for RawFile { } impl BlockBackend for RawFile { - fn size(&self) -> std::result::Result { + fn logical_size(&self) -> std::result::Result { Ok(self.metadata().map_err(crate::Error::RawFileError)?.len()) } } diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index cd6a1fb774..241bb52162 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -9,11 +9,11 @@ use std::os::fd::AsRawFd; use vmm_sys_util::eventfd::EventFd; -use crate::AsyncAdaptor; use crate::async_io::{ AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; use crate::qcow::{QcowFile, RawFile, Result as QcowResult}; +use crate::{AsyncAdaptor, BlockBackend}; pub struct QcowDiskSync { qcow_file: QcowFile, @@ -28,7 +28,7 @@ impl QcowDiskSync { } impl DiskFile for QcowDiskSync { - fn size(&mut self) -> DiskFileResult { + fn logical_size(&mut self) -> DiskFileResult { self.qcow_file .seek(SeekFrom::End(0)) .map_err(DiskFileError::Size) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 0018b50290..30ff853849 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -26,7 +26,7 @@ impl RawFileDisk { } impl DiskFile for RawFileDisk { - fn size(&mut self) -> DiskFileResult { + fn logical_size(&mut self) -> DiskFileResult { self.file .seek(SeekFrom::End(0)) .map_err(DiskFileError::Size) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index f2f070b43d..1669bb51bc 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -29,7 +29,7 @@ impl RawFileDiskAio { } impl DiskFile for RawFileDiskAio { - fn size(&mut self) -> DiskFileResult { + fn logical_size(&mut self) -> DiskFileResult { self.file .seek(SeekFrom::End(0)) .map_err(DiskFileError::Size) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index b17018f72b..b7d652a56b 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -26,7 +26,7 @@ impl RawFileDiskSync { } impl DiskFile for RawFileDiskSync { - fn size(&mut self) -> DiskFileResult { + fn logical_size(&mut self) -> DiskFileResult { self.file .seek(SeekFrom::End(0)) .map_err(DiskFileError::Size) diff --git a/block/src/vhdx/mod.rs b/block/src/vhdx/mod.rs index 141c43c6d5..5bcb198f2c 100644 --- a/block/src/vhdx/mod.rs +++ b/block/src/vhdx/mod.rs @@ -202,7 +202,7 @@ impl Seek for Vhdx { } impl BlockBackend for Vhdx { - fn size(&self) -> std::result::Result { + fn logical_size(&self) -> std::result::Result { Ok(self.virtual_disk_size()) } } diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 01bcbf5e7f..cba2ad0545 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -8,11 +8,11 @@ use std::os::fd::AsRawFd; use vmm_sys_util::eventfd::EventFd; -use crate::AsyncAdaptor; use crate::async_io::{ AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; use crate::vhdx::{Result as VhdxResult, Vhdx}; +use crate::{AsyncAdaptor, BlockBackend, Error}; pub struct VhdxDiskSync { vhdx_file: Vhdx, @@ -27,7 +27,7 @@ impl VhdxDiskSync { } impl DiskFile for VhdxDiskSync { - fn size(&mut self) -> DiskFileResult { + fn logical_size(&mut self) -> DiskFileResult { Ok(self.vhdx_file.virtual_disk_size()) } diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index c358cdf746..c7fc934c44 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -675,7 +675,7 @@ impl Block { ) } else { let disk_size = disk_image - .size() + .logical_size() .map_err(|e| io::Error::other(format!("Failed getting disk size: {e}")))?; if disk_size % SECTOR_SIZE != 0 { warn!( @@ -773,7 +773,7 @@ impl Block { // TODO In future, we could add a `lock_granularity=` configuration to the CLI. // For now, we stick to QEMU behavior. fn lock_granularity(&mut self) -> LockGranularity { - self.disk_image.size().map_or_else( + self.disk_image.logical_size().map_or_else( // use a safe fallback |e| { let fallback = LockGranularity::WholeFile; From 603b5e862cf19a55b1ca81231acfc057cfba4572 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 12 Dec 2025 09:07:44 +0100 Subject: [PATCH 0414/1893] block: add DiskFile::physical_size() This is a pre-requisite for the bug fix in the following commit. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- block/src/async_io.rs | 4 +++- block/src/fixed_vhd.rs | 8 ++++++++ block/src/fixed_vhd_async.rs | 4 ++++ block/src/fixed_vhd_sync.rs | 10 ++++++++++ block/src/lib.rs | 4 +++- block/src/qcow/mod.rs | 6 ++++++ block/src/qcow/qcow_raw_file.rs | 4 ++++ block/src/qcow/raw_file.rs | 4 ++++ block/src/qcow_sync.rs | 10 ++++++++++ block/src/raw_async.rs | 7 +++++++ block/src/raw_async_aio.rs | 7 +++++++ block/src/raw_sync.rs | 7 +++++++ block/src/vhdx/mod.rs | 7 +++++++ block/src/vhdx_sync.rs | 10 ++++++++++ 14 files changed, 90 insertions(+), 2 deletions(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index fb60fde71d..52f642908c 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -58,10 +58,12 @@ impl AsRawFd for BorrowedDiskFd<'_> { pub trait DiskFile: Send { /// Returns the logical disk size a guest will see. /// - /// For raw formats, this is equal to the physical size. For file formats + /// 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; + /// Returns the physical size of the underlying file. + fn physical_size(&mut self) -> DiskFileResult; fn new_async_io(&self, ring_depth: u32) -> DiskFileResult>; fn topology(&mut self) -> DiskTopology { DiskTopology::default() diff --git a/block/src/fixed_vhd.rs b/block/src/fixed_vhd.rs index 35364906a1..aa9bd95303 100644 --- a/block/src/fixed_vhd.rs +++ b/block/src/fixed_vhd.rs @@ -78,6 +78,14 @@ impl BlockBackend for FixedVhd { fn logical_size(&self) -> Result { Ok(self.size) } + + /// Returns the physical size of the underlying file. + fn physical_size(&self) -> Result { + self.file + .metadata() + .map(|m| m.len()) + .map_err(crate::Error::GetFileMetadata) + } } impl Clone for FixedVhd { diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index d56c1fafca..df596a6165 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -27,6 +27,10 @@ impl DiskFile for FixedVhdDiskAsync { Ok(self.0.logical_size().unwrap()) } + fn physical_size(&mut self) -> DiskFileResult { + Ok(self.0.physical_size().unwrap()) + } + fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { Ok(Box::new( FixedVhdAsync::new( diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index 07702c56e7..fd44adfd05 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -27,6 +27,16 @@ impl DiskFile for FixedVhdDiskSync { Ok(self.0.logical_size().unwrap()) } + fn physical_size(&mut self) -> DiskFileResult { + 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> { Ok(Box::new( FixedVhdSync::new(self.0.as_raw_fd(), self.0.logical_size().unwrap()) diff --git a/block/src/lib.rs b/block/src/lib.rs index 756163afe2..34c96eea7f 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -836,10 +836,12 @@ pub fn detect_image_type(f: &mut File) -> std::io::Result { pub trait BlockBackend: Read + Write + Seek + Send + Debug { /// Returns the logical disk size a guest will see. /// - /// For raw formats, this is equal to the physical_size. For file formats + /// 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(&self) -> Result; + /// Returns the physical size of the underlying file. + fn physical_size(&self) -> Result; } #[derive(Debug)] diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 0293d97b1b..5e1ec8d548 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1811,6 +1811,12 @@ impl BlockBackend for QcowFile { fn logical_size(&self) -> std::result::Result { Ok(self.virtual_size()) } + + fn physical_size(&self) -> std::result::Result { + self.raw_file + .physical_size() + .map_err(crate::Error::GetFileMetadata) + } } // Returns an Error if the given offset doesn't align to a cluster boundary. diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index 1b869b4596..8d5dba2e40 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -179,6 +179,10 @@ impl QcowRawFile { self.file.seek(SeekFrom::Start(address))?; self.file.write_all(&data[0..cluster_size]) } + + pub fn physical_size(&self) -> Result { + self.file.metadata().map(|m| m.len()) + } } impl Clone for QcowRawFile { diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index 060fcaa2e0..b80a30adc2 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -357,6 +357,10 @@ impl BlockBackend for RawFile { fn logical_size(&self) -> std::result::Result { Ok(self.metadata().map_err(crate::Error::RawFileError)?.len()) } + + fn physical_size(&self) -> std::result::Result { + Ok(self.metadata().map_err(crate::Error::RawFileError)?.len()) + } } impl Clone for RawFile { diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 241bb52162..0efc106fc7 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -34,6 +34,16 @@ impl DiskFile for QcowDiskSync { .map_err(DiskFileError::Size) } + fn physical_size(&mut self) -> DiskFileResult { + self.qcow_file.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> { Ok(Box::new(QcowSync::new(self.qcow_file.clone())) as Box) } diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 30ff853849..f62c563d28 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -32,6 +32,13 @@ impl DiskFile for RawFileDisk { .map_err(DiskFileError::Size) } + fn physical_size(&mut self) -> DiskFileResult { + self.file + .metadata() + .map(|m| m.len()) + .map_err(DiskFileError::Size) + } + fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { Ok(Box::new( RawFileAsync::new(self.file.as_raw_fd(), ring_depth) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 1669bb51bc..ad41872c74 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -35,6 +35,13 @@ impl DiskFile for RawFileDiskAio { .map_err(DiskFileError::Size) } + fn physical_size(&mut self) -> DiskFileResult { + self.file + .metadata() + .map(|m| m.len()) + .map_err(DiskFileError::Size) + } + fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { Ok(Box::new( RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index b7d652a56b..0f9ce07025 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -32,6 +32,13 @@ impl DiskFile for RawFileDiskSync { .map_err(DiskFileError::Size) } + fn physical_size(&mut self) -> DiskFileResult { + self.file + .metadata() + .map(|m| m.len()) + .map_err(DiskFileError::Size) + } + fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box) } diff --git a/block/src/vhdx/mod.rs b/block/src/vhdx/mod.rs index 5bcb198f2c..f8d404fc5b 100644 --- a/block/src/vhdx/mod.rs +++ b/block/src/vhdx/mod.rs @@ -205,6 +205,13 @@ impl BlockBackend for Vhdx { fn logical_size(&self) -> std::result::Result { Ok(self.virtual_disk_size()) } + + fn physical_size(&self) -> std::result::Result { + self.file + .metadata() + .map(|m| m.len()) + .map_err(crate::Error::GetFileMetadata) + } } impl Clone for Vhdx { diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index cba2ad0545..47e7539dcb 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -31,6 +31,16 @@ impl DiskFile for VhdxDiskSync { Ok(self.vhdx_file.virtual_disk_size()) } + fn physical_size(&mut self) -> DiskFileResult { + self.vhdx_file.physical_size().map_err(|e| { + let io_inner = match e { + Error::GetFileMetadata(e) => e, + _ => unreachable!(), + }; + DiskFileError::Size(io_inner) + }) + } + fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { Ok( Box::new(VhdxSync::new(self.vhdx_file.clone()).map_err(DiskFileError::NewAsyncIo)?) From 9569809d4cc8e640302c3e5fe6f8b21634c356f2 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 11 Dec 2025 10:52:00 +0100 Subject: [PATCH 0415/1893] block: fix bug with wrong lock granularity for image container formats The lock must always correspond to the physical size of the file, everything else doesn't make sense. Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- virtio-devices/src/block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index c7fc934c44..0f46ce3890 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -773,7 +773,7 @@ impl Block { // TODO In future, we could add a `lock_granularity=` configuration to the CLI. // For now, we stick to QEMU behavior. fn lock_granularity(&mut self) -> LockGranularity { - self.disk_image.logical_size().map_or_else( + self.disk_image.physical_size().map_or_else( // use a safe fallback |e| { let fallback = LockGranularity::WholeFile; From a8d1411307dc80ae1e13315e9507650c5f3a775d Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Tue, 25 Nov 2025 16:28:30 +0100 Subject: [PATCH 0416/1893] vmm: Write directly to member when creating new devices Virtio PCI devices are created in a set of nested functions. In each of this functions a vector is created to add created devices to, only to be appended to the vector of the higher nesting level. Those nested vectors are unnecessary as we can directly write to the member of `DeviceManager`. Signed-off-by: Pascal Scholz On-behalf-of: SAP pascal.scholz@sap.com --- vmm/src/device_manager.rs | 139 +++++++++++++++----------------------- 1 file changed, 56 insertions(+), 83 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index fac6ef7da0..077c4ec7ee 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1410,8 +1410,6 @@ impl DeviceManager { ) -> DeviceManagerResult<()> { trace_scoped!("create_devices"); - let mut virtio_devices: Vec = Vec::new(); - self.cpu_manager .lock() .unwrap() @@ -1464,7 +1462,6 @@ impl DeviceManager { self.console = self.add_console_devices( legacy_interrupt_manager.as_ref(), - &mut virtio_devices, console_info, console_resize_pipe, )?; @@ -1477,11 +1474,8 @@ impl DeviceManager { } self.legacy_interrupt_manager = Some(legacy_interrupt_manager); - virtio_devices.append(&mut self.make_virtio_devices()?); - - self.add_pci_devices(virtio_devices.clone())?; - - self.virtio_devices = virtio_devices; + self.make_virtio_devices()?; + self.add_pci_devices()?; // Add pvmemcontrol if required #[cfg(feature = "pvmemcontrol")] @@ -1590,10 +1584,7 @@ impl DeviceManager { } #[allow(unused_variables)] - fn add_pci_devices( - &mut self, - virtio_devices: Vec, - ) -> DeviceManagerResult<()> { + fn add_pci_devices(&mut self) -> DeviceManagerResult<()> { let iommu_id = String::from(IOMMU_DEVICE_NAME); let iommu_address_width_bits = @@ -1635,7 +1626,7 @@ impl DeviceManager { let mut iommu_attached_devices = Vec::new(); { - for handle in virtio_devices { + for handle in self.virtio_devices.clone() { let mapping: Option> = if handle.iommu { self.iommu_mapping.clone() } else { @@ -2323,7 +2314,6 @@ impl DeviceManager { fn add_virtio_console_device( &mut self, - virtio_devices: &mut Vec, console_fd: ConsoleOutput, resize_pipe: Option>, ) -> DeviceManagerResult>> { @@ -2379,7 +2369,7 @@ impl DeviceManager { ) .map_err(DeviceManagerError::CreateVirtioConsole)?; let virtio_console_device = Arc::new(Mutex::new(virtio_console_device)); - virtio_devices.push(MetaVirtioDevice { + self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_console_device) as Arc>, iommu: console_config.iommu, @@ -2412,7 +2402,6 @@ impl DeviceManager { fn add_console_devices( &mut self, interrupt_manager: &dyn InterruptManager, - virtio_devices: &mut Vec, console_info: Option, console_resize_pipe: Option>, ) -> DeviceManagerResult> { @@ -2476,11 +2465,8 @@ impl DeviceManager { } } - let console_resizer = self.add_virtio_console_device( - virtio_devices, - console_info.console_main_fd, - console_resize_pipe, - )?; + let console_resizer = + self.add_virtio_console_device(console_info.console_main_fd, console_resize_pipe)?; Ok(Arc::new(Console { console_resizer })) } @@ -2538,35 +2524,33 @@ impl DeviceManager { Ok(()) } - fn make_virtio_devices(&mut self) -> DeviceManagerResult> { - let mut devices: Vec = Vec::new(); - + fn make_virtio_devices(&mut self) -> DeviceManagerResult<()> { // Create "standard" virtio devices (net/block/rng) - devices.append(&mut self.make_virtio_block_devices()?); - devices.append(&mut self.make_virtio_net_devices()?); - devices.append(&mut self.make_virtio_rng_devices()?); + self.make_virtio_block_devices()?; + self.make_virtio_net_devices()?; + self.make_virtio_rng_devices()?; // Add virtio-fs if required - devices.append(&mut self.make_virtio_fs_devices()?); + self.make_virtio_fs_devices()?; // Add virtio-pmem if required - devices.append(&mut self.make_virtio_pmem_devices()?); + self.make_virtio_pmem_devices()?; // Add virtio-vsock if required - devices.append(&mut self.make_virtio_vsock_devices()?); + self.make_virtio_vsock_devices()?; - devices.append(&mut self.make_virtio_mem_devices()?); + self.make_virtio_mem_devices()?; // Add virtio-balloon if required - devices.append(&mut self.make_virtio_balloon_devices()?); + self.make_virtio_balloon_devices()?; // Add virtio-watchdog device - devices.append(&mut self.make_virtio_watchdog_devices()?); + self.make_virtio_watchdog_devices()?; // Add vDPA devices if required - devices.append(&mut self.make_vdpa_devices()?); + self.make_vdpa_devices()?; - Ok(devices) + Ok(()) } // Cache whether aio is supported to avoid checking for very block device @@ -2837,18 +2821,17 @@ impl DeviceManager { }) } - fn make_virtio_block_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); - + fn make_virtio_block_devices(&mut self) -> DeviceManagerResult<()> { let mut block_devices = self.config.lock().unwrap().disks.clone(); if let Some(disk_list_cfg) = &mut block_devices { for disk_cfg in disk_list_cfg.iter_mut() { - devices.push(self.make_virtio_block_device(disk_cfg, false)?); + let device = self.make_virtio_block_device(disk_cfg, false)?; + self.virtio_devices.push(device); } } self.config.lock().unwrap().disks = block_devices; - Ok(devices) + Ok(()) } fn make_virtio_net_device( @@ -3009,22 +2992,20 @@ impl DeviceManager { } /// Add virto-net and vhost-user-net devices - fn make_virtio_net_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); + fn make_virtio_net_devices(&mut self) -> DeviceManagerResult<()> { let mut net_devices = self.config.lock().unwrap().net.clone(); if let Some(net_list_cfg) = &mut net_devices { for net_cfg in net_list_cfg.iter_mut() { - devices.push(self.make_virtio_net_device(net_cfg)?); + let device = self.make_virtio_net_device(net_cfg)?; + self.virtio_devices.push(device); } } self.config.lock().unwrap().net = net_devices; - Ok(devices) + Ok(()) } - fn make_virtio_rng_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); - + fn make_virtio_rng_devices(&mut self) -> DeviceManagerResult<()> { // Add virtio-rng if required let rng_config = self.config.lock().unwrap().rng.clone(); if let Some(rng_path) = rng_config.src.to_str() { @@ -3045,7 +3026,7 @@ impl DeviceManager { ) .map_err(DeviceManagerError::CreateVirtioRng)?, )); - devices.push(MetaVirtioDevice { + self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_rng_device) as Arc>, iommu: rng_config.iommu, @@ -3063,7 +3044,7 @@ impl DeviceManager { .insert(id.clone(), device_node!(id, virtio_rng_device)); } - Ok(devices) + Ok(()) } fn make_virtio_fs_device( @@ -3119,18 +3100,17 @@ impl DeviceManager { } } - fn make_virtio_fs_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); - + fn make_virtio_fs_devices(&mut self) -> DeviceManagerResult<()> { let mut fs_devices = self.config.lock().unwrap().fs.clone(); if let Some(fs_list_cfg) = &mut fs_devices { for fs_cfg in fs_list_cfg.iter_mut() { - devices.push(self.make_virtio_fs_device(fs_cfg)?); + let device = self.make_virtio_fs_device(fs_cfg)?; + self.virtio_devices.push(device); } } self.config.lock().unwrap().fs = fs_devices; - Ok(devices) + Ok(()) } fn make_virtio_pmem_device( @@ -3306,18 +3286,18 @@ impl DeviceManager { }) } - fn make_virtio_pmem_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); + fn make_virtio_pmem_devices(&mut self) -> DeviceManagerResult<()> { // Add virtio-pmem if required let mut pmem_devices = self.config.lock().unwrap().pmem.clone(); if let Some(pmem_list_cfg) = &mut pmem_devices { for pmem_cfg in pmem_list_cfg.iter_mut() { - devices.push(self.make_virtio_pmem_device(pmem_cfg)?); + let device = self.make_virtio_pmem_device(pmem_cfg)?; + self.virtio_devices.push(device); } } self.config.lock().unwrap().pmem = pmem_devices; - Ok(devices) + Ok(()) } fn make_virtio_vsock_device( @@ -3377,21 +3357,18 @@ impl DeviceManager { }) } - fn make_virtio_vsock_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); - + fn make_virtio_vsock_devices(&mut self) -> DeviceManagerResult<()> { let mut vsock = self.config.lock().unwrap().vsock.clone(); if let Some(vsock_cfg) = &mut vsock { - devices.push(self.make_virtio_vsock_device(vsock_cfg)?); + let device = self.make_virtio_vsock_device(vsock_cfg)?; + self.virtio_devices.push(device); } self.config.lock().unwrap().vsock = vsock; - Ok(devices) + Ok(()) } - fn make_virtio_mem_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); - + fn make_virtio_mem_devices(&mut self) -> DeviceManagerResult<()> { let mm = self.memory_manager.clone(); let mut mm = mm.lock().unwrap(); for (memory_zone_id, memory_zone) in mm.memory_zones_mut().iter_mut() { @@ -3426,7 +3403,7 @@ impl DeviceManager { self.virtio_mem_devices.push(Arc::clone(&virtio_mem_device)); - devices.push(MetaVirtioDevice { + self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_mem_device) as Arc>, iommu: false, @@ -3445,7 +3422,7 @@ impl DeviceManager { } } - Ok(devices) + Ok(()) } #[cfg(feature = "pvmemcontrol")] @@ -3490,9 +3467,7 @@ impl DeviceManager { Ok((pvmemcontrol_bus_device, pvmemcontrol_pci_device)) } - fn make_virtio_balloon_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); - + fn make_virtio_balloon_devices(&mut self) -> DeviceManagerResult<()> { if let Some(balloon_config) = &self.config.lock().unwrap().balloon { let id = String::from(BALLOON_DEVICE_NAME); info!("Creating virtio-balloon device: id = {id}"); @@ -3515,7 +3490,7 @@ impl DeviceManager { self.balloon = Some(virtio_balloon_device.clone()); - devices.push(MetaVirtioDevice { + self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_balloon_device) as Arc>, iommu: false, @@ -3530,14 +3505,12 @@ impl DeviceManager { .insert(id.clone(), device_node!(id, virtio_balloon_device)); } - Ok(devices) + Ok(()) } - fn make_virtio_watchdog_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); - + fn make_virtio_watchdog_devices(&mut self) -> DeviceManagerResult<()> { if !self.config.lock().unwrap().watchdog { - return Ok(devices); + return Ok(()); } let id = String::from(WATCHDOG_DEVICE_NAME); @@ -3556,7 +3529,7 @@ impl DeviceManager { ) .map_err(DeviceManagerError::CreateVirtioWatchdog)?, )); - devices.push(MetaVirtioDevice { + self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_watchdog_device) as Arc>, iommu: false, @@ -3570,7 +3543,7 @@ impl DeviceManager { .unwrap() .insert(id.clone(), device_node!(id, virtio_watchdog_device)); - Ok(devices) + Ok(()) } fn make_vdpa_device( @@ -3624,18 +3597,18 @@ impl DeviceManager { }) } - fn make_vdpa_devices(&mut self) -> DeviceManagerResult> { - let mut devices = Vec::new(); + fn make_vdpa_devices(&mut self) -> DeviceManagerResult<()> { // Add vdpa if required let mut vdpa_devices = self.config.lock().unwrap().vdpa.clone(); if let Some(vdpa_list_cfg) = &mut vdpa_devices { for vdpa_cfg in vdpa_list_cfg.iter_mut() { - devices.push(self.make_vdpa_device(vdpa_cfg)?); + let device = self.make_vdpa_device(vdpa_cfg)?; + self.virtio_devices.push(device); } } self.config.lock().unwrap().vdpa = vdpa_devices; - Ok(devices) + Ok(()) } fn next_device_name(&mut self, prefix: &str) -> DeviceManagerResult { From 82e8002fa0a195438b4cb4239faebfe11b18afec Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 10 Dec 2025 13:30:22 +0100 Subject: [PATCH 0417/1893] misc: clippy: remove some overrides Closes #4986 [0]. [0]: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/4986 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- cloud-hypervisor/src/main.rs | 7 +++---- virtio-devices/src/vsock/packet.rs | 4 ++-- vm-device/src/bus.rs | 3 --- vmm/src/lib.rs | 16 ++++++---------- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 10d73b5940..d08293b6e4 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -487,8 +487,7 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String .args(args) } -#[allow(clippy::needless_pass_by_value)] -fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { +fn start_vmm(cmd_arguments: &ArgMatches) -> Result, Error> { let log_level = match cmd_arguments.get_count("v") { 0 => LevelFilter::Warn, 1 => LevelFilter::Info, @@ -724,7 +723,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { cmd_arguments.contains_id("kernel") || cmd_arguments.contains_id("firmware"); if payload_present { - let vm_params = VmParams::from_arg_matches(&cmd_arguments); + let vm_params = VmParams::from_arg_matches(cmd_arguments); let vm_config = VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?; // Create and boot the VM based off the VM config we just built. @@ -886,7 +885,7 @@ fn main() { warn!("Error expanding FD table: {e}"); } - let exit_code = match start_vmm(cmd_arguments) { + let exit_code = match start_vmm(&cmd_arguments) { Ok(path) => { path.map(|s| std::fs::remove_file(s).ok()); info!("Cloud Hypervisor exited successfully"); diff --git a/virtio-devices/src/vsock/packet.rs b/virtio-devices/src/vsock/packet.rs index d28b7b508b..a9e477c1da 100644 --- a/virtio-devices/src/vsock/packet.rs +++ b/virtio-devices/src/vsock/packet.rs @@ -417,7 +417,6 @@ impl VsockPacket { } #[cfg(test)] -#[allow(clippy::undocumented_unsafe_blocks)] mod unit_tests { use virtio_bindings::virtio_ring::VRING_DESC_F_WRITE; use virtio_queue::QueueOwnedT; @@ -465,8 +464,9 @@ mod unit_tests { let hdr_gpa = guest_desc.addr.get(); let hdr_ptr = get_host_address_range(mem, GuestAddress(hdr_gpa), VSOCK_PKT_HDR_SIZE).unwrap(); + // SAFETY: The length is valid. let len_ptr = unsafe { hdr_ptr.add(HDROFF_LEN) }; - + // SAFETY: The length is valid. LittleEndian::write_u32(unsafe { std::slice::from_raw_parts_mut(len_ptr, 4) }, len); } diff --git a/vm-device/src/bus.rs b/vm-device/src/bus.rs index a029f0cc9e..2897ac303e 100644 --- a/vm-device/src/bus.rs +++ b/vm-device/src/bus.rs @@ -304,7 +304,6 @@ mod unit_tests { } #[test] - #[allow(clippy::redundant_clone)] fn bus_read_write() { let bus = Bus::new(); let dummy = Arc::new(DummyDevice); @@ -322,7 +321,6 @@ mod unit_tests { } #[test] - #[allow(clippy::redundant_clone)] fn bus_read_write_values() { let bus = Bus::new(); let dummy = Arc::new(ConstantDevice); @@ -338,7 +336,6 @@ mod unit_tests { } #[test] - #[allow(clippy::redundant_clone)] fn busrange_cmp() { let range = BusRange { base: 0x10, len: 2 }; assert_eq!(range, BusRange { base: 0x10, len: 3 }); diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index cb98fd1e0a..863e95ce82 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1235,12 +1235,10 @@ impl Vmm { Ok(true) } - #[allow(clippy::needless_pass_by_value)] fn send_migration( vm: &mut Vm, - #[cfg(all(feature = "kvm", target_arch = "x86_64"))] hypervisor: Arc< - dyn hypervisor::Hypervisor, - >, + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + hypervisor: &dyn hypervisor::Hypervisor, send_data_migration: &VmSendMigrationData, ) -> result::Result<(), MigratableError> { // Set up the socket connection @@ -1265,12 +1263,10 @@ impl Vmm { } let amx = vm_config.lock().unwrap().cpus.features.amx; - let phys_bits = vm::physical_bits( - hypervisor.as_ref(), - vm_config.lock().unwrap().cpus.max_phys_bits, - ); + let phys_bits = + vm::physical_bits(hypervisor, vm_config.lock().unwrap().cpus.max_phys_bits); arch::generate_common_cpuid( - hypervisor.as_ref(), + hypervisor, &arch::CpuidConfig { phys_bits, kvm_hyperv: vm_config.lock().unwrap().cpus.kvm_hyperv, @@ -2323,7 +2319,7 @@ impl RequestHandler for Vmm { Self::send_migration( vm, #[cfg(all(feature = "kvm", target_arch = "x86_64"))] - self.hypervisor.clone(), + self.hypervisor.as_ref(), &send_data_migration, ) .map_err(|migration_err| { From 7288031c0ecf5771840e7ed014dbfe5c22d06444 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 10 Dec 2025 14:47:39 +0100 Subject: [PATCH 0418/1893] misc: slightly improve feature list readability Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/config.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 3e41a83baa..78d6f9f1e1 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -635,23 +635,22 @@ impl CpusConfig { .convert::("features") .map_err(Error::ParseCpus)? .unwrap_or_default(); - // Some ugliness here as the features being checked might be disabled - // at compile time causing the below allow and the need to specify the - // ref type in the match. - // The issue will go away once kvm_hyperv is moved under the features - // list as it will always be checked for. + #[allow(unused_mut)] let mut features = CpuFeatures::default(); - #[allow(clippy::never_loop)] - for s in features_list.0 { - match >::as_ref(&s) { - #[cfg(target_arch = "x86_64")] - "amx" => { - features.amx = true; - Ok(()) + { + #[cfg(target_arch = "x86_64")] + for feature in features_list.0 { + match feature.as_str() { + "amx" => features.amx = true, + _ => return Err(Error::InvalidCpuFeatures(feature)), } - _ => Err(Error::InvalidCpuFeatures(s)), - }?; + } + + #[cfg(not(target_arch = "x86_64"))] + if let Some(feature) = features_list.0.into_iter().next() { + return Err(Error::InvalidCpuFeatures(feature)); + } } let nested = parser From d696cea02414ea37b62a7d95675e5a04202cf0be Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 15 Dec 2025 20:07:42 +0100 Subject: [PATCH 0419/1893] performance-metrics: Refactor fio_control to BlockControl Introduce a new BlockControl struct to encapsulate fio operation parameters. This replaces the tuple-based fio_control with a more extensible structure that includes: - fio_ops: The FIO operation type - bandwidth: Whether to measure bandwidth or IOPS - test_file: The file path to test against This refactoring enables reusing performance_block_io with different test files. Signed-off-by: Anatol Belski --- performance-metrics/src/main.rs | 115 +++++++++++++++---- performance-metrics/src/performance_tests.rs | 12 +- 2 files changed, 101 insertions(+), 26 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index bba89cc4fc..79c0699cf9 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -162,6 +162,13 @@ impl fmt::Display for PerformanceTestOverrides { } } +#[derive(Clone)] +pub struct BlockControl { + pub fio_ops: FioOps, + pub bandwidth: bool, + pub test_file: &'static str, +} + #[derive(Clone)] pub struct PerformanceTestControl { test_timeout: u32, @@ -169,7 +176,7 @@ pub struct PerformanceTestControl { num_queues: Option, queue_size: Option, net_control: Option<(bool, bool)>, // First bool is for RX(true)/TX(false), second bool is for bandwidth or PPS - fio_control: Option<(FioOps, bool)>, // Second parameter controls whether we want bandwidth or IOPS + block_control: Option, num_boot_vcpus: Option, } @@ -189,9 +196,11 @@ impl fmt::Display for PerformanceTestControl { let (rx, bw) = o; output = format!("{output}, rx = {rx}, bandwidth = {bw}"); } - if let Some(o) = &self.fio_control { - let (ops, bw) = o; - output = format!("{output}, fio_ops = {ops}, bandwidth = {bw}"); + if let Some(o) = &self.block_control { + output = format!( + "{output}, fio_ops = {}, bandwidth = {}, test_file = {}", + o.fio_ops, o.bandwidth, o.test_file + ); } write!(f, "{output}") @@ -206,7 +215,7 @@ impl PerformanceTestControl { num_queues: None, queue_size: None, net_control: None, - fio_control: None, + block_control: None, num_boot_vcpus: Some(1), } } @@ -467,7 +476,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(1), queue_size: Some(128), - fio_control: Some((FioOps::Read, true)), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::Bps_to_MiBps, @@ -478,7 +491,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(1), queue_size: Some(128), - fio_control: Some((FioOps::Write, true)), + block_control: Some(BlockControl { + fio_ops: FioOps::Write, + bandwidth: true, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::Bps_to_MiBps, @@ -489,7 +506,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(1), queue_size: Some(128), - fio_control: Some((FioOps::RandomRead, true)), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::Bps_to_MiBps, @@ -500,7 +521,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(1), queue_size: Some(128), - fio_control: Some((FioOps::RandomWrite, true)), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomWrite, + bandwidth: true, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::Bps_to_MiBps, @@ -511,7 +536,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(2), queue_size: Some(128), - fio_control: Some((FioOps::Read, true)), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::Bps_to_MiBps, @@ -522,7 +551,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(2), queue_size: Some(128), - fio_control: Some((FioOps::Write, true)), + block_control: Some(BlockControl { + fio_ops: FioOps::Write, + bandwidth: true, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::Bps_to_MiBps, @@ -533,7 +566,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(2), queue_size: Some(128), - fio_control: Some((FioOps::RandomRead, true)), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::Bps_to_MiBps, @@ -544,7 +581,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(2), queue_size: Some(128), - fio_control: Some((FioOps::RandomWrite, true)), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomWrite, + bandwidth: true, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::Bps_to_MiBps, @@ -555,7 +596,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(1), queue_size: Some(128), - fio_control: Some((FioOps::Read, false)), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: false, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::identity, @@ -566,7 +611,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(1), queue_size: Some(128), - fio_control: Some((FioOps::Write, false)), + block_control: Some(BlockControl { + fio_ops: FioOps::Write, + bandwidth: false, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::identity, @@ -577,7 +626,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(1), queue_size: Some(128), - fio_control: Some((FioOps::RandomRead, false)), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: false, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::identity, @@ -588,7 +641,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(1), queue_size: Some(128), - fio_control: Some((FioOps::RandomWrite, false)), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomWrite, + bandwidth: false, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::identity, @@ -599,7 +656,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(2), queue_size: Some(128), - fio_control: Some((FioOps::Read, false)), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: false, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::identity, @@ -610,7 +671,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(2), queue_size: Some(128), - fio_control: Some((FioOps::Write, false)), + block_control: Some(BlockControl { + fio_ops: FioOps::Write, + bandwidth: false, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::identity, @@ -621,7 +686,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(2), queue_size: Some(128), - fio_control: Some((FioOps::RandomRead, false)), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: false, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::identity, @@ -632,7 +701,11 @@ const TEST_LIST: [PerformanceTest; 30] = [ control: PerformanceTestControl { num_queues: Some(2), queue_size: Some(128), - fio_control: Some((FioOps::RandomWrite, false)), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomWrite, + bandwidth: false, + test_file: BLK_IO_TEST_IMG, + }), ..PerformanceTestControl::default() }, unit_adjuster: adjuster::identity, diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index e16bd938e9..049310a092 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -32,7 +32,7 @@ enum Error { // The test image cannot be created on tmpfs (e.g. /tmp) filesystem, // as tmpfs does not support O_DIRECT -const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img"; +pub const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img"; pub fn init_tests(overrides: &PerformanceTestOverrides) { let mut cmd = format!("dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096"); @@ -366,7 +366,10 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { let test_timeout = control.test_timeout; let num_queues = control.num_queues.unwrap(); let queue_size = control.queue_size.unwrap(); - let (fio_ops, bandwidth) = control.fio_control.as_ref().unwrap(); + let block_control = control.block_control.as_ref().unwrap(); + let fio_ops = &block_control.fio_ops; + let bandwidth = block_control.bandwidth; + let test_file = block_control.test_file; let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest = performance_test_new_guest(Box::new(focal)); @@ -395,8 +398,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - format!("path={BLK_IO_TEST_IMG},queue_size={queue_size},num_queues={num_queues}") - .as_str(), + format!("path={test_file},queue_size={queue_size},num_queues={num_queues}").as_str(), ]) .default_net() .args(["--api-socket", &api_socket]) @@ -420,7 +422,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { .unwrap(); // Parse fio output - if *bandwidth { + if bandwidth { parse_fio_output(&output, fio_ops, num_queues).unwrap() } else { parse_fio_output_iops(&output, fio_ops, num_queues).unwrap() From d75bd1675c463103701d1f057e885c6724bca91a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 15 Dec 2025 20:14:50 +0100 Subject: [PATCH 0420/1893] performance-metrics: Add QCOW2 backing file performance test Add sequential and random read performance tests for QCOW2 overlays with QCOW2 backing files. Signed-off-by: Anatol Belski --- performance-metrics/src/main.rs | 32 +++++++++++++++++++- performance-metrics/src/performance_tests.rs | 16 ++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 79c0699cf9..e25a84d34f 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -319,7 +319,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 30] = [ +const TEST_LIST: [PerformanceTest; 32] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -710,6 +710,36 @@ const TEST_LIST: [PerformanceTest; 30] = [ }, unit_adjuster: adjuster::identity, }, + PerformanceTest { + name: "block_qcow2_backing_qcow2_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: OVERLAY_WITH_QCOW2_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_backing_qcow2_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: OVERLAY_WITH_QCOW2_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 049310a092..1407037017 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -33,6 +33,8 @@ enum Error { // The test image cannot be created on tmpfs (e.g. /tmp) filesystem, // as tmpfs does not support O_DIRECT pub const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img"; +const QCOW2_BACKING_FILE: &str = "/var/tmp/ch-blk-io-test-qcow2-backing.qcow2"; +pub const OVERLAY_WITH_QCOW2_BACKING: &str = "/var/tmp/ch-blk-io-test-overlay-qcow2.qcow2"; pub fn init_tests(overrides: &PerformanceTestOverrides) { let mut cmd = format!("dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096"); @@ -54,11 +56,25 @@ pub fn init_tests(overrides: &PerformanceTestOverrides) { } assert!(exec_host_command_output(&cmd).status.success()); + + // QCOW2 backing file for backing file tests + cmd = format!("qemu-img create -f qcow2 -o preallocation=full {QCOW2_BACKING_FILE} 4G"); + assert!(exec_host_command_output(&cmd).status.success()); + + // QCOW2 overlay with QCOW2 backing + cmd = format!( + "qemu-img create -f qcow2 -b {QCOW2_BACKING_FILE} -F qcow2 {OVERLAY_WITH_QCOW2_BACKING} 4G" + ); + assert!(exec_host_command_output(&cmd).status.success()); } pub fn cleanup_tests() { fs::remove_file(BLK_IO_TEST_IMG) .unwrap_or_else(|_| panic!("Failed to remove file '{BLK_IO_TEST_IMG}'.")); + fs::remove_file(QCOW2_BACKING_FILE) + .unwrap_or_else(|_| panic!("Failed to remove file '{QCOW2_BACKING_FILE}'.")); + fs::remove_file(OVERLAY_WITH_QCOW2_BACKING) + .unwrap_or_else(|_| panic!("Failed to remove file '{OVERLAY_WITH_QCOW2_BACKING}'.")); } // Performance tests are expected to be executed sequentially, so we can From 8e52bf251bf61bf18d8fa08d05adfdea9388a60c Mon Sep 17 00:00:00 2001 From: Thomas Prescher Date: Mon, 10 Nov 2025 14:22:48 +0100 Subject: [PATCH 0421/1893] block: virtio-devices: make disk_nsectors a shared atomic This change is a prerequisite for live disk resizing. Before this commit, the epoll-handler threads just got a copy of the sector size which we cannot update during runtime. On-behalf-of: SAP thomas.prescher@sap.com Signed-off-by: Thomas Prescher --- virtio-devices/src/block.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 0f46ce3890..2ede4c15ad 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -137,7 +137,7 @@ struct BlockEpollHandler { queue: Queue, mem: GuestMemoryAtomic, disk_image: Box, - disk_nsectors: u64, + disk_nsectors: Arc, interrupt_cb: Arc, serial: Vec, kill_evt: EventFd, @@ -234,7 +234,7 @@ impl BlockEpollHandler { let result = request.execute_async( desc_chain.memory(), - self.disk_nsectors, + self.disk_nsectors.load(Ordering::SeqCst), self.disk_image.as_mut(), &self.serial, desc_chain.head_index() as u64, @@ -625,7 +625,7 @@ pub struct Block { id: String, disk_image: Box, disk_path: PathBuf, - disk_nsectors: u64, + disk_nsectors: Arc, config: VirtioBlockConfig, writeback: Arc, counters: BlockCounters, @@ -753,7 +753,7 @@ impl Block { id, disk_image, disk_path, - disk_nsectors, + disk_nsectors: Arc::new(AtomicU64::new(disk_nsectors)), config, writeback: Arc::new(AtomicBool::new(true)), counters: BlockCounters::default(), @@ -843,7 +843,7 @@ impl Block { fn state(&self) -> BlockState { BlockState { disk_path: self.disk_path.to_str().unwrap().to_owned(), - disk_nsectors: self.disk_nsectors, + disk_nsectors: self.disk_nsectors.load(Ordering::SeqCst), avail_features: self.common.avail_features, acked_features: self.common.acked_features, config: self.config, @@ -957,7 +957,7 @@ impl VirtioDevice for Block { error!("failed to create new AsyncIo: {e}"); ActivateError::BadActivate })?, - disk_nsectors: self.disk_nsectors, + disk_nsectors: self.disk_nsectors.clone(), interrupt_cb: interrupt_cb.clone(), serial: self.serial.clone(), kill_evt, From 37d71fa038191b44d2206cb8b0b9e57986aa5f52 Mon Sep 17 00:00:00 2001 From: Thomas Prescher Date: Tue, 11 Nov 2025 09:40:16 +0100 Subject: [PATCH 0422/1893] vmm: disk resize infrastructure Add basic infrastructure so resize events are propagated to the underlying disk implementation. On-behalf-of: SAP thomas.prescher@sap.com Signed-off-by: Thomas Prescher --- block/src/async_io.rs | 10 +++++++++ fuzz/fuzz_targets/http_api.rs | 4 ++++ virtio-devices/src/block.rs | 40 ++++++++++++++++++++++++++++++++++- vmm/src/api/mod.rs | 2 ++ vmm/src/device_manager.rs | 16 ++++++++++++++ vmm/src/lib.rs | 10 +++++++++ vmm/src/vm.rs | 13 ++++++++++++ 7 files changed, 94 insertions(+), 1 deletion(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index 52f642908c..bd4c7bbe5d 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -18,6 +18,12 @@ pub enum DiskFileError { /// 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 = std::result::Result; @@ -68,6 +74,10 @@ pub trait DiskFile: Send { 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 diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index 6c00216a0d..ee9dd62f18 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -108,6 +108,10 @@ impl RequestHandler for StubApiRequestHandler { Ok(()) } + fn vm_resize_disk(&mut self, _: String, _: u64) -> Result<(), VmError> { + Ok(()) + } + #[cfg(target_arch = "x86_64")] fn vm_coredump(&mut self, _: &str) -> Result<(), VmError> { Ok(()) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 2ede4c15ad..d3a0e6df30 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -18,7 +18,7 @@ use std::sync::{Arc, Barrier}; use std::{io, result}; use anyhow::anyhow; -use block::async_io::{AsyncIo, AsyncIoError, DiskFile}; +use block::async_io::{AsyncIo, AsyncIoError, DiskFile, DiskFileError}; use block::fcntl::{LockError, LockGranularity, LockType, get_lock_state}; use block::{ ExecuteAsync, ExecuteError, Request, RequestType, VirtioBlockConfig, build_serial, fcntl, @@ -95,6 +95,16 @@ pub enum Error { /// The path of the disk image. path: PathBuf, }, + #[error("Disk image size is not a multiple of {}", SECTOR_SIZE)] + InvalidSize, + #[error("Failed to pause vcpus")] + PauseVcpus(#[source] MigratableError), + #[error("Failed to resume vcpus")] + ResumeVcpus(#[source] MigratableError), + #[error("Failed signal config interrupt")] + ConfigChange(#[source] io::Error), + #[error("Disk resize failed")] + DiskResize(#[source] DiskFileError), } pub type Result = result::Result; @@ -870,6 +880,34 @@ impl Block { self.writeback.store(writeback, Ordering::Release); } + pub fn resize(&mut self, new_size: u64) -> Result<()> { + if !new_size.is_multiple_of(SECTOR_SIZE) { + return Err(Error::InvalidSize); + } + + self.disk_image + .resize(new_size) + .map_err(Error::DiskResize)?; + + let nsectors = new_size / SECTOR_SIZE; + + self.common.pause().map_err(Error::PauseVcpus)?; + + self.disk_nsectors.store(nsectors, Ordering::SeqCst); + self.config.capacity = nsectors; + self.state().disk_nsectors = nsectors; + + self.common.resume().map_err(Error::ResumeVcpus)?; + + if let Some(interrupt_cb) = self.common.interrupt_cb.as_ref() { + interrupt_cb + .trigger(VirtioInterruptType::Config) + .map_err(Error::ConfigChange) + } else { + Ok(()) + } + } + #[cfg(fuzzing)] pub fn wait_for_epoll_threads(&mut self) { self.common.wait_for_epoll_threads(); diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index a0b090542a..3b5fe83dd2 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -315,6 +315,8 @@ pub trait RequestHandler { fn vm_resize_zone(&mut self, id: String, desired_ram: u64) -> Result<(), VmError>; + fn vm_resize_disk(&mut self, id: String, desired_size: u64) -> Result<(), VmError>; + fn vm_add_device(&mut self, device_cfg: DeviceConfig) -> Result>, VmError>; fn vm_add_user_device( diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 077c4ec7ee..6d465047b1 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -670,6 +670,10 @@ pub enum DeviceManagerError { /// Error adding fw_cfg to bus. #[error("Error adding fw_cfg to bus")] ErrorAddingFwCfgToBus(#[source] vm_device::BusError), + + /// Disk resizing failed. + #[error("Disk resize error")] + DiskResize(#[source] virtio_devices::block::Error), } pub type DeviceManagerResult = result::Result; @@ -4901,6 +4905,18 @@ impl DeviceManager { 0 } + pub fn resize_disk(&mut self, device_id: &str, new_size: u64) -> DeviceManagerResult<()> { + for dev in &self.block_devices { + let mut disk = dev.lock().unwrap(); + if disk.id() == device_id { + return disk + .resize(new_size) + .map_err(DeviceManagerError::DiskResize); + } + } + Err(DeviceManagerError::UnknownDeviceId(device_id.to_string())) + } + pub fn device_tree(&self) -> Arc> { self.device_tree.clone() } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 863e95ce82..6917e005e7 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1962,6 +1962,16 @@ impl RequestHandler for Vmm { } } + fn vm_resize_disk(&mut self, id: String, desired_size: u64) -> result::Result<(), VmError> { + self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; + + if let Some(ref mut vm) = self.vm { + return vm.resize_disk(&id, desired_size); + } + + Err(VmError::ResizeDisk) + } + fn vm_resize_zone(&mut self, id: String, desired_ram: u64) -> result::Result<(), VmError> { self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 721c490b8e..5364818855 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -246,6 +246,9 @@ pub enum Error { #[error("Failed resizing a memory zone")] ResizeZone, + #[error("Failed resizing a disk image")] + ResizeDisk, + #[error("Cannot activate virtio devices")] ActivateVirtioDevices(#[source] DeviceManagerError), @@ -1706,6 +1709,16 @@ impl Vm { Ok(()) } + pub fn resize_disk(&mut self, id: &str, desired_size: u64) -> Result<()> { + self.device_manager + .lock() + .unwrap() + .resize_disk(id, desired_size) + .map_err(Error::DeviceManager)?; + + Ok(()) + } + pub fn resize_zone(&mut self, id: &str, desired_memory: u64) -> Result<()> { let memory_config = &mut self.config.lock().unwrap().memory; From aac240d07663b50147cdee0d4856a8c6feca70b3 Mon Sep 17 00:00:00 2001 From: Thomas Prescher Date: Tue, 11 Nov 2025 09:41:48 +0100 Subject: [PATCH 0423/1893] block: raw_async: implement disk resizing Support for resize events for raw_async disks. On-behalf-of: SAP thomas.prescher@sap.com Signed-off-by: Thomas Prescher --- block/src/raw_async.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index f62c563d28..e36f249e82 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -55,6 +55,10 @@ impl DiskFile for RawFileDisk { } } + fn resize(&mut self, size: u64) -> DiskFileResult<()> { + self.file.set_len(size).map_err(DiskFileError::ResizeError) + } + fn fd(&mut self) -> BorrowedDiskFd<'_> { BorrowedDiskFd::new(self.file.as_raw_fd()) } From 5fb078305a5bf539c479ceb9747861468a4b506b Mon Sep 17 00:00:00 2001 From: Thomas Prescher Date: Mon, 10 Nov 2025 12:49:17 +0100 Subject: [PATCH 0424/1893] ch-remote: support live disk resizing Support disk resizing via ch-remote and REST api. On-behalf-of: SAP thomas.prescher@sap.com Signed-off-by: Thomas Prescher --- cloud-hypervisor/src/bin/ch-remote.rs | 41 ++++++++++++++++++++++++ vmm/src/api/http/http_endpoint.rs | 5 +-- vmm/src/api/http/mod.rs | 6 +++- vmm/src/api/mod.rs | 45 +++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 3 deletions(-) diff --git a/cloud-hypervisor/src/bin/ch-remote.rs b/cloud-hypervisor/src/bin/ch-remote.rs index aa95ce58ae..fd48ffab1f 100644 --- a/cloud-hypervisor/src/bin/ch-remote.rs +++ b/cloud-hypervisor/src/bin/ch-remote.rs @@ -65,6 +65,8 @@ enum Error { ReadingStdin(#[source] std::io::Error), #[error("Error reading from file")] ReadingFile(#[source] std::io::Error), + #[error("Invalid disk size")] + InvalidDiskSize(#[source] ByteSizedParseError), } enum TargetApi<'a> { @@ -320,6 +322,22 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu )?; simple_api_command(socket, "PUT", "resize", Some(&resize)).map_err(Error::HttpApiClient) } + Some("resize-disk") => { + let resize_disk = resize_disk_config( + matches + .subcommand_matches("resize-disk") + .unwrap() + .get_one::("disk") + .unwrap(), + matches + .subcommand_matches("resize-disk") + .unwrap() + .get_one::("size") + .unwrap(), + )?; + simple_api_command(socket, "PUT", "resize-disk", Some(&resize_disk)) + .map_err(Error::HttpApiClient) + } Some("resize-zone") => { let resize_zone = resize_zone_config( matches @@ -762,6 +780,15 @@ fn resize_config( Ok(serde_json::to_string(&resize).unwrap()) } +fn resize_disk_config(id: &str, size: &str) -> Result { + let resize_disk = vmm::api::VmResizeDiskData { + id: id.to_owned(), + desired_size: size.parse::().map_err(Error::InvalidDiskSize)?.0, + }; + + Ok(serde_json::to_string(&resize_disk).unwrap()) +} + fn resize_zone_config(id: &str, size: &str) -> Result { let resize_zone = vmm::api::VmResizeZoneData { id: id.to_owned(), @@ -1022,6 +1049,20 @@ fn get_cli_commands_sorted() -> Box<[Command]> { .help("New memory size in bytes (supports K/M/G suffix)") .num_args(1), ), + Command::new("resize-disk") + .about("Resize an attached disk") + .arg( + Arg::new("disk") + .long("disk") + .help("Disk identifier") + .num_args(1), + ) + .arg( + Arg::new("size") + .long("size") + .help("New disk size") + .num_args(1), + ), Command::new("resize-zone") .about("Resize a memory zone") .arg( diff --git a/vmm/src/api/http/http_endpoint.rs b/vmm/src/api/http/http_endpoint.rs index be8118e0e4..e463a20819 100644 --- a/vmm/src/api/http/http_endpoint.rs +++ b/vmm/src/api/http/http_endpoint.rs @@ -47,8 +47,8 @@ use crate::api::http::{EndpointHandler, HttpError, error_response}; use crate::api::{ AddDisk, ApiAction, ApiError, ApiRequest, NetConfig, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmConfig, VmCounters, VmDelete, VmNmi, VmPause, - VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, - VmResume, VmSendMigration, VmShutdown, VmSnapshot, + VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk, + VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot, }; use crate::config::RestoreConfig; use crate::cpu::Error as CpuError; @@ -424,6 +424,7 @@ vm_action_put_handler_body!(VmAddVdpa); vm_action_put_handler_body!(VmAddVsock); vm_action_put_handler_body!(VmAddUserDevice); vm_action_put_handler_body!(VmRemoveDevice); +vm_action_put_handler_body!(VmResizeDisk); vm_action_put_handler_body!(VmResizeZone); vm_action_put_handler_body!(VmSnapshot); vm_action_put_handler_body!(VmReceiveMigration); diff --git a/vmm/src/api/http/mod.rs b/vmm/src/api/http/mod.rs index 9ba679a808..2aa52e8e37 100644 --- a/vmm/src/api/http/mod.rs +++ b/vmm/src/api/http/mod.rs @@ -30,7 +30,7 @@ use crate::api::VmCoredump; use crate::api::{ AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmNmi, VmPause, VmPowerButton, VmReboot, - VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume, + VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk, VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot, }; use crate::landlock::Landlock; @@ -251,6 +251,10 @@ pub static HTTP_ROUTES: LazyLock = LazyLock::new(|| { endpoint!("/vm.resize"), Box::new(VmActionHandler::new(&VmResize)), ); + r.routes.insert( + endpoint!("/vm.resize-disk"), + Box::new(VmActionHandler::new(&VmResizeDisk)), + ); r.routes.insert( endpoint!("/vm.resize-zone"), Box::new(VmActionHandler::new(&VmResizeZone)), diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 3b5fe83dd2..12ca6b9877 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -134,6 +134,10 @@ pub enum ApiError { #[error("The VM could not be resized")] VmResize(#[source] VmError), + /// The disk could not be resized. + #[error("The disk could not be resized")] + VmResizeDisk(#[source] VmError), + /// The memory zone could not be resized. #[error("The memory zone could not be resized")] VmResizeZone(#[source] VmError), @@ -223,6 +227,12 @@ pub struct VmResizeData { pub desired_balloon: Option, } +#[derive(Clone, Deserialize, Serialize, Default, Debug)] +pub struct VmResizeDiskData { + pub id: String, + pub desired_size: u64, +} + #[derive(Clone, Deserialize, Serialize, Default, Debug)] pub struct VmResizeZoneData { pub id: String, @@ -1139,6 +1149,41 @@ impl ApiAction for VmResize { } } +pub struct VmResizeDisk; + +impl ApiAction for VmResizeDisk { + type RequestBody = VmResizeDiskData; + type ResponseBody = Option; + + fn request( + &self, + resize_disk_data: Self::RequestBody, + response_sender: Sender, + ) -> ApiRequest { + Box::new(move |vmm| { + let response = vmm + .vm_resize_disk(resize_disk_data.id, resize_disk_data.desired_size) + .map_err(ApiError::VmResizeDisk) + .map(|_| ApiResponsePayload::Empty); + + response_sender + .send(response) + .map_err(VmmError::ApiResponseSend)?; + + Ok(false) + }) + } + + fn send( + &self, + api_evt: EventFd, + api_sender: Sender, + data: Self::RequestBody, + ) -> ApiResult { + get_response_body(self, api_evt, api_sender, data) + } +} + pub struct VmResizeZone; impl ApiAction for VmResizeZone { From b15459106cd45347c02aa5b411fbe340fd1726de Mon Sep 17 00:00:00 2001 From: Thomas Prescher Date: Thu, 13 Nov 2025 19:36:43 +0100 Subject: [PATCH 0425/1893] docs: update vm.resize-disk On-behalf-of: SAP thomas.prescher@sap.com Signed-off-by: Thomas Prescher --- docs/api.md | 1 + vmm/src/api/openapi/cloud-hypervisor.yaml | 27 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/docs/api.md b/docs/api.md index 8fc951c727..95118b4d10 100644 --- a/docs/api.md +++ b/docs/api.md @@ -86,6 +86,7 @@ The Cloud Hypervisor API exposes the following actions through its endpoints: | Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted | | Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted | | Add/remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted | +| Resize a disk attached to the VM | `/vm.resize-disk` | `/schemas/VmResizeDisk` | N/A | The VM is created | | Add/remove memory from a zone | `/vm.resize-zone` | `/schemas/VmResizeZone` | N/A | The VM is booted | | Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created | | Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | `/schemas/PciDeviceInfo` | The VM is booted | diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index d87573c558..1fa3d9b517 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -163,6 +163,22 @@ paths: 429: description: The VM instance could not be resized because a cpu removal is still pending. + /vm.resize-disk: + put: + summary: Resize a disk + requestBody: + description: Resizes a disk attached to the VM + content: + application/json: + schema: + $ref: "#/components/schemas/VmResizeDisk" + required: true + responses: + 204: + description: The disk was successfully resized. + 500: + description: The disk could not be resized. + /vm.resize-zone: put: summary: Resize a memory zone @@ -1203,6 +1219,17 @@ components: type: integer format: int64 + VmResizeDisk: + type: object + properties: + id: + description: disk identifier + type: string + desired_size: + description: desired disk size in bytes + type: integer + format: int64 + VmResizeZone: type: object properties: From 736813f90c732f72116e442e23b42a22ed8063c9 Mon Sep 17 00:00:00 2001 From: Thomas Prescher Date: Mon, 8 Dec 2025 20:38:34 +0100 Subject: [PATCH 0426/1893] tests: add live disk resize test This test verifies that we can grow and shrink disk images during runtime. On-behalf-of: SAP thomas.prescher@sap.com Signed-off-by: Thomas Prescher --- cloud-hypervisor/tests/integration.rs | 124 ++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 04638f79d8..2164437954 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -717,6 +717,18 @@ fn resize_zone_command(api_socket: &str, id: &str, desired_size: &str) -> bool { cmd.status().expect("Failed to launch ch-remote").success() } +fn resize_disk_command(api_socket: &str, id: &str, desired_size: &str) -> bool { + let mut cmd = Command::new(clh_command("ch-remote")); + cmd.args([ + &format!("--api-socket={api_socket}"), + "resize-disk", + &format!("--disk={id}"), + &format!("--size={desired_size}"), + ]); + + cmd.status().expect("Failed to launch ch-remote").success() +} + // setup OVS-DPDK bridge and ports fn setup_ovs_dpdk() { // setup OVS-DPDK @@ -5645,6 +5657,118 @@ mod common_parallel { _test_disk_hotplug(true); } + #[test] + fn test_disk_resize() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = edk2_path(); + + let api_socket = temp_api_path(&guest.tmp_dir); + + // Create a disk image that we can write to + assert!( + exec_host_command_output(&format!( + "sudo dd if=/dev/zero of=/tmp/resize.img bs=1M count=16" + )) + .status + .success() + ); + + let mut cmd = GuestCommand::new(&guest); + + cmd.args(["--api-socket", &api_socket]) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot(None).unwrap(); + + // Add the disk to the VM + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-disk", + Some("path=/tmp/resize.img,id=test0"), + ); + + assert!(cmd_success); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); + + // Check that /dev/vdc exists and the block size is 16M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 16M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + // And check the block device can be written to. + guest + .ssh_command("sudo dd if=/dev/zero of=/dev/vdc bs=1M count=16") + .unwrap(); + + // Resize disk to 32M + let resize_up_success = + resize_disk_command(&api_socket, "test0", "33554432" /* 32M */); + assert!(resize_up_success); + + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 32M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // And check all blocks can be written to + guest + .ssh_command("sudo dd if=/dev/zero of=/dev/vdc bs=1M count=32") + .unwrap(); + + // Resize down to original size + let resize_down_success = + resize_disk_command(&api_socket, "test0", "16777216" /* 16M */); + assert!(resize_down_success); + + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 16M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // And check all blocks can be written to, again + guest + .ssh_command("sudo dd if=/dev/zero of=/dev/vdc bs=1M count=16") + .unwrap(); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } + fn create_loop_device(backing_file_path: &str, block_size: u32, num_retries: usize) -> String { const LOOP_CONFIGURE: u64 = 0x4c0a; const LOOP_CTL_GET_FREE: u64 = 0x4c82; From 9a24680abdfedf014e496bced49e11b26dda582c Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Wed, 17 Dec 2025 01:47:58 +0000 Subject: [PATCH 0427/1893] build: Release v50.0 Signed-off-by: Bo Chen --- Cargo.lock | 2 +- cloud-hypervisor/Cargo.toml | 2 +- release-notes.md | 272 ++++++++++++++++++++++++------------ 3 files changed, 186 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4919fa77dc..62bbf709fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,7 +417,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cloud-hypervisor" -version = "49.0.0" +version = "50.0.0" dependencies = [ "anyhow", "api_client", diff --git a/cloud-hypervisor/Cargo.toml b/cloud-hypervisor/Cargo.toml index 579bae8a27..426a522635 100644 --- a/cloud-hypervisor/Cargo.toml +++ b/cloud-hypervisor/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" license = "Apache-2.0 AND BSD-3-Clause" name = "cloud-hypervisor" -version = "49.0.0" +version = "50.0.0" # Minimum buildable version: # Keep in sync with version in .github/workflows/build.yaml # Policy on MSRV (see #4318): diff --git a/release-notes.md b/release-notes.md index d839d79c4f..82df4ed948 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,9 +1,19 @@ -- [v49.0](#v490) - - [MSHV Support Improvements](#mshv-support-improvements) +- [v50.0](#v500) + - [Configurable Nested Virtualization Option on x86_64](#configurable-nested-virtualization-option-on-x86_64) + - [Compression Support for QCOW2](#compression-support-for-qcow2) + - [Notable Performance Improvements](#notable-performance-improvements) + - [Developer Experience Improvements](#developer-experience-improvements) + - [Improved File-level Locking Support](#improved-file-level-locking-support) + - [Live Disk Resizing Support for Raw Images](#live-disk-resizing-support-for-raw-images) - [Logging Improvements](#logging-improvements) - - [Removed Default IP and Mask for `virtio-net` Devices](#removed-default-ip-and-mask-for-virtio-net-devices) - [Notable Bug Fixes](#notable-bug-fixes) - [Contributors](#contributors) +- [v49.0](#v490) + - [MSHV Support Improvements](#mshv-support-improvements) + - [Logging Improvements](#logging-improvements-1) + - [Removed Default IP and Mask for `virtio-net` Devices](#removed-default-ip-and-mask-for-virtio-net-devices) + - [Notable Bug Fixes](#notable-bug-fixes-1) + - [Contributors](#contributors-1) - [v48.0](#v480) - [Experimental `fw_cfg` Device Support](#experimental-fw_cfg-device-support) - [Experimental `ivshmem` Device Support](#experimental-ivshmem-device-support) @@ -14,62 +24,62 @@ - [Updated Documentation on Windows Guest Support](#updated-documentation-on-windows-guest-support) - [Policy on AI Generated Code](#policy-on-ai-generated-code) - [Removed SGX Support](#removed-sgx-support) - - [Notable Bug Fixes](#notable-bug-fixes-1) - - [Contributors](#contributors-1) + - [Notable Bug Fixes](#notable-bug-fixes-2) + - [Contributors](#contributors-2) - [v47.0](#v470) - [Block Device Error Reporting to the Guest](#block-device-error-reporting-to-the-guest) - [Nice Error Messages on Exit](#nice-error-messages-on-exit) - [Alphabetically Sorted CLI Options for ch-remote](#alphabetically-sorted-cli-options-for-ch-remote) - - [Notable Bug Fixes](#notable-bug-fixes-2) + - [Notable Bug Fixes](#notable-bug-fixes-3) - [Deprecations](#deprecations) - - [Contributors](#contributors-2) + - [Contributors](#contributors-3) - [v46.0](#v460) - [File-level Locking Support with `--disk`](#file-level-locking-support-with---disk) - [Improved Error Reporting with VM Resizing](#improved-error-reporting-with-vm-resizing) - [IPv6 Address Support with `--net`](#ipv6-address-support-with---net) - [Experimental AArch64 Support with the MSHV Hypervisor](#experimental-aarch64-support-with-the-mshv-hypervisor) - [Deprecated SGX Support](#deprecated-sgx-support) - - [Notable Bug Fixes](#notable-bug-fixes-3) - - [Contributors](#contributors-3) + - [Notable Bug Fixes](#notable-bug-fixes-4) + - [Contributors](#contributors-4) - [v45.0](#v450) - [Experimental `riscv64` Architecture Support](#experimental-riscv64-architecture-support) - [Alphabetically Sorted CLI Options](#alphabetically-sorted-cli-options) - [Improved Downtime of VM Live Migration](#improved-downtime-of-vm-live-migration) - - [Notable Bug Fixes](#notable-bug-fixes-4) - - [Contributors](#contributors-4) -- [v44.0](#v440) - - [Configurable `virtio-iommu` Address Width](#configurable-virtio-iommu-address-width) - - [Notable Performance Improvements](#notable-performance-improvements) - - [New Fuzzers](#new-fuzzers) - [Notable Bug Fixes](#notable-bug-fixes-5) - [Contributors](#contributors-5) -- [v43.0](#v430) - - [Live Migration over TCP Connections](#live-migration-over-tcp-connections) +- [v44.0](#v440) + - [Configurable `virtio-iommu` Address Width](#configurable-virtio-iommu-address-width) - [Notable Performance Improvements](#notable-performance-improvements-1) + - [New Fuzzers](#new-fuzzers) - [Notable Bug Fixes](#notable-bug-fixes-6) - [Contributors](#contributors-6) +- [v43.0](#v430) + - [Live Migration over TCP Connections](#live-migration-over-tcp-connections) + - [Notable Performance Improvements](#notable-performance-improvements-2) + - [Notable Bug Fixes](#notable-bug-fixes-7) + - [Contributors](#contributors-7) - [v42.0](#v420) - [SVE/SVE2 Support on AArch64](#svesve2-support-on-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-7) + - [Notable Bug Fixes](#notable-bug-fixes-8) - [Sponsorships](#sponsorships) - - [Contributors](#contributors-7) + - [Contributors](#contributors-8) - [v41.0](#v410) - [Experimental "Pvmemcontrol" Support](#experimental-pvmemcontrol-support) - [Sandboxing With Landlock Support](#sandboxing-with-landlock-support) - - [Notable Performance Improvements](#notable-performance-improvements-2) - - [Notable Bug Fixes](#notable-bug-fixes-8) - - [Contributors](#contributors-8) -- [v40.0](#v400) - - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) + - [Notable Performance Improvements](#notable-performance-improvements-3) - [Notable Bug Fixes](#notable-bug-fixes-9) - [Contributors](#contributors-9) +- [v40.0](#v400) + - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) + - [Notable Bug Fixes](#notable-bug-fixes-10) + - [Contributors](#contributors-10) - [v39.0](#v390) - [Variable Sizing of PCI Apertures for Segments](#variable-sizing-of-pci-apertures-for-segments) - [Direct Booting with bzImages](#direct-booting-with-bzimages) - [Support for NVIDIA GPUDirect P2P Support](#support-for-nvidia-gpudirect-p2p-support) - [Guest NMI Injection Support](#guest-nmi-injection-support) - - [Notable Bug Fixes](#notable-bug-fixes-10) - - [Contributors](#contributors-10) + - [Notable Bug Fixes](#notable-bug-fixes-11) + - [Contributors](#contributors-11) - [v38.0](#v380) - [Group Rate Limiter on Block Devices](#group-rate-limiter-on-block-devices) - [CPU Pinning Support for Block Device Worker Thread](#cpu-pinning-support-for-block-device-worker-thread) @@ -77,16 +87,16 @@ - [New 'debug-console' Device](#new-debug-console-device) - [Improved VFIO Device Support](#improved-vfio-device-support) - [Extended CPU Affinity Support](#extended-cpu-affinity-support) - - [Notable Bug Fixes](#notable-bug-fixes-11) - - [Contributors](#contributors-11) + - [Notable Bug Fixes](#notable-bug-fixes-12) + - [Contributors](#contributors-12) - [v37.0](#v370) - [Long Term Support (LTS) Release](#long-term-support-lts-release) - [Multiple PCI segments Support for 32-bit VFIO devices](#multiple-pci-segments-support-for-32-bit-vfio-devices) - [Configurable Named TAP Devices](#configurable-named-tap-devices) - [TTY Output from Both Serial Device and Virtio Console](#tty-output-from-both-serial-device-and-virtio-console) - [Faster VM Restoration from Snapshots](#faster-vm-restoration-from-snapshots) - - [Notable Bug Fixes](#notable-bug-fixes-12) - - [Contributors](#contributors-12) + - [Notable Bug Fixes](#notable-bug-fixes-13) + - [Contributors](#contributors-13) - [v36.0](#v360) - [Command Line Changes](#command-line-changes) - [Enabled Features Reported via API Endpoint and CLI](#enabled-features-reported-via-api-endpoint-and-cli) @@ -95,31 +105,31 @@ - [Unix Socket Backend for Serial Port](#unix-socket-backend-for-serial-port) - [AIO Backend for Block Devices](#aio-backend-for-block-devices) - [Documentation Improvements](#documentation-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-13) - - [Contributors](#contributors-13) + - [Notable Bug Fixes](#notable-bug-fixes-14) + - [Contributors](#contributors-14) - [v35.0](#v350) - [`virtio-vsock` Support for Linux Guest Kernel v6.3+](#virtio-vsock-support-for-linux-guest-kernel-v63) - [User Specified Serial Number for `virtio-block`](#user-specified-serial-number-for-virtio-block) - [vCPU TSC Frequency Included in Migration State](#vcpu-tsc-frequency-included-in-migration-state) - - [Notable Bug Fixes](#notable-bug-fixes-14) - - [Contributors](#contributors-14) + - [Notable Bug Fixes](#notable-bug-fixes-15) + - [Contributors](#contributors-15) - [v34.0](#v340) - [Paravirtualised Panic Device Support](#paravirtualised-panic-device-support) - [Improvements to VM Core Dump](#improvements-to-vm-core-dump) - [QCOW2 Support for Backing Files](#qcow2-support-for-backing-files) - [Minimum Host Kernel Bump](#minimum-host-kernel-bump) - - [Notable Bug Fixes](#notable-bug-fixes-15) - - [Contributors](#contributors-15) + - [Notable Bug Fixes](#notable-bug-fixes-16) + - [Contributors](#contributors-16) - [v33.0](#v330) - [D-Bus based API](#d-bus-based-api) - [Expose Host CPU Cache Details for AArch64](#expose-host-cpu-cache-details-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-16) - - [Contributors](#contributors-16) + - [Notable Bug Fixes](#notable-bug-fixes-17) + - [Contributors](#contributors-17) - [v32.0](#v320) - [Increased PCI Segment Limit](#increased-pci-segment-limit) - [API Changes](#api-changes) - - [Notable Bug Fixes](#notable-bug-fixes-17) - - [Contributors](#contributors-17) + - [Notable Bug Fixes](#notable-bug-fixes-18) + - [Contributors](#contributors-18) - [v31.1](#v311) - [v31.0](#v310) - [Update to Latest `acpi_tables`](#update-to-latest-acpi_tables) @@ -127,15 +137,15 @@ - [Improvements on Console `SIGWINCH` Handler](#improvements-on-console-sigwinch-handler) - [Remove Directory Support from `MemoryZoneConfig::file`](#remove-directory-support-from-memoryzoneconfigfile) - [Documentation Improvements](#documentation-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-18) - - [Contributors](#contributors-18) + - [Notable Bug Fixes](#notable-bug-fixes-19) + - [Contributors](#contributors-19) - [v30.0](#v300) - [Command Line Changes for Reduced Binary Size](#command-line-changes-for-reduced-binary-size) - [Basic vfio-user Server Support](#basic-vfio-user-server-support) - [Heap Profiling Support](#heap-profiling-support) - [Documentation Improvements](#documentation-improvements-2) - - [Notable Bug Fixes](#notable-bug-fixes-19) - - [Contributors](#contributors-19) + - [Notable Bug Fixes](#notable-bug-fixes-20) + - [Contributors](#contributors-20) - [v28.2](#v282) - [v29.0](#v290) - [Release Binary Supports Both MSHV and KVM](#release-binary-supports-both-mshv-and-kvm) @@ -145,10 +155,10 @@ - [`AArch64` Documentation Integration](#aarch64-documentation-integration) - [`virtio-block` Counters Enhancement](#virtio-block-counters-enhancement) - [TCP Offload Control](#tcp-offload-control) - - [Notable Bug Fixes](#notable-bug-fixes-20) + - [Notable Bug Fixes](#notable-bug-fixes-21) - [Removals](#removals) - [Deprecations](#deprecations-1) - - [Contributors](#contributors-20) + - [Contributors](#contributors-21) - [v28.1](#v281) - [v28.0](#v280) - [Community Engagement (Reminder)](#community-engagement-reminder) @@ -156,9 +166,9 @@ - [Virtualised TPM Support](#virtualised-tpm-support) - [Transparent Huge Page Support](#transparent-huge-page-support) - [README Quick Start Improved](#readme-quick-start-improved) - - [Notable Bug Fixes](#notable-bug-fixes-21) + - [Notable Bug Fixes](#notable-bug-fixes-22) - [Removals](#removals-1) - - [Contributors](#contributors-21) + - [Contributors](#contributors-22) - [v27.0](#v270) - [Community Engagement](#community-engagement) - [Prebuilt Packages](#prebuilt-packages) @@ -167,41 +177,41 @@ - [Simplified Build Feature Flags](#simplified-build-feature-flags) - [Asynchronous Kernel Loading](#asynchronous-kernel-loading) - [GDB Support for AArch64](#gdb-support-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-22) + - [Notable Bug Fixes](#notable-bug-fixes-23) - [Deprecations](#deprecations-2) - - [Contributors](#contributors-22) + - [Contributors](#contributors-23) - [v26.0](#v260) - [SMBIOS Improvements via `--platform`](#smbios-improvements-via---platform) - [Unified Binary MSHV and KVM Support](#unified-binary-mshv-and-kvm-support) - - [Notable Bug Fixes](#notable-bug-fixes-23) + - [Notable Bug Fixes](#notable-bug-fixes-24) - [Deprecations](#deprecations-3) - [Removals](#removals-2) - - [Contributors](#contributors-23) + - [Contributors](#contributors-24) - [v25.0](#v250) - [`ch-remote` Improvements](#ch-remote-improvements-1) - [VM "Coredump" Support](#vm-coredump-support) - - [Notable Bug Fixes](#notable-bug-fixes-24) + - [Notable Bug Fixes](#notable-bug-fixes-25) - [Removals](#removals-3) - - [Contributors](#contributors-24) + - [Contributors](#contributors-25) - [v24.0](#v240) - [Bypass Mode for `virtio-iommu`](#bypass-mode-for-virtio-iommu) - [Ensure Identifiers Uniqueness](#ensure-identifiers-uniqueness) - [Sparse Mmap support](#sparse-mmap-support) - [Expose Platform Serial Number](#expose-platform-serial-number) - - [Notable Bug Fixes](#notable-bug-fixes-25) + - [Notable Bug Fixes](#notable-bug-fixes-26) - [Notable Improvements](#notable-improvements) - [Deprecations](#deprecations-4) - [New on the Website](#new-on-the-website) - - [Contributors](#contributors-25) + - [Contributors](#contributors-26) - [v23.1](#v231) - [v23.0](#v230) - [vDPA Support](#vdpa-support) - [Updated OS Support list](#updated-os-support-list) - [`AArch64` Memory Map Improvements](#aarch64-memory-map-improvements) - [`AMX` Support](#amx-support) - - [Notable Bug Fixes](#notable-bug-fixes-26) + - [Notable Bug Fixes](#notable-bug-fixes-27) - [Deprecations](#deprecations-5) - - [Contributors](#contributors-26) + - [Contributors](#contributors-27) - [v22.1](#v221) - [v22.0](#v220) - [GDB Debug Stub Support](#gdb-debug-stub-support) @@ -212,13 +222,13 @@ - [PMU Support for AArch64](#pmu-support-for-aarch64) - [Documentation Under CC-BY-4.0 License](#documentation-under-cc-by-40-license) - [Deprecation of "Classic" `virtiofsd`](#deprecation-of-classic-virtiofsd) - - [Notable Bug Fixes](#notable-bug-fixes-27) - - [Contributors](#contributors-27) + - [Notable Bug Fixes](#notable-bug-fixes-28) + - [Contributors](#contributors-28) - [v21.0](#v210) - [Efficient Local Live Migration (for Live Upgrade)](#efficient-local-live-migration-for-live-upgrade) - [Recommended Kernel is Now 5.15](#recommended-kernel-is-now-515) - - [Notable Bug fixes](#notable-bug-fixes-28) - - [Contributors](#contributors-28) + - [Notable Bug fixes](#notable-bug-fixes-29) + - [Contributors](#contributors-29) - [v20.2](#v202) - [v20.1](#v201) - [v20.0](#v200) @@ -227,8 +237,8 @@ - [Improved VFIO support](#improved-vfio-support) - [Safer code](#safer-code) - [Extended documentation](#extended-documentation) - - [Notable bug fixes](#notable-bug-fixes-29) - - [Contributors](#contributors-29) + - [Notable bug fixes](#notable-bug-fixes-30) + - [Contributors](#contributors-30) - [v19.0](#v190) - [Improved PTY handling for serial and `virtio-console`](#improved-pty-handling-for-serial-and-virtio-console) - [PCI boot time optimisations](#pci-boot-time-optimisations) @@ -236,8 +246,8 @@ - [Live migration enhancements](#live-migration-enhancements) - [`virtio-mem` support with `vfio-user`](#virtio-mem-support-with-vfio-user) - [AArch64 for `virtio-iommu`](#aarch64-for-virtio-iommu) - - [Notable bug fixes](#notable-bug-fixes-30) - - [Contributors](#contributors-30) + - [Notable bug fixes](#notable-bug-fixes-31) + - [Contributors](#contributors-31) - [v18.0](#v180) - [Experimental User Device (`vfio-user`) support](#experimental-user-device-vfio-user-support) - [Migration support for `vhost-user` devices](#migration-support-for-vhost-user-devices) @@ -247,23 +257,23 @@ - [Live migration on MSHV hypervisor](#live-migration-on-mshv-hypervisor) - [AArch64 CPU topology support](#aarch64-cpu-topology-support) - [Power button support on AArch64](#power-button-support-on-aarch64) - - [Notable bug fixes](#notable-bug-fixes-31) - - [Contributors](#contributors-31) + - [Notable bug fixes](#notable-bug-fixes-32) + - [Contributors](#contributors-32) - [v17.0](#v170) - [ARM64 NUMA support using ACPI](#arm64-numa-support-using-acpi) - [`Seccomp` support for MSHV backend](#seccomp-support-for-mshv-backend) - [Hotplug of `macvtap` devices](#hotplug-of-macvtap-devices) - [Improved SGX support](#improved-sgx-support) - [Inflight tracking for `vhost-user` devices](#inflight-tracking-for-vhost-user-devices) - - [Notable bug fixes](#notable-bug-fixes-32) - - [Contributors](#contributors-32) + - [Notable bug fixes](#notable-bug-fixes-33) + - [Contributors](#contributors-33) - [v16.0](#v160) - [Improved live migration support](#improved-live-migration-support) - [Improved `vhost-user` support](#improved-vhost-user-support) - [ARM64 ACPI and UEFI support](#arm64-acpi-and-uefi-support) - - [Notable bug fixes](#notable-bug-fixes-33) + - [Notable bug fixes](#notable-bug-fixes-34) - [Removed functionality](#removed-functionality) - - [Contributors](#contributors-33) + - [Contributors](#contributors-34) - [v15.0](#v150) - [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees) - [Network device rate limiting](#network-device-rate-limiting) @@ -271,7 +281,7 @@ - [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter) - [Bug fixes](#bug-fixes) - [Deprecations](#deprecations-6) - - [Contributors](#contributors-34) + - [Contributors](#contributors-35) - [v0.14.1](#v0141) - [v0.14.0](#v0140) - [Structured event monitoring](#structured-event-monitoring) @@ -281,7 +291,7 @@ - [PTY control for serial and `virtio-console`](#pty-control-for-serial-and-virtio-console) - [Block device rate limiting](#block-device-rate-limiting) - [Deprecations](#deprecations-7) - - [Contributors](#contributors-35) + - [Contributors](#contributors-36) - [v0.13.0](#v0130) - [Wider VFIO device support](#wider-vfio-device-support) - [Improved huge page support](#improved-huge-page-support) @@ -289,13 +299,13 @@ - [VHD disk image support](#vhd-disk-image-support) - [Improved Virtio device threading](#improved-virtio-device-threading) - [Clean shutdown support via synthetic power button](#clean-shutdown-support-via-synthetic-power-button) - - [Contributors](#contributors-36) + - [Contributors](#contributors-37) - [v0.12.0](#v0120) - [ARM64 enhancements](#arm64-enhancements) - [Removal of `vhost-user-net` and `vhost-user-block` self spawning](#removal-of-vhost-user-net-and-vhost-user-block-self-spawning) - [Migration of `vhost-user-fs` backend](#migration-of-vhost-user-fs-backend) - [Enhanced "info" API](#enhanced-info-api) - - [Contributors](#contributors-37) + - [Contributors](#contributors-38) - [v0.11.0](#v0110) - [`io_uring` support by default for `virtio-block`](#io_uring-support-by-default-for-virtio-block) - [Windows Guest Support](#windows-guest-support) @@ -307,15 +317,15 @@ - [Default Log Level Changed](#default-log-level-changed) - [New `--balloon` Parameter Added](#new---balloon-parameter-added) - [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support) - - [Notable Bug Fixes](#notable-bug-fixes-34) - - [Contributors](#contributors-38) + - [Notable Bug Fixes](#notable-bug-fixes-35) + - [Contributors](#contributors-39) - [v0.10.0](#v0100) - [`virtio-block` Support for Multiple Descriptors](#virtio-block-support-for-multiple-descriptors) - [Memory Zones](#memory-zones) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements) - [Preliminary KVM HyperV Emulation Control](#preliminary-kvm-hyperv-emulation-control) - - [Notable Bug Fixes](#notable-bug-fixes-35) - - [Contributors](#contributors-39) + - [Notable Bug Fixes](#notable-bug-fixes-36) + - [Contributors](#contributors-40) - [v0.9.0](#v090) - [`io_uring` Based Block Device Support](#io_uring-based-block-device-support) - [Block and Network Device Statistics](#block-and-network-device-statistics) @@ -328,17 +338,17 @@ - [Enhancements to ARM64 Support](#enhancements-to-arm64-support) - [Intel SGX Support](#intel-sgx-support) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-36) - - [Contributors](#contributors-40) + - [Notable Bug Fixes](#notable-bug-fixes-37) + - [Contributors](#contributors-41) - [v0.8.0](#v080) - [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support) - [Experimental ARM64 Support](#experimental-arm64-support) - [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests) - [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices) - [`vhost_user_fs` Improvements](#vhost_user_fs-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-37) + - [Notable Bug Fixes](#notable-bug-fixes-38) - [Command Line and API Changes](#command-line-and-api-changes) - - [Contributors](#contributors-41) + - [Contributors](#contributors-42) - [v0.7.0](#v070) - [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug) - [Alternative `libc` Support](#alternative-libc-support) @@ -348,14 +358,14 @@ - [`Seccomp` Sandboxing](#seccomp-sandboxing) - [Updated Distribution Support](#updated-distribution-support) - [Command Line and API Changes](#command-line-and-api-changes-1) - - [Contributors](#contributors-42) + - [Contributors](#contributors-43) - [v0.6.0](#v060) - [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug) - [Shared Filesystem Improvements](#shared-filesystem-improvements) - [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading) - [Command Line Interface](#command-line-interface) - [PVH Boot](#pvh-boot) - - [Contributors](#contributors-43) + - [Contributors](#contributors-44) - [v0.5.1](#v051) - [v0.5.0](#v050) - [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing) @@ -363,7 +373,7 @@ - [New Interrupt Management Framework](#new-interrupt-management-framework) - [Development Tools](#development-tools) - [Kata Containers Integration](#kata-containers-integration) - - [Contributors](#contributors-44) + - [Contributors](#contributors-45) - [v0.4.0](#v040) - [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition) - [Programmatic firmware tables generation](#programmatic-firmware-tables-generation) @@ -372,7 +382,7 @@ - [Userspace IOAPIC by default](#userspace-ioapic-by-default) - [PCI BAR reprogramming](#pci-bar-reprogramming) - [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization) - - [Contributors](#contributors-45) + - [Contributors](#contributors-46) - [v0.3.0](#v030) - [Block device offloading](#block-device-offloading) - [Network device backend](#network-device-backend) @@ -399,6 +409,92 @@ - [Unit testing](#unit-testing) - [Integration tests parallelization](#integration-tests-parallelization) +# v50.0 + +This release has been tracked in [v50.0 +group](https://github.com/orgs/cloud-hypervisor/projects/6/views/6?filterQuery=release%3A%22Release+50%22) +of our [roadmap project](https://github.com/orgs/cloud-hypervisor/projects/6/). + +### Configurable Nested Virtualization Option on x86_64 + +The `nested=on|off` option has been added to `--cpu` to allow users +to configure nested virtualization support in the guest on x86_64 +hosts (for both KVM and MSHV). The default value is `on` to maintain +consistency with existing behavior. (#7408) + +### Compression Support for QCOW2 + +QCOW2 support has been extended to handle compression clusters based on +zlib and zstd. (#7462) + +### Notable Performance Improvements + +Performance of live migration has been improved via an optimized +implementation of dirty bitmap maintenance. (#7468) + +### Live Disk Resizing Support for Raw Images + +The `/vm.resize-disk` API has been introduced to allow users to resize block +devices backed by raw images while a guest is running. (#7476) + +### Developer Experience Improvements + +Significant improvements have been made to developer experience and +productivity. These include a simplified root manifest, codified and +tightened Clippy lints, and streamlined workflows for `cargo clippy` and +`cargo test`. (#7489) + +### Improved File-level Locking Support + +Block devices now use byte-range advisory locks instead of whole-file +locks. While both approaches prevent multiple Cloud Hypervisor instances +from simultaneously accessing the same disk image with write +permissions, byte-range locks provide better compatibility with network +storage backends. (#7494) + +### Logging Improvements + +Logs now include event information generated by the event-monitor +module. (#7512) + +### Notable Bug Fixes + +* Fix several issues around CPUID in the guest (#7485, #7495, #7508) +* Fix snapshot/restore for Windows Guest (#7492) +* Respect queue size in block performance tests (#7515) +* Fix several Serial Manager issues (#7502) +* Fix several seccomp violation issues (#7477, #7497, #7518) +* Fix various issues around block and qcow (#7526, #7528, #7537, #7546, + #7549) +* Retrieve MSRs list correctly on MSHV (#7543) +* Fix live migration (and snapshot/restore) with AMX state (#7534) + +### Contributors + +Many thanks to everyone who has contributed to our release: + +* Anatol Belski +* Anirudh Rayabharam +* Bo Chen +* Changyuan Lyu +* Chengyu Fu +* Connor Brewster +* Demi Marie Obenour +* Eugene Korenevsky +* Julian Stecklina +* Matt Moriarity +* Muminul Islam +* Oliver Anderson +* Pascal Scholz +* Philipp Schuster +* Praveen K Paladugu +* Rob Bradford +* Sebastien Boeuf +* Songqian Li +* Stefan Nürnberger +* Thomas Prescher +* Wei Liu + # v49.0 This release has been tracked in [v49.0 From 2278a5236a75ea3d8dbdf5a6d5223ced4dfec7db Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 19 Dec 2025 23:10:41 -0500 Subject: [PATCH 0428/1893] 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 --- cloud-hypervisor/src/main.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index d08293b6e4..3abc382f35 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -218,18 +218,6 @@ fn get_cli_options_sorted( ) .default_value(default_vcpus) .group("vm-config"), - #[cfg(target_arch = "x86_64")] - Arg::new("debug-console") - .long("debug-console") - .help("Debug console: off|pty|tty|file=,iobase=") - .default_value("off,iobase=0xe9") - .group("vm-config"), - #[cfg(feature = "dbus_api")] - Arg::new("dbus-service-name") - .long("dbus-service-name") - .help("Well known name of the device") - .num_args(1) - .group("vmm-config"), #[cfg(feature = "dbus_api")] Arg::new("dbus-object-path") .long("dbus-object-path") @@ -237,12 +225,24 @@ fn get_cli_options_sorted( .num_args(1) .group("vmm-config"), #[cfg(feature = "dbus_api")] + Arg::new("dbus-service-name") + .long("dbus-service-name") + .help("Well known name of the device") + .num_args(1) + .group("vmm-config"), + #[cfg(feature = "dbus_api")] Arg::new("dbus-system-bus") .long("dbus-system-bus") .action(ArgAction::SetTrue) .help("Use the system bus instead of a session bus") .num_args(0) .group("vmm-config"), + #[cfg(target_arch = "x86_64")] + Arg::new("debug-console") + .long("debug-console") + .help("Debug console: off|pty|tty|file=,iobase=") + .default_value("off,iobase=0xe9") + .group("vm-config"), Arg::new("device") .long("device") .help(DeviceConfig::SYNTAX) From d741cd53de1a45e86ad4d6df8111fff3267528e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:31:59 +0000 Subject: [PATCH 0429/1893] 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](https://github.com/microsoft/igvm/compare/5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8...81dd0e49cd3caf9e894e376969e33f2584e6f8ba) Updates `igvm_defs` from `5d2b5a5` to `81dd0e4` - [Release notes](https://github.com/microsoft/igvm/releases) - [Commits](https://github.com/microsoft/igvm/compare/5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8...81dd0e49cd3caf9e894e376969e33f2584e6f8ba) 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] --- Cargo.lock | 56 ++++++++++++++++++++++++------------------------- Cargo.toml | 2 +- fuzz/Cargo.lock | 32 ++++++++++++++-------------- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62bbf709fe..71b7cee467 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,9 +181,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener", "event-listener-strategy", @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -365,9 +365,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -994,7 +994,7 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "igvm" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8" +source = "git+https://github.com/microsoft/igvm?branch=main#81dd0e49cd3caf9e894e376969e33f2584e6f8ba" dependencies = [ "bitfield-struct 0.10.1", "crc32fast", @@ -1011,7 +1011,7 @@ dependencies = [ [[package]] name = "igvm_defs" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#5d2b5a58e0b294e2adfc923ac4baddfd270eb5a8" +source = "git+https://github.com/microsoft/igvm?branch=main#81dd0e49cd3caf9e894e376969e33f2584e6f8ba" dependencies = [ "bitfield-struct 0.10.1", "open-enum", @@ -1066,9 +1066,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jiff" @@ -1162,9 +1162,9 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags 2.10.0", "libc", @@ -1667,9 +1667,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "portable-atomic-util" @@ -1861,9 +1861,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "scopeguard" @@ -1912,9 +1912,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" dependencies = [ "itoa", "memchr", @@ -2132,18 +2132,18 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -2153,9 +2153,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] @@ -2184,9 +2184,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2206,9 +2206,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] diff --git a/Cargo.toml b/Cargo.toml index b738dde5bf..8e05e534de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates serde = "1.0.228" -serde_json = "1.0.145" +serde_json = "1.0.146" serde_with = { version = "3.16.1", default-features = false } # other crates diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 63aa99b8e4..791c5e113f 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -156,9 +156,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -168,9 +168,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -575,9 +575,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jobserver" @@ -954,9 +954,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "scopeguard" @@ -1005,9 +1005,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" dependencies = [ "itoa", "memchr", @@ -1147,18 +1147,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -1168,9 +1168,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] From aca7b01c6b0a2b0e266e67b91a0de9d2cb48a644 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sun, 7 Dec 2025 16:27:09 -0500 Subject: [PATCH 0430/1893] net_util: Fix MAC address parsing It wrongly allowed addresses containing a + instead of a hex character. Signed-off-by: Demi Marie Obenour --- net_util/src/mac.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/net_util/src/mac.rs b/net_util/src/mac.rs index dec8027889..d9501a1d9c 100644 --- a/net_util/src/mac.rs +++ b/net_util/src/mac.rs @@ -39,13 +39,10 @@ impl MacAddr { if v[i].len() != 2 { return common_err; } - bytes[i] = u8::from_str_radix(v[i], 16).map_err(|e| { - io::Error::other(format!( - "parsing of {} into a MAC address failed: {}", - s.as_ref(), - e - )) - })?; + if !v[i].bytes().all(|a| a.is_ascii_hexdigit()) { + return common_err; + } + bytes[i] = u8::from_str_radix(v[i], 16).unwrap(); } Ok(MacAddr { bytes }) @@ -187,6 +184,8 @@ mod unit_tests { let bytes = mac.get_bytes(); assert_eq!(bytes, [0x12u8, 0x34, 0x56, 0x78, 0x9a, 0xbc]); + MacAddr::parse_str("12:34:56:78:9a:+c").unwrap_err(); + let s = serde_json::to_string(&mac).expect("MacAddr serialization failed."); assert_eq!(s, "\"12:34:56:78:9a:bc\""); } From db205150f96ce98102b71434cc3a9ff05fd3873d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:16:05 +0000 Subject: [PATCH 0431/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index c97487d198..09bce3c649 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.40.0 + - uses: crate-ci/typos@v1.40.1 From f11682feea7bf168dcba81282eb18e94502b46e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:34:39 +0000 Subject: [PATCH 0432/1893] 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] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71b7cee467..956a5c9dc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1861,9 +1861,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "scopeguard" From 14911a9518a8d377030658d1f1dfb743a334e451 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:34:46 +0000 Subject: [PATCH 0433/1893] 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] --- fuzz/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 791c5e113f..2bd4e96209 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -954,9 +954,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "scopeguard" From 379e2d464783653d1b3b62bd0c965c8436334eb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 03:34:42 +0000 Subject: [PATCH 0434/1893] 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] --- Cargo.lock | 74 +++++++++++++++++++++++-------------------- Cargo.toml | 4 +-- fuzz/Cargo.lock | 60 +++++++++++++++++++++-------------- hypervisor/Cargo.toml | 2 +- 4 files changed, 79 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 956a5c9dc4..6cb5dbbfb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,9 +100,12 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] [[package]] name = "arch" @@ -365,9 +368,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.50" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -732,9 +735,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "flate2" @@ -1066,15 +1069,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8" dependencies = [ "jiff-static", "log", @@ -1085,9 +1088,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58" dependencies = [ "proc-macro2", "quote", @@ -1162,9 +1165,9 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", @@ -1667,9 +1670,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -1700,9 +1703,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -1842,9 +1845,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -1859,12 +1862,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" - [[package]] name = "scopeguard" version = "1.2.0" @@ -1912,15 +1909,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.146" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1968,9 +1965,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.18" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +checksum = "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952" dependencies = [ "libc", "signal-hook-registry", @@ -1978,10 +1975,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -2049,9 +2047,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", @@ -2834,6 +2832,12 @@ dependencies = [ "syn", ] +[[package]] +name = "zmij" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9747e91771f56fd7893e1164abd78febd14a670ceec257caad15e051de35f06" + [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index 8e05e534de..af47202a2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates serde = "1.0.228" -serde_json = "1.0.146" +serde_json = "1.0.148" serde_with = { version = "3.16.1", default-features = false } # other crates @@ -87,7 +87,7 @@ flume = "0.12.0" itertools = "0.14.0" libc = "0.2.178" log = "0.4.29" -signal-hook = "0.3.18" +signal-hook = "0.4.1" thiserror = "2.0.17" uuid = { version = "1.19.0" } wait-timeout = "0.2.1" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 2bd4e96209..fd39a37198 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -80,9 +80,12 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] [[package]] name = "arch" @@ -168,9 +171,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.50" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -379,6 +382,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "event_monitor" version = "0.1.0" @@ -407,9 +420,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "flate2" @@ -575,9 +588,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -873,9 +886,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -952,12 +965,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" - [[package]] name = "scopeguard" version = "1.2.0" @@ -1005,15 +1012,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.146" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1050,9 +1057,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.18" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +checksum = "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952" dependencies = [ "libc", "signal-hook-registry", @@ -1060,10 +1067,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -1591,6 +1599,12 @@ dependencies = [ "syn", ] +[[package]] +name = "zmij" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9747e91771f56fd7893e1164abd78febd14a670ceec257caad15e051de35f06" + [[package]] name = "zstd" version = "0.13.3" diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 993d18583d..308406443e 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -14,7 +14,7 @@ tdx = [] [dependencies] anyhow = { workspace = true } -arc-swap = "1.7.1" +arc-swap = "1.8.0" bitfield-struct = "0.12.0" byteorder = { workspace = true } cfg-if = { workspace = true } From de618ec35ecf5b2c54ff6fe6c9285094ca9b940f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Dec 2025 23:13:58 +0000 Subject: [PATCH 0435/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 09bce3c649..0fb26cdea6 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.40.1 + - uses: crate-ci/typos@v1.41.0 From dccdb223b718d512b78a5cd6b015645d27bd31cb Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 22 Dec 2025 20:41:20 +0000 Subject: [PATCH 0436/1893] tests: Re-enable CPU topology tests on MSHV Fixes: #7433 Signed-off-by: Anatol Belski --- cloud-hypervisor/tests/integration.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 2164437954..9f459267af 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2667,19 +2667,16 @@ mod common_parallel { } #[test] - #[cfg(not(feature = "mshv"))] // See issue #7433 fn test_cpu_topology_421() { test_cpu_topology(4, 2, 1, false); } #[test] - #[cfg(not(feature = "mshv"))] // See issue #7433 fn test_cpu_topology_142() { test_cpu_topology(1, 4, 2, false); } #[test] - #[cfg(not(feature = "mshv"))] // See issue #7433 fn test_cpu_topology_262() { test_cpu_topology(2, 6, 2, false); } From 3657db7843aa1d9c408ed7a0a5797da507d9f7b8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 22 Dec 2025 21:06:52 +0000 Subject: [PATCH 0437/1893] 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 --- hypervisor/src/mshv/mod.rs | 11 +++++++++++ vmm/src/cpu.rs | 23 +++++++++++++++++++++++ vmm/src/vm.rs | 9 +++++++++ 3 files changed, 43 insertions(+) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 40d8796e9a..8ef150294c 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -69,6 +69,7 @@ pub use x86_64::{VcpuMshvState, emulator}; /// Export generically-named wrappers of mshv-bindings for Unix-based platforms /// pub use { + mshv_bindings::hv_partition_property_code_HV_PARTITION_PROPERTY_PROCESSORS_PER_SOCKET as HV_PARTITION_PROPERTY_PROCESSORS_PER_SOCKET, mshv_bindings::mshv_create_device as CreateDevice, mshv_bindings::mshv_device_attr as DeviceAttr, mshv_ioctls, mshv_ioctls::DeviceFd, }; @@ -1712,6 +1713,16 @@ impl MshvVm { .map_err(|e| vm::HypervisorVmError::CreateDevice(e.into()))?; Ok(VfioDeviceFd::new_from_mshv(device_fd)) } + + /// + /// Sets a partition property. + /// + /// This allows runtime configuration of partition properties. + pub fn set_partition_property(&self, code: u32, value: u64) -> anyhow::Result<()> { + self.fd + .set_partition_property(code, value) + .map_err(|e| anyhow!("Failed to set partition property: {e:?}")) + } } /// diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index ea030fb0cf..bba78e642c 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -216,6 +216,10 @@ pub enum Error { #[cfg(target_arch = "x86_64")] #[error("Failed to inject NMI")] NmiError(#[source] hypervisor::HypervisorCpuError), + + #[cfg(feature = "mshv")] + #[error("Failed to set partition property")] + SetPartitionProperty(#[source] anyhow::Error), } pub type Result = result::Result; @@ -1374,6 +1378,25 @@ impl CpuManager { self.activate_vcpus(self.boot_vcpus(), false, Some(paused)) } + #[cfg(feature = "mshv")] + pub fn set_processors_per_socket_property(&self) -> Result<()> { + if let Some(mshv_vm) = self.vm.as_any().downcast_ref::() { + let threads_per_core = if let Some(ref topology) = self.config.topology { + topology.threads_per_core as u64 + } else { + 1u64 + }; + + mshv_vm + .set_partition_property( + hypervisor::mshv::HV_PARTITION_PROPERTY_PROCESSORS_PER_SOCKET, + threads_per_core, + ) + .map_err(Error::SetPartitionProperty)?; + } + Ok(()) + } + pub fn start_restored_vcpus(&mut self) -> Result<()> { self.activate_vcpus(self.vcpus.len() as u32, false, Some(true)) .map_err(|e| { diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 5364818855..441d0bcafb 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2415,6 +2415,15 @@ impl Vm { .map_err(Error::CpuManager)?; } + #[cfg(feature = "mshv")] + { + self.cpu_manager + .lock() + .unwrap() + .set_processors_per_socket_property() + .ok(); + } + #[cfg(feature = "tdx")] let (sections, guid_found) = if tdx_enabled { self.extract_tdvf_sections()? From f6f0d49c62bd70556a971f3cf51bf4be11b8dccc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:41:35 +0000 Subject: [PATCH 0438/1893] 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] --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 4 ++-- fuzz/Cargo.lock | 24 ++++++++++++------------ fuzz/Cargo.toml | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6cb5dbbfb2..679483e2e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -392,18 +392,18 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -1159,9 +1159,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libredox" @@ -1703,9 +1703,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -2036,9 +2036,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" dependencies = [ "proc-macro2", "quote", @@ -2834,9 +2834,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.3" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9747e91771f56fd7893e1164abd78febd14a670ceec257caad15e051de35f06" +checksum = "dcb2c125bd7365735bebeb420ccb880265ed2d2bddcbcd49f597fdfe6bd5e577" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index af47202a2d..b646b8e383 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,14 +78,14 @@ anyhow = "1.0.99" bitflags = "2.10.0" byteorder = "1.5.0" cfg-if = "1.0.4" -clap = "4.5.53" +clap = "4.5.54" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" epoll = "4.4.0" flume = "0.12.0" itertools = "0.14.0" -libc = "0.2.178" +libc = "0.2.179" log = "0.4.29" signal-hook = "0.4.1" thiserror = "2.0.17" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index fd39a37198..275cd4ba27 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -189,18 +189,18 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -654,9 +654,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libfuzzer-sys" @@ -886,9 +886,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -1104,9 +1104,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" dependencies = [ "proc-macro2", "quote", @@ -1601,9 +1601,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.3" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9747e91771f56fd7893e1164abd78febd14a670ceec257caad15e051de35f06" +checksum = "dcb2c125bd7365735bebeb420ccb880265ed2d2bddcbcd49f597fdfe6bd5e577" [[package]] name = "zstd" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 6cbcfa9050..c31b02f94c 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.178" +libc = "0.2.179" libfuzzer-sys = "0.4.10" linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } From 2e05836669c1d32dc6b8d69d10871379395cd41c Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 31 Dec 2025 12:06:24 -0500 Subject: [PATCH 0439/1893] hypervisor: Suppress unused_unsafe warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- arch/src/x86_64/mod.rs | 7 +++++++ hypervisor/src/hypervisor.rs | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 41ea7514e3..979fd52a9b 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -559,6 +559,7 @@ pub fn generate_common_cpuid( hypervisor: &dyn hypervisor::Hypervisor, config: &CpuidConfig, ) -> super::Result> { + #[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 @@ -694,6 +695,7 @@ pub fn generate_common_cpuid( // 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 { + #[allow(unused_unsafe)] // SAFETY: cpuid called with valid leaves if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 { // SAFETY: cpuid called with valid leaves @@ -708,8 +710,10 @@ pub fn generate_common_cpuid( // 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 { + #[allow(unused_unsafe)] // SAFETY: cpuid called with valid leaves if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 { + #[allow(unused_unsafe)] // SAFETY: cpuid called with valid leaves let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) }; entry.eax = leaf.eax; @@ -747,6 +751,7 @@ pub fn generate_common_cpuid( 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 { std::arch::x86_64::__cpuid(i) }; cpuid.push(CpuIdEntry { function: i, @@ -859,6 +864,7 @@ 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 @@ -1307,6 +1313,7 @@ pub fn initramfs_load_addr( 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); diff --git a/hypervisor/src/hypervisor.rs b/hypervisor/src/hypervisor.rs index fdc551c28e..a25f8a9bf7 100644 --- a/hypervisor/src/hypervisor.rs +++ b/hypervisor/src/hypervisor.rs @@ -155,7 +155,8 @@ pub trait Hypervisor: Send + Sync { /// Determine CPU vendor /// fn get_cpu_vendor(&self) -> CpuVendor { - // SAFETY: call cpuid with valid leaves + #[allow(unused_unsafe)] + // SAFETY: not actually unsafe, but considered unsafe by current stable unsafe { let leaf = x86_64::__cpuid(0x0); From c396f1ad2a66aa705c97a4f6e9f098603557bcaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 17:20:56 +0000 Subject: [PATCH 0440/1893] 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] --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 4 ++-- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 28 ++++++++++++++-------------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 679483e2e6..4536105386 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1024,9 +1024,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -1075,9 +1075,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -1088,9 +1088,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", @@ -1712,9 +1712,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -1909,9 +1909,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -2036,9 +2036,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.113" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -2814,18 +2814,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -2834,9 +2834,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb2c125bd7365735bebeb420ccb880265ed2d2bddcbcd49f597fdfe6bd5e577" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index b646b8e383..2750b683dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" } # serde crates serde = "1.0.228" -serde_json = "1.0.148" +serde_json = "1.0.149" serde_with = { version = "3.16.1", default-features = false } # other crates @@ -91,7 +91,7 @@ signal-hook = "0.4.1" thiserror = "2.0.17" uuid = { version = "1.19.0" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.31", default-features = false } +zerocopy = { version = "0.8.33", default-features = false } [workspace.lints.clippy] # Any clippy lint (group) in alphabetical order: diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 28fcbb1ad7..12b2fd1af6 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.31", features = [ +zerocopy = { version = "0.8.33", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 275cd4ba27..aad8a70afb 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -563,9 +563,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -895,9 +895,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -1012,9 +1012,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -1104,9 +1104,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.113" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1581,18 +1581,18 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -1601,9 +1601,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb2c125bd7365735bebeb420ccb880265ed2d2bddcbcd49f597fdfe6bd5e577" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" [[package]] name = "zstd" From 847de7e86ea06853b5208c6d608c938cacc41aec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 01:37:30 +0000 Subject: [PATCH 0441/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 0fb26cdea6..2458d82c9e 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.41.0 + - uses: crate-ci/typos@v1.42.0 From 079d94ecaeb63ff040a209e73a747cbc7b06346d Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 9 Jan 2026 17:51:07 -0500 Subject: [PATCH 0442/1893] 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 --- virtio-devices/src/seccomp_filters.rs | 1 + vmm/src/seccomp_filters.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs index 07601b68a8..5afd056a6b 100644 --- a/virtio-devices/src/seccomp_filters.rs +++ b/virtio-devices/src/seccomp_filters.rs @@ -293,6 +293,7 @@ fn virtio_thread_common() -> Vec<(i64, Vec)> { (libc::SYS_epoll_wait, vec![]), (libc::SYS_exit, vec![]), (libc::SYS_futex, vec![]), + (libc::SYS_gettid, vec![]), (libc::SYS_madvise, vec![]), (libc::SYS_mmap, vec![]), (libc::SYS_mprotect, vec![]), diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 8c0071fc74..55e1531113 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -497,6 +497,7 @@ fn signal_handler_thread_rules() -> Result)>, Backend (libc::SYS_mmap, vec![]), (libc::SYS_munmap, vec![]), (libc::SYS_prctl, vec![]), + (libc::SYS_gettid, vec![]), (libc::SYS_recvfrom, vec![]), (libc::SYS_rt_sigprocmask, vec![]), (libc::SYS_rt_sigreturn, vec![]), @@ -536,6 +537,7 @@ fn pty_foreground_thread_rules() -> Result)>, Backend (libc::SYS_rt_sigreturn, vec![]), (libc::SYS_sched_yield, vec![]), (libc::SYS_setsid, vec![]), + (libc::SYS_gettid, vec![]), (libc::SYS_sigaltstack, vec![]), (libc::SYS_write, vec![]), #[cfg(debug_assertions)] @@ -787,6 +789,7 @@ fn vcpu_thread_rules( (libc::SYS_exit, vec![]), (libc::SYS_epoll_ctl, vec![]), (libc::SYS_fstat, vec![]), + (libc::SYS_gettid, vec![]), (libc::SYS_futex, vec![]), (libc::SYS_getrandom, vec![]), (libc::SYS_getpid, vec![]), @@ -847,6 +850,7 @@ fn http_api_thread_rules() -> Result)>, BackendError> (libc::SYS_epoll_wait, vec![]), (libc::SYS_exit, vec![]), (libc::SYS_fcntl, vec![]), + (libc::SYS_gettid, vec![]), (libc::SYS_futex, vec![]), (libc::SYS_getrandom, vec![]), (libc::SYS_ioctl, create_api_ioctl_seccomp_rule()?), @@ -881,6 +885,7 @@ fn dbus_api_thread_rules() -> Result)>, BackendError> (libc::SYS_dup, vec![]), (libc::SYS_epoll_ctl, vec![]), (libc::SYS_exit, vec![]), + (libc::SYS_gettid, vec![]), (libc::SYS_futex, vec![]), (libc::SYS_getrandom, vec![]), (libc::SYS_madvise, vec![]), @@ -905,6 +910,7 @@ fn event_monitor_thread_rules() -> Result)>, BackendE Ok(vec![ (libc::SYS_brk, vec![]), (libc::SYS_close, vec![]), + (libc::SYS_gettid, vec![]), (libc::SYS_futex, vec![]), (libc::SYS_landlock_create_ruleset, vec![]), (libc::SYS_landlock_restrict_self, vec![]), From c87ca3921963f40ee748c3b05ccfc3431871fc21 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 1 Jan 2026 02:14:58 +0000 Subject: [PATCH 0443/1893] 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 --- vmm/src/acpi.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 7fca8435f0..5cb96f5d67 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -515,6 +515,8 @@ fn create_dbg2_table(base_address: u64) -> Sdt { } #[cfg(target_arch = "aarch64")] +// Generate IORT table based on Spec Revision E.b: +// https://developer.arm.com/documentation/den0049/eb/?lang=en fn create_iort_table(pci_segments: &[PciSegment]) -> Sdt { const ACPI_IORT_NODE_ITS_GROUP: u8 = 0x00; const ACPI_IORT_NODE_PCI_ROOT_COMPLEX: u8 = 0x02; @@ -528,7 +530,7 @@ fn create_iort_table(pci_segments: &[PciSegment]) -> Sdt { let iort_table_size: u32 = (ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET + ACPI_IORT_NODE_ROOT_COMPLEX_SIZE * pci_segments.len()) as u32; - let mut iort = Sdt::new(*b"IORT", iort_table_size, 2, *b"CLOUDH", *b"CHIORT ", 1); + let mut iort = Sdt::new(*b"IORT", iort_table_size, 3, *b"CLOUDH", *b"CHIORT ", 1); iort.write(36, ((1 + pci_segments.len()) as u32).to_le()); iort.write(40, (48u32).to_le()); @@ -536,6 +538,8 @@ fn create_iort_table(pci_segments: &[PciSegment]) -> Sdt { iort.write(48, ACPI_IORT_NODE_ITS_GROUP); // Length of the ITS group node in bytes iort.write(49, (24u16).to_le()); + // Revision + iort.write(51, (1u8).to_le()); // ITS counts iort.write(64, (1u32).to_le()); @@ -560,7 +564,7 @@ fn create_iort_table(pci_segments: &[PciSegment]) -> Sdt { // Fully coherent device iort.write(node_offset + 16, (1u32).to_le()); // CCA = CPM = DCAS = 1 - iort.write(node_offset + 24, 3u8); + iort.write(node_offset + 23, 3u8); // PCI segment number iort.write(node_offset + 28, (segment.id as u32).to_le()); // Memory address size limit From 01d711e8939f8c074b2bc24d2fb6cd9bf29c73f5 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Fri, 2 Jan 2026 22:08:08 +0000 Subject: [PATCH 0444/1893] 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 --- vmm/src/acpi.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 5cb96f5d67..b2da91e089 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -542,6 +542,8 @@ fn create_iort_table(pci_segments: &[PciSegment]) -> Sdt { iort.write(51, (1u8).to_le()); // ITS counts iort.write(64, (1u32).to_le()); + // GIC ITS Identity Array + iort.write(68, (0u32).to_le()); // Value must match what's defined in MADT // Root Complex Nodes for (i, segment) in pci_segments.iter().enumerate() { From 11edb280699657b917cc1188214a214bfce51784 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Fri, 2 Jan 2026 23:18:48 +0000 Subject: [PATCH 0445/1893] 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] https://github.com/cloud-hypervisor/cloud-hypervisor/commit/c9374d87ac453d49185aa7b734df089444166484 Signed-off-by: Bo Chen --- vmm/src/acpi.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index b2da91e089..563a5f2e19 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -582,7 +582,12 @@ fn create_iort_table(pci_segments: &[PciSegment]) -> Sdt { // 1 (bus) x 32 (devices) x 8 (functions) = 256 // Note: Currently only 1 bus is supported in a segment. iort.write(mapping_offset + 4, (255_u32).to_le()); - // The lowest value in the output range + // Output base maps to ITS device IDs which must match the + // device ID encoding used in KVM MSI routing setup, which + // shares the same limitation - only 1 bus per segment and + // up to 256 segments. + // See: https://github.com/cloud-hypervisor/cloud-hypervisor/commit/c9374d87ac453d49185aa7b734df089444166484 + assert!(segment.id < 256, "Up to 256 PCI segments are supported."); iort.write(mapping_offset + 8, ((256 * segment.id) as u32).to_le()); // id_mapping_array_output_reference should be // the ITS group node (the first node) if no SMMU From c19ee037a2fa2305941c0b67303b1ed0ebe7225b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 04:17:48 +0000 Subject: [PATCH 0446/1893] 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] --- Cargo.lock | 75 ++++++++++++++----------------------- Cargo.toml | 2 +- block/Cargo.toml | 2 +- cloud-hypervisor/Cargo.toml | 2 +- fuzz/Cargo.lock | 34 ++++++++--------- fuzz/Cargo.toml | 2 +- vmm/Cargo.toml | 2 +- 7 files changed, 50 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4536105386..cd1223bb80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,9 +368,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.51" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "jobserver", @@ -384,12 +384,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "clap" version = "4.5.54" @@ -414,9 +408,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "cloud-hypervisor" @@ -735,15 +729,15 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -1159,9 +1153,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.179" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libredox" @@ -1330,19 +1324,6 @@ dependencies = [ "vmm-sys-util", ] -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - [[package]] name = "no-std-net" version = "0.6.0" @@ -1747,9 +1728,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" dependencies = [ "getrandom 0.3.4", ] @@ -2753,9 +2734,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zbus" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" +checksum = "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b" dependencies = [ "async-broadcast", "async-executor", @@ -2771,8 +2752,9 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix", + "libc", "ordered-stream", + "rustix", "serde", "serde_repr", "tracing", @@ -2787,9 +2769,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" +checksum = "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2802,12 +2784,11 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "static_assertions", "winnow", "zvariant", ] @@ -2834,9 +2815,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec" [[package]] name = "zstd" @@ -2868,9 +2849,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +checksum = "326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543" dependencies = [ "endi", "enumflags2", @@ -2882,9 +2863,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +checksum = "ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2895,9 +2876,9 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 2750b683dc..c191a4db62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,7 +85,7 @@ env_logger = "0.11.8" epoll = "4.4.0" flume = "0.12.0" itertools = "0.14.0" -libc = "0.2.179" +libc = "0.2.180" log = "0.4.29" signal-hook = "0.4.1" thiserror = "2.0.17" diff --git a/block/Cargo.toml b/block/Cargo.toml index 91b3394ef7..c038b0cd83 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -11,7 +11,7 @@ io_uring = ["dep:io-uring"] [dependencies] byteorder = { workspace = true } crc-any = "2.5.0" -flate2 = "1.0" +flate2 = "1.1" io-uring = { version = "0.7.11", optional = true } libc = { workspace = true } log = { workspace = true } diff --git a/cloud-hypervisor/Cargo.toml b/cloud-hypervisor/Cargo.toml index 426a522635..0dd1f4788a 100644 --- a/cloud-hypervisor/Cargo.toml +++ b/cloud-hypervisor/Cargo.toml @@ -38,7 +38,7 @@ tracer = { path = "../tracer" } vm-memory = { workspace = true } vmm = { path = "../vmm" } vmm-sys-util = { workspace = true } -zbus = { version = "5.7.1", optional = true } +zbus = { version = "5.13.1", optional = true } [dev-dependencies] dirs = { workspace = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index aad8a70afb..5e0be02e5f 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -171,9 +171,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.51" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "jobserver", @@ -210,9 +210,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "cloud-hypervisor-fuzz" @@ -409,7 +409,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -420,15 +420,15 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -490,9 +490,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -654,9 +654,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.179" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libfuzzer-sys" @@ -930,9 +930,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" dependencies = [ "getrandom 0.3.4", ] @@ -1601,9 +1601,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec" [[package]] name = "zstd" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c31b02f94c..a05812f188 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.179" +libc = "0.2.180" libfuzzer-sys = "0.4.10" linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index a2fadfbd82..a50ec35434 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -91,7 +91,7 @@ vm-memory = { workspace = true, features = [ vm-migration = { path = "../vm-migration" } vm-virtio = { path = "../vm-virtio" } vmm-sys-util = { workspace = true, features = ["with-serde"] } -zbus = { version = "5.12.0", optional = true } +zbus = { version = "5.13.1", optional = true } zerocopy = { workspace = true, features = ["alloc", "derive"] } [lints] From 34ee973ee0089eed22df6cfd3a4a3a5780a098a9 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 14 Jan 2026 14:45:31 +0800 Subject: [PATCH 0447/1893] 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 --- scripts/test-util.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-util.sh b/scripts/test-util.sh index 04d79371b4..5c49dcae62 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -212,7 +212,7 @@ mount_and_exec() { local COMMAND_STATUS=0 # Cleanup function to unmount and detach loop device - # shellcheck disable=SC2317 + # shellcheck disable=SC2317,SC2329 cleanup() { if [ -n "$MOUNT_DIR" ]; then echo "Cleanup: Unmounting $MOUNT_DIR..." >&2 From 7b92a36c4cb3fed4ba2c8a485fe72ad75169b1b3 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 6 Jan 2026 19:46:25 +0800 Subject: [PATCH 0448/1893] 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 --- docs/balloon.md | 6 +++--- docs/cpu.md | 42 +++++++++++++++++++++--------------------- docs/device_model.md | 6 +++--- docs/ivshmem.md | 11 ++++++----- docs/macvtap-bridge.md | 6 +++--- docs/memory.md | 26 ++++++++++++-------------- docs/vdpa.md | 3 ++- docs/virtiofs-root.md | 18 +++++++++--------- 8 files changed, 59 insertions(+), 59 deletions(-) diff --git a/docs/balloon.md b/docs/balloon.md index 361c0fee34..ee6d5ac882 100644 --- a/docs/balloon.md +++ b/docs/balloon.md @@ -26,8 +26,8 @@ struct BalloonConfig { Size of the balloon device. It is subtracted from the VM's total size. For instance, if creating a VM with 4GiB of RAM, along with a balloon of 1GiB, the -guest will be able to use 3GiB of accessible memory. The guest sees all the RAM -and unless it is balloon enlightened is entitled to all of it. +guest will be able to use 3GiB of accessible memory. The guest sees all the RAM, +and unless it is balloon enlightened, it is entitled to all of it. This parameter is mandatory. @@ -42,7 +42,7 @@ _Example_ ### `deflate_on_oom` -Allow the guest to deflate the balloon if running Out Of Memory (OOM). Assuming +Allow the guest to deflate the balloon when running Out Of Memory (OOM). Assuming the balloon size is greater than 0, this means the guest is allowed to reduce the balloon size all the way down to 0 if this can help recover from the OOM event. diff --git a/docs/cpu.md b/docs/cpu.md index 56e7f3aec6..6a55942691 100644 --- a/docs/cpu.md +++ b/docs/cpu.md @@ -11,8 +11,8 @@ to set vCPUs options for Cloud Hypervisor. ```rust struct CpusConfig { - boot_vcpus: u8, - max_vcpus: u8, + boot_vcpus: u32, + max_vcpus: u32, topology: Option, kvm_hyperv: bool, max_phys_bits: u8, @@ -30,12 +30,12 @@ struct CpusConfig { Number of vCPUs present at boot time. -This option allows to define a specific number of vCPUs to be present at the +This option allows defining a specific number of vCPUs to be present at the time the VM is started. This option is mandatory when using the `--cpus` parameter. If `--cpus` is not specified, this option takes the default value of `1`, starting the VM with a single vCPU. -Value is an unsigned integer of 8 bits. +Value is an unsigned integer of 32 bits. _Example_ @@ -48,14 +48,14 @@ _Example_ Maximum number of vCPUs. This option defines the maximum number of vCPUs that can be assigned to the VM. -In particular, this option is used when looking for CPU hotplug as it lets the -provide an indication about how many vCPUs might be needed later during the -runtime of the VM. +In particular, this option is used when looking for CPU hotplug as it provides +an indication about how many vCPUs might be needed later during the runtime of +the VM. For instance, if booting the VM with 2 vCPUs and a maximum of 6 vCPUs, it means up to 4 vCPUs can be added later at runtime by resizing the VM. The value must be greater than or equal to the number of boot vCPUs. -The value is an unsigned integer of 8 bits. +The value is an unsigned integer of 32 bits. By default this option takes the value of `boot`, meaning vCPU hotplug is not expected and can't be performed. @@ -73,16 +73,16 @@ Topology of the guest platform. This option gives the user a way to describe the exact topology that should be exposed to the guest. It can be useful to describe to the guest the same topology found on the host as it allows for proper usage of the resources and -is a way to achieve better performances. +is a way to achieve better performance. The topology is described through the following structure: ```rust struct CpuTopology { - threads_per_core: u8, - cores_per_die: u8, - dies_per_package: u8, - packages: u8, + threads_per_core: u16, + cores_per_die: u16, + dies_per_package: u16, + packages: u16, } ``` @@ -124,7 +124,7 @@ Maximum size for guest's addressable space. This option defines the maximum number of physical bits for all vCPUs, which sets a limit for the size of the guest's addressable space. This is mainly -useful for debug purpose. +useful for debugging purposes. The value is an unsigned integer of 8 bits. @@ -141,16 +141,16 @@ Affinity of each vCPU. This option gives the user a way to provide the host CPU set associated with each vCPU. It is useful for achieving CPU pinning, ensuring multiple VMs won't affect the performance of each other. It might also be used in the context of -NUMA as it is way of making sure the VM can run on a specific host NUMA node. -In general, this option is used to increase the performances of a VM depending +NUMA as it is a way of making sure the VM can run on a specific host NUMA node. +In general, this option is used to increase the performance of a VM depending on the host platform and the type of workload running in the guest. The affinity is described through the following structure: ```rust struct CpuAffinity { - vcpu: u8, - host_cpus: Vec, + vcpu: u32, + host_cpus: Vec, } ``` @@ -164,8 +164,8 @@ The outer brackets define the list of vCPUs. And for each vCPU, the inner brackets attached to `@` define the list of host CPUs the vCPU is allowed to run onto. -Multiple values can be provided to define each list. Each value is an unsigned -integer of 8 bits. +Multiple values can be provided to define each list. Each value is a +platform-native unsigned integer (`usize`). For instance, if one needs to run vCPU 0 on host CPUs from 0 to 4, the syntax using `-` will help define a contiguous range with `affinity=0@[0-4]`. The @@ -220,4 +220,4 @@ _Example_ ``` --cpus nested=on -``` \ No newline at end of file +``` diff --git a/docs/device_model.md b/docs/device_model.md index 0233ad07a0..c072dc2eb6 100644 --- a/docs/device_model.md +++ b/docs/device_model.md @@ -31,7 +31,7 @@ Simple emulation of a serial port by reading and writing to specific port I/O addresses. The serial port can be very useful to gather early logs from the operating system booted inside the VM. -For x86_64, The default serial port is from an emulated 16550A device. It can +For x86_64, the default serial port is from an emulated 16550A device. It can be used as the default console for Linux when booting with the option `console=ttyS0`. For AArch64, the default serial port is from an emulated PL011 UART device. The related command line for AArch64 is `console=ttyAMA0`. @@ -48,7 +48,7 @@ This device is built-in by default, but it can be compiled out with Rust features. When compiled in, it is always enabled, and cannot be disabled from the command line. -For AArch64 machines, an ARM PrimeCell Real Time Clock(PL031) is implemented. +For AArch64 machines, an ARM PrimeCell Real Time Clock (PL031) is implemented. This device is built-in by default for the AArch64 platform, and it is always enabled, and cannot be disabled from the command line. @@ -136,7 +136,7 @@ flag `--net`. The `virtio-pmem` implementation emulates a virtual persistent memory device that `cloud-hypervisor` can e.g. boot from. Booting from a `virtio-pmem` device -allows to bypass the guest page cache and improve the guest memory footprint. +allows bypassing the guest page cache and improve the guest memory footprint. This device is always built-in, and it is enabled based on the presence of the flag `--pmem`. diff --git a/docs/ivshmem.md b/docs/ivshmem.md index 3a7913c3f6..0bc82cbb9a 100644 --- a/docs/ivshmem.md +++ b/docs/ivshmem.md @@ -5,7 +5,7 @@ region between a guest and the host. In order for all guests to be able to pick up the shared memory area, it is modeled as a PCI device exposing said memory to the guest as a PCI BAR. -Device Specification is +Device Specification is available at https://www.qemu.org/docs/master/specs/ivshmem-spec.html. Now we support setting a backend file to share data between host and guest. @@ -16,9 +16,10 @@ supported yet. `--ivshmem`, an optional argument, can be passed to enable ivshmem device. This argument takes a file as a `path` value and a file size as a `size` value. +The `size` value must be 2^n. ``` ---ivshmem device backend file "path=,size="; +--ivshmem device backend file "path=,size=" ``` ## Example @@ -41,11 +42,11 @@ Start application to mmap the file data to a memory region: --ivshmem path=/tmp/ivshmem.data,size=1M ``` -Insmod a ivshmem device driver to enable the device. The file data will be +Insmod an ivshmem device driver to enable the device. The file data will be mmapped to the PCI `bar2` of ivshmem device, guest can r/w data by accessing this memory. -A simple example of ivshmem driver can get from: +A simple example of ivshmem driver can be obtained from: https://github.com/lisongqian/clh-linux/commits/ch-6.12.8-ivshmem -The host process can r/w this data by remmaping the `/tmp/ivshmem.data`. +The host process can r/w this data by remapping the `/tmp/ivshmem.data`. diff --git a/docs/macvtap-bridge.md b/docs/macvtap-bridge.md index 5161eb7bb6..66c177d333 100644 --- a/docs/macvtap-bridge.md +++ b/docs/macvtap-bridge.md @@ -1,6 +1,6 @@ # Using MACVTAP to Bridge onto Host Network -Cloud Hypervisor supports using a MACVTAP device which is derived from a MACVLAN. Full details of configuring MACVLAN or MACVTAP is out of scope of this document. However the example below indicates how to bridge the guest directly onto the network the host is on. Due to the lack of hairpin mode it not usually possible to reach the guest directly from the host. +Cloud Hypervisor supports using a MACVTAP device which is derived from a MACVLAN. Full details of configuring MACVLAN or MACVTAP are out of scope of this document. However the example below indicates how to bridge the guest directly onto the network the host is on. Due to the lack of hairpin mode it is not usually possible to reach the guest directly from the host. ```bash # The MAC address must be attached to the macvtap and be used inside the guest @@ -26,7 +26,7 @@ target/debug/cloud-hypervisor \ --disk path=~/workloads/focal.raw \ --cpus boot=1 --memory size=512M \ --cmdline "root=/dev/vda1 console=hvc0" \ - --net fd=3,mac=$mac 3<>$"$tapdevice" + --net fd=3,mac=$mac 3<>"$tapdevice" ``` -As the guest is now connected to the same L2 network as the host you can obtain an IP address based on your host network (potentially including via DHCP) +As the guest is now connected to the same L2 network as the host, you can obtain an IP address based on your host network (potentially including via DHCP) diff --git a/docs/memory.md b/docs/memory.md index a429ff1b78..d8a7619e22 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -20,7 +20,7 @@ struct MemoryConfig { hugepages: bool, hugepage_size: Option, prefault: bool, - thp: bool + thp: bool, zones: Option>, } ``` @@ -119,7 +119,7 @@ By default this option is turned off, which results in performing `mmap(2)` with `MAP_PRIVATE` flag. If `hugepages=on` then the value of this field is ignored as huge pages always -requires `MAP_SHARED`. +require `MAP_SHARED`. _Example_ @@ -135,8 +135,7 @@ If no huge page size is supplied the system's default huge page size is used. By using hugepages, one can improve the overall performance of the VM, assuming the guest will allocate hugepages as well. Another interesting use case is VFIO -as it speeds up the VM's boot time since the amount of IOMMU mappings are -reduced. +as it speeds up the VM's boot time since the amount of IOMMU mappings is reduced. The user is responsible for ensuring there are sufficient huge pages of the specified size for the VMM to use. Failure to do so may result in strange VMM @@ -185,7 +184,7 @@ backing file) should be labelled `MADV_HUGEPAGE` with `madvise(2)` indicating to the kernel that this memory may be backed with huge pages transparently. The use of transparent huge pages can improve the performance of the guest as -there will fewer virtualisation related page faults. Unlike using +there will be fewer virtualisation related page faults. Unlike using `hugepages=on` a specific number of huge pages do not need to be allocated by the kernel. @@ -295,9 +294,9 @@ vhost-user devices as part of the VM device model, as they will be driven by standalone daemons needing access to the guest RAM content. If `hugepages=on` then the value of this field is ignored as huge pages always -requires `MAP_SHARED`. +require `MAP_SHARED`. -By default this option is turned off, which result in performing `mmap(2)` +By default this option is turned off, which results in performing `mmap(2)` with `MAP_PRIVATE` flag. _Example_ @@ -315,8 +314,7 @@ If no huge page size is supplied the system's default huge page size is used. By using hugepages, one can improve the overall performance of the VM, assuming the guest will allocate hugepages as well. Another interesting use case is VFIO -as it speeds up the VM's boot time since the amount of IOMMU mappings are -reduced. +as it speeds up the VM's boot time since the amount of IOMMU mappings is reduced. The user is responsible for ensuring there are sufficient huge pages of the specified size for the VMM to use. Failure to do so may result in strange VMM @@ -325,7 +323,7 @@ error with `hugepages` enabled, just disable it or check whether there are enoug huge pages. If `hugepages=on` then the value of `shared` is ignored as huge pages always -requires `MAP_SHARED`. +require `MAP_SHARED`. By default this option is turned off. @@ -434,7 +432,7 @@ it allows for specifying the distance between each NUMA node. ```rust struct NumaConfig { guest_numa_id: u32, - cpus: Option>, + cpus: Option>, distances: Option>, memory_zones: Option>, } @@ -470,7 +468,7 @@ regarding the CPUs associated with it, which might help the guest run more efficiently. Multiple values can be provided to define the list. Each value is an unsigned -integer of 8 bits. +integer of 32 bits. For instance, if one needs to attach all CPUs from 0 to 4 to a specific node, the syntax using `-` will help define a contiguous range with `cpus=0-4`. The @@ -493,7 +491,7 @@ _Example_ ### `distances` List of distances between the current NUMA node referred by `guest_numa_id` -and the destination NUMA nodes listed along with distances. This option let +and the destination NUMA nodes listed along with distances. This option lets the user choose the distances between guest NUMA nodes. This is important to provide an accurate description of the way non uniform memory accesses will perform in the guest. @@ -552,7 +550,7 @@ _Example_ ### PCI bus Cloud Hypervisor supports guests with one or more PCI segments. The default PCI segment always -has affinity to NUMA node 0. Be default, all other PCI segments have affinity to NUMA node 0. +has affinity to NUMA node 0. By default, all other PCI segments have affinity to NUMA node 0. The user may configure the NUMA affinity for any additional PCI segments. _Example_ diff --git a/docs/vdpa.md b/docs/vdpa.md index 2ba9126bd8..c1aa34c571 100644 --- a/docs/vdpa.md +++ b/docs/vdpa.md @@ -29,6 +29,7 @@ parameters available for the vDPA device. struct VdpaConfig { path: PathBuf, num_queues: usize, + iommu: bool, id: Option, pci_segment: u16, } @@ -83,7 +84,7 @@ _Example_ ### `pci_segment` -PCI segment number to which the vDPA device should be attached to. +PCI segment number to which the vDPA device should be attached. This parameter is optional. diff --git a/docs/virtiofs-root.md b/docs/virtiofs-root.md index d3a23f17d5..bc784f5c18 100644 --- a/docs/virtiofs-root.md +++ b/docs/virtiofs-root.md @@ -37,20 +37,20 @@ sudo sed -i '/vt100/a \n# paravirt console\nhvc0::respawn:/sbin/getty -L hvc0 11 # any sort of production setup sudo sed -i 's/root:!::0:::::/root:::0:::::/' etc/shadow # set up init scripts -for i in acpid crond +for i in acpid crond; do sudo ln -sf /etc/init.d/$i etc/runlevels/default/$i -end -for i in bootmisc hostname hwclock loadkmap modules networking swap sysctl syslog urandom +done +for i in bootmisc hostname hwclock loadkmap modules networking swap sysctl syslog urandom; do sudo ln -sf /etc/init.d/$i etc/runlevels/boot/$i -end +done -for i in killprocs mount-ro savecache +for i in killprocs mount-ro savecache; do sudo ln -sf /etc/init.d/$i etc/runlevels/shutdown/$i -end +done -for i in devfs dmesg hwdrivers mdev +for i in devfs dmesg hwdrivers mdev; do sudo ln -sf /etc/init.d/$i etc/runlevels/sysinit/$i -end +done # setup network config echo 'auto lo iface lo inet loopback @@ -89,4 +89,4 @@ virtiofs If you find any issues or have suggestions, feel free to reach out to @iggy on the cloud-hypervisor slack. Also if this works for you, I'd like to know as well. It would also be nice to get steps for preparing other distribution root -filesystems. \ No newline at end of file +filesystems. From e7a151e9539188c1b831ed1c3377528859899f07 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 6 Jan 2026 20:40:41 +0800 Subject: [PATCH 0449/1893] 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 --- docs/api.md | 27 +++++++++++++-------------- docs/building.md | 4 ++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/api.md b/docs/api.md index 95118b4d10..8f7a10642d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -8,14 +8,14 @@ - [REST API Examples](#rest-api-examples) - [Create a Virtual Machine](#create-a-virtual-machine) - [Boot a Virtual Machine](#boot-a-virtual-machine) - - [Dump a Virtual Machine Information](#dump-a-virtual-machine-information) + - [Dump Virtual Machine Information](#dump-virtual-machine-information) - [Reboot a Virtual Machine](#reboot-a-virtual-machine) - [Shut a Virtual Machine Down](#shut-a-virtual-machine-down) - [D-Bus API](#d-bus-api) - [D-Bus API Location and availability](#d-bus-api-location-and-availability) - [D-Bus API Interface](#d-bus-api-interface) - [Command Line Interface](#command-line-interface) - - [REST API, D-Bus API and CLI Architectural Relationship](#rest-api-and-cli-architectural-relationship) + - [REST API, D-Bus API and CLI Architectural Relationship](#rest-api-d-bus-api-and-cli-architectural-relationship) - [Internal API](#internal-api) - [Goals and Design](#goals-and-design) - [End to End Example](#end-to-end-example) @@ -31,7 +31,7 @@ The Cloud Hypervisor API is made of 2 distinct interfaces: 1. **The internal API**, based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/) module. This API is used internally by the Cloud Hypervisor threads to - communicate between each others. + communicate with each other. The goal of this document is to describe the Cloud Hypervisor API as a whole, and to outline how the internal and external APIs are architecturally related. @@ -81,7 +81,7 @@ The Cloud Hypervisor API exposes the following actions through its endpoints: | Trigger power button of the VM | `/vm.power-button` | N/A | N/A | The VM is booted | | Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted | | Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused | -| Task a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused | +| Take a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused | | Perform a coredump of the VM* | `/vm.coredump` | `/schemas/VmCoredumpData` | N/A | The VM is paused | | Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted | | Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted | @@ -155,9 +155,9 @@ Once the VM is created, we can boot it: curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.boot' ``` -##### Dump a Virtual Machine Information +##### Dump Virtual Machine Information -We can fetch information about any VM, as soon as it's created: +We can fetch information about any VM as soon as it's created: ```shell #!/usr/bin/env bash @@ -201,7 +201,7 @@ see [D-Bus API Interface](#d-bus-api-interface). #### D-Bus API Location and availability This feature is not compiled into Cloud Hypervisor by default. Users who -wish to use the D-Bus API, must explicitly enable it with the `dbus_api` +wish to use the D-Bus API must explicitly enable it with the `dbus_api` feature flag when compiling Cloud Hypervisor. ```sh @@ -278,7 +278,7 @@ From the CLI, one can: The REST API, D-Bus API and the CLI all rely on a common, [internal API](#internal-api). The CLI options are parsed by the -[clap crate](https://docs.rs/clap/4.3.11/clap/) and then translated into +[clap crate](https://docs.rs/clap/4.5.53/clap/) and then translated into [internal API](#internal-api) commands. The REST API is processed by an HTTP thread using the @@ -288,7 +288,7 @@ crate. As with the CLI, the HTTP requests eventually get translated into The D-Bus API is implemented using the [zbus](https://github.com/dbus2/zbus) crate and runs in its own thread. Whenever it needs to call the [internal API](#internal-api), -the [blocking](https://github.com/smol-rs/blocking) crate is used perform the call in zbus' async context. +the [blocking](https://github.com/smol-rs/blocking) crate is used to perform the call in zbus' async context. As a summary, the REST API, the D-Bus API and the CLI are essentially frontends for the [internal API](#internal-api): @@ -321,7 +321,7 @@ As a summary, the REST API, the D-Bus API and the CLI are essentially frontends The Cloud Hypervisor internal API, as its name suggests, is used internally by the different Cloud Hypervisor threads (VMM, HTTP, D-Bus, control loop, -etc) to send commands and responses to each others. +etc) to send commands and responses to each other. It is based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/), and the single consumer (a.k.a. the API receiver) is the Cloud Hypervisor @@ -365,9 +365,8 @@ APIs work together, let's look at a complete VM creation flow, from the [REST API](#rest-api) call, to the reply the external user will receive: 1. A user or operator sends an HTTP request to the Cloud Hypervisor - [REST API](#rest-api) in order to creates a virtual machine: - ``` - shell + [REST API](#rest-api) in order to create a virtual machine: + ```shell #!/usr/bin/env bash curl --unix-socket /tmp/cloud-hypervisor.sock -i \ @@ -414,7 +413,7 @@ APIs work together, let's look at a complete VM creation flow, from the the `VmCreate` payload, and extracts both the `VmConfig` structure and the [Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html) from the command payload. It stores the `VmConfig` structure and replies back to the - sender ((The HTTP thread): + sender (The HTTP thread): ```Rust match api_request { ApiRequest::VmCreate(config, sender) => { diff --git a/docs/building.md b/docs/building.md index 39d9e1a056..b74941b390 100644 --- a/docs/building.md +++ b/docs/building.md @@ -24,8 +24,8 @@ Hypervisor. Here, all the steps are based on Ubuntu, for other Linux distributions please replace the package manager and package name. ```shell -# Install basic packages needed. For a package list targeting for more -# functionalities for example the test, please see resources/Dockerfile. +# Install basic dependencies. For a list of packages required for additional +# features (e.g., testing), please refer to resources/Dockerfile. $ sudo apt-get update $ sudo apt install git build-essential m4 bison flex uuid-dev qemu-utils musl-tools # Install rust tool chain From 2c2dec50f9eead6bb20673bf99d0afacaeb3dee8 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 7 Jan 2026 00:09:36 +0800 Subject: [PATCH 0450/1893] docs: fix minor syntax issues for debug There are some minor syntax and command issues in debug-port document. As commit 5febdec81a69 (vmm: Enable `gdbstub` on AArch64) supported aarch64, the docs should keep consistent. Signed-off-by: Yi Wang --- docs/debug-port.md | 6 +++--- docs/gdb.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/debug-port.md b/docs/debug-port.md index 8983a8f0cb..490fae0924 100644 --- a/docs/debug-port.md +++ b/docs/debug-port.md @@ -13,7 +13,7 @@ be used simultaneously. ### `0x80` I/O port -Whenever the guest write one byte between `0x0` and `0xF` on this particular +Whenever the guest writes one byte between `0x0` and `0xF` on this particular I/O port, `cloud-hypervisor` will log and timestamp that event at the `debug` log level. @@ -52,7 +52,7 @@ to easily grep for the tracing logs (e.g. ``` ./target/debug/cloud-hypervisor \ - --kernel ~/rust-hypervisor-firmware/target/target/release/hypervisor-fw \ + --kernel ~/rust-hypervisor-firmware/target/release/hypervisor-fw \ --disk path=~/hypervisor/images/focal-server-cloudimg-amd64.raw \ --cpus 4 \ --memory size=1024M \ @@ -94,4 +94,4 @@ The `0x80` debug port and the port of the firmware debug device are always available. The debug console must be activated via the command line, but provides more configuration options. -You can use different ports for different aspect of your logging messages. +You can use different ports for different aspects of your logging messages. diff --git a/docs/gdb.md b/docs/gdb.md index 10b75c9f6c..49449b0192 100644 --- a/docs/gdb.md +++ b/docs/gdb.md @@ -1,6 +1,6 @@ # GDB Support -This feature allows remote guest debugging using GDB. Note that this feature is only supported on x86_64/KVM. +This feature allows remote guest debugging using GDB. Note that this feature is supported on x86_64 and aarch64 with KVM. To enable debugging with GDB, build with the `guest_debug` feature enabled: From 34b8aed662d5bd37237c544b5f26f01680077777 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 7 Jan 2026 10:01:45 +0800 Subject: [PATCH 0451/1893] 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 --- docs/amd_sev_snp.md | 11 +++++------ docs/intel_tdx.md | 14 +++++++------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/amd_sev_snp.md b/docs/amd_sev_snp.md index c3bcddf8d2..adf37f75a1 100644 --- a/docs/amd_sev_snp.md +++ b/docs/amd_sev_snp.md @@ -2,7 +2,7 @@ ### WARNING -This feature is only currently supported on MSHV. +This feature is currently only supported on MSHV. AMD Secure Encrypted Virtualization & Secure Nested Paging (SEV-SNP) is an AMD technology designed to add strong memory integrity protection to help prevent @@ -10,13 +10,12 @@ malicious hypervisor-based attacks like data replay, memory-remapping and more in order to create an isolated execution environment. Here are some useful links: -- [SNP Homepage](https://www.amd.com/content/dam/amd/en/documents/epyc-business-docs/solution-briefs/amd-secure-encrypted-virtualization-solution-brief.pdf): +- [SNP Homepage](https://docs.amd.com/v/u/en-US/amd-secure-encrypted-virtualization-solution-brief): more information about SEV-SNP technical aspects, design and specification. ## Cloud Hypervisor support -It is required to use a machine which has enabled support for AMD SEV-SNP in -the BIOS. +A machine with AMD SEV-SNP support which is enabled in the BIOS is required. On the Cloud Hypervisor side, all you need is to build the project with the `sev_snp` feature enabled: @@ -26,7 +25,7 @@ cargo build --no-default-features --features "sev_snp" ``` **Note** -Please note that `sev_snp` cannot be enabled in conjunction with `tdx` feature flag. +Please note that `sev_snp` cannot be enabled in conjunction with the `tdx` feature flag. You can run a SEV-SNP VM using the following command: @@ -38,4 +37,4 @@ You can run a SEV-SNP VM using the following command: --disk path=ubuntu.img ``` -For more information related to Microsoft Hypervisor please see [mshv.md](mshv.md) +For more information related to Microsoft Hypervisor, please see [mshv.md](mshv.md) diff --git a/docs/intel_tdx.md b/docs/intel_tdx.md index 95bb5a1b55..f2e7aa9b1f 100644 --- a/docs/intel_tdx.md +++ b/docs/intel_tdx.md @@ -75,7 +75,7 @@ meaning it will be printing guest kernel logs to the `virtio-console` device. ```bash ./cloud-hypervisor \ - --platform tdx=on + --platform tdx=on \ --firmware edk2/Build/IntelTdx/RELEASE_GCC5/FV/OVMF.fd \ --cpus boot=1 \ --memory size=1G \ @@ -87,7 +87,7 @@ firmware: ```bash ./cloud-hypervisor \ - --platform tdx=on + --platform tdx=on \ --firmware edk2/Build/IntelTdx/DEBUG_GCC5/FV/OVMF.fd \ --cpus boot=1 \ --memory size=1G \ @@ -105,7 +105,7 @@ This is a lightweight version of the TDVF, written in Rust and designed for direct kernel boot, which is useful for containers use cases. To build TDShim from source, it is required to install `Rust`, `NASM`, -and `LLVM` first. The TDshim can be build as follows: +and `LLVM` first. The TDshim can be built as follows: ```bash git clone https://github.com/confidential-containers/td-shim @@ -136,10 +136,10 @@ option as well. ```bash ./cloud-hypervisor \ - --platform tdx=on + --platform tdx=on \ --firmware td-shim/target/release/final.bin \ --kernel bzImage \ - --cmdline "root=/dev/vda3 console=hvc0 rw" + --cmdline "root=/dev/vda3 console=hvc0 rw" \ --cpus boot=1 \ --memory size=1G \ --disk path=tdx_guest_img @@ -150,10 +150,10 @@ TDShim: ```bash ./cloud-hypervisor \ - --platform tdx=on + --platform tdx=on \ --firmware td-shim/target/debug/final.bin \ --kernel bzImage \ - --cmdline "root=/dev/vda3 console=hvc0 rw" + --cmdline "root=/dev/vda3 console=hvc0 rw" \ --cpus boot=1 \ --memory size=1G \ --disk path=tdx_guest_img From 03252f5851353b2cc4fc4abf2ad1347a4e3207cf Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 7 Jan 2026 10:23:33 +0800 Subject: [PATCH 0452/1893] 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 --- docs/hotplug.md | 16 ++++++++-------- docs/io_throttling.md | 8 ++++---- docs/iommu.md | 12 ++++++------ docs/landlock.md | 10 +++++----- docs/live_migration.md | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/hotplug.md b/docs/hotplug.md index 86ba9ad631..35e9053611 100644 --- a/docs/hotplug.md +++ b/docs/hotplug.md @@ -110,7 +110,7 @@ Mem: 3.0Gi 71Mi 2.8Gi 0.0Ki 47Mi 2.8Gi Swap: 32Mi 0B 32Mi ``` -Due to guest OS limitations is is necessary to ensure that amount of memory added (between currently assigned RAM and that which is desired) is a multiple of 128MiB. +Due to guest OS limitations it is necessary to ensure that amount of memory added (between currently assigned RAM and that which is desired) is a multiple of 128MiB. The same API can also be used to reduce the desired RAM for a VM but the change will not be applied until the VM is rebooted. @@ -179,7 +179,7 @@ Notice the addition of `--api-socket=/tmp/ch-socket`. ### Add VFIO Device -To ask the VMM to add additional VFIO device then use the `add-device` API. +To ask the VMM to add additional VFIO device, use the `add-device` API. ```shell ./ch-remote --api-socket=/tmp/ch-socket add-device path=/sys/bus/pci/devices/0000:01:00.0/ @@ -187,7 +187,7 @@ To ask the VMM to add additional VFIO device then use the `add-device` API. ### Add Disk Device -To ask the VMM to add additional disk device then use the `add-disk` API. +To ask the VMM to add additional disk device, use the `add-disk` API. ```shell ./ch-remote --api-socket=/tmp/ch-socket add-disk path=/foo/bar/cloud.img @@ -195,7 +195,7 @@ To ask the VMM to add additional disk device then use the `add-disk` API. ### Add Fs Device -To ask the VMM to add additional fs device then use the `add-fs` API. +To ask the VMM to add additional fs device, use the `add-fs` API. ```shell ./ch-remote --api-socket=/tmp/ch-socket add-fs tag=myfs,socket=/foo/bar/virtiofs.sock @@ -203,7 +203,7 @@ To ask the VMM to add additional fs device then use the `add-fs` API. ### Add Net Device -To ask the VMM to add additional network device then use the `add-net` API. +To ask the VMM to add additional network device, use the `add-net` API. ```shell ./ch-remote --api-socket=/tmp/ch-socket add-net tap=chtap0 @@ -211,7 +211,7 @@ To ask the VMM to add additional network device then use the `add-net` API. ### Add Pmem Device -To ask the VMM to add additional PMEM device then use the `add-pmem` API. +To ask the VMM to add additional PMEM device, use the `add-pmem` API. ```shell ./ch-remote --api-socket=/tmp/ch-socket add-pmem file=/foo/bar.cloud.img @@ -219,7 +219,7 @@ To ask the VMM to add additional PMEM device then use the `add-pmem` API. ### Add Vsock Device -To ask the VMM to add additional vsock device then use the `add-vsock` API. +To ask the VMM to add additional vsock device, use the `add-vsock` API. ```shell ./ch-remote --api-socket=/tmp/ch-socket add-vsock cid=3,socket=/foo/bar/vsock.sock @@ -241,7 +241,7 @@ After a reboot the added PCI device will remain. ### Remove PCI device -Removing a PCI device works the same way for all kind of PCI devices. The unique identifier related to the device must be provided. This identifier can be provided by the user when adding the new device, or by default Cloud Hypervisor will assign one. +Removing a PCI device works the same way for all kinds of PCI devices. The unique identifier related to the device must be provided. This identifier can be provided by the user when adding the new device, or by default Cloud Hypervisor will assign one. ```shell ./ch-remote --api-socket=/tmp/ch-socket remove-device _disk0 diff --git a/docs/io_throttling.md b/docs/io_throttling.md index 76b0219564..22c5dfc6f7 100644 --- a/docs/io_throttling.md +++ b/docs/io_throttling.md @@ -27,11 +27,11 @@ Hypervisor provides another three options for limiting I/O operations, i.e., `ops_size` (I/O operations), `ops_one_time_burst` (I/O operations), and `ops_refill_time` (ms). -One caveat in the I/O throttling is that every-time the bucket gets +One caveat in the I/O throttling is that every time the bucket gets empty, it will stop I/O operations for a fixed amount of time (`cool_down_time`). The `cool_down_time` now is fixed at `100 ms`, it -can have big implications to the actual rate limit (which can be a lot -different the expected "refill-rate" derived from user inputs). For +can have big implications for the actual rate limit (which can be quite +different from the expected "refill-rate" derived from user inputs). For example, to have a 1000 IOPS limit on a virtio-blk device, users should be able to provide either of the following two options: `ops_size=1000,ops_refill_time=1000` or @@ -53,5 +53,5 @@ demonstrates how to throttle the aggregate bandwidth of two disks to 10 MiB/s. ``` --disk path=disk0.raw,rate_limit_group=group0 \ path=disk1.raw,rate_limit_group=group0 \ ---rate-limit-group bw_size=1048576,bw_refill_time,bw_refill_time=100 +--rate-limit-group bw_size=1048576,bw_refill_time=100 ``` diff --git a/docs/iommu.md b/docs/iommu.md index 21d1d30c47..cf7a640ef5 100644 --- a/docs/iommu.md +++ b/docs/iommu.md @@ -15,7 +15,7 @@ to increase the security regarding the memory accesses performed by the virtual devices (VIRTIO devices), on behalf of the guest drivers. With a virtual IOMMU, the VMM stands between the guest driver and its device -counterpart, validating and translating every address before to try accessing +counterpart, validating and translating every address before trying accessing the guest memory. This is standard interposition that is performed here by the VMM. @@ -75,8 +75,8 @@ Not all devices support this extra option, and the default value will always be `off` since we want to avoid the performance impact for most users who don't need this. -Refer to the command line `--help` to find out which device support to be -attached to the virtual IOMMU. +Refer to the command line `--help` to find out which devices can be supported +to be attached to the virtual IOMMU. Below is a simple example exposing the `virtio-blk` device as attached to the virtual IOMMU: @@ -128,7 +128,7 @@ When ACPI is disabled, virtual IOMMU is supported through Flattened Device Tree IOMMU-attached and which should not. No matter how many devices you attached to the virtual IOMMU by setting `iommu=on` option, all the devices on the PCI bus will be attached to the virtual IOMMU (except the IOMMU itself). Each of the -devices will be added into a IOMMU group. +devices will be added into an IOMMU group. As a result, the directory content of `/sys/kernel/iommu_groups` would be: @@ -151,7 +151,7 @@ of requests need to be issued in order to create large mappings. One use case is even more impacted by the slowdown, the nested VFIO case. When passing a device through a L2 guest, the VFIO driver running in L1 will update the DMAR entries for the specific device. Because VFIO pins the entire guest -memory, this means the entire mapping of the L2 guest need to be stored into +memory, this means the entire mapping of the L2 guest needs to be stored into multiple 4k mappings. Obviously, the bigger the L2 guest RAM is, the longer the update of the mappings will last. There is an additional problem happening in this case, if the L2 guest RAM is quite large, it will require a large number @@ -194,7 +194,7 @@ be consumed. ### Nested usage Let's now look at the specific example of nested virtualization. In order to -reach optimized performances, the L2 guest also need to be mapped based on +reach optimized performances, the L2 guest also needs to be mapped based on huge pages. Here is how to achieve this, assuming the physical device you are passing through is `0000:00:01.0`. diff --git a/docs/landlock.md b/docs/landlock.md index 571ddc7086..92ab0648d2 100644 --- a/docs/landlock.md +++ b/docs/landlock.md @@ -16,11 +16,11 @@ permissions. ## Host Setup -Landlock should be enabled in Host kernel to use it with cloud-hypervisor. -Please following [Kernel-Support](https://docs.kernel.org/userspace-api/landlock.html#kernel-support) link to enable Landlock on Host kernel. +Landlock should be enabled in host kernel to use it with cloud-hypervisor. +Please follow [Kernel-Support](https://docs.kernel.org/userspace-api/landlock.html#kernel-support) link to enable Landlock on Host kernel. -Landlock support can be checked with following command: +Landlock support can be checked with the following command: ``` $ sudo dmesg | grep -w landlock [ 0.000000] landlock: Up and running. @@ -30,8 +30,8 @@ Linux kernel confirms Landlock support with above message in dmesg. ## Enable Landlock At the time of enabling Landlock, Cloud-Hypervisor process needs the complete -list of files it accesses over its lifetime. So, Landlock is enabled `vm_create` -stage of guest boot. +list of files it accesses over its lifetime. So, Landlock is enabled at the +`vm_create` stage of guest boot. ### Command Line Append `--landlock` to Cloud-Hypervisor's command line to enable Landlock diff --git a/docs/live_migration.md b/docs/live_migration.md index 94c9afc236..ac5bf93f75 100644 --- a/docs/live_migration.md +++ b/docs/live_migration.md @@ -6,7 +6,7 @@ in Cloud Hypervisor: 1. local migration - migrating a VM from one Cloud Hypervisor instance to another on the same machine; 1. remote migration - migrating a VM between two machines; -> :warning: These examples place sockets /tmp. This is done for +> :warning: These examples place sockets in /tmp. This is done for > simplicity and should not be done in production. ## Local Migration (Suitable for Live Upgrade of VMM) From 5aba9b430806aec8751beddcae03fd566acfacf4 Mon Sep 17 00:00:00 2001 From: Stanislav Kinsburskii Date: Tue, 13 Jan 2026 08:03:25 -0800 Subject: [PATCH 0453/1893] 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 --- hypervisor/src/arch/x86/emulator/mod.rs | 28 ++++++++++++++++--------- hypervisor/src/mshv/mod.rs | 9 ++++++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/hypervisor/src/arch/x86/emulator/mod.rs b/hypervisor/src/arch/x86/emulator/mod.rs index 3de545e475..35a35a0ec4 100644 --- a/hypervisor/src/arch/x86/emulator/mod.rs +++ b/hypervisor/src/arch/x86/emulator/mod.rs @@ -538,16 +538,13 @@ impl Emulator<'_, T> { handler } - fn emulate_insn_stream( + pub fn emulate_insn_stream( &mut self, - cpu_id: usize, + old_state: &T, insn_stream: &[u8], num_insn: Option, ) -> EmulationResult { - let mut state = self - .platform - .cpu_state(cpu_id) - .map_err(EmulationError::PlatformEmulationError)?; + let mut state = old_state.clone(); let mut decoder = Decoder::new(64, insn_stream, DecoderOptions::NONE); let mut insn = Instruction::default(); let mut num_insn_emulated: usize = 0; @@ -627,7 +624,11 @@ impl Emulator<'_, T> { /// Emulate all instructions from the instructions stream. pub fn emulate(&mut self, cpu_id: usize, insn_stream: &[u8]) -> EmulationResult { - self.emulate_insn_stream(cpu_id, insn_stream, None) + let state = self + .platform + .cpu_state(cpu_id) + .map_err(EmulationError::PlatformEmulationError)?; + self.emulate_insn_stream(&state, insn_stream, None) } /// Only emulate the first instruction from the stream. @@ -640,7 +641,11 @@ impl Emulator<'_, T> { cpu_id: usize, insn_stream: &[u8], ) -> EmulationResult { - self.emulate_insn_stream(cpu_id, insn_stream, Some(1)) + let state = self + .platform + .cpu_state(cpu_id) + .map_err(EmulationError::PlatformEmulationError)?; + self.emulate_insn_stream(&state, insn_stream, Some(1)) } } @@ -706,10 +711,13 @@ mod mock_vmm { insn: &[u8], num_insn: Option, ) -> MockResult { - let ip = self.cpu_state(cpu_id).unwrap().ip(); + let mut state = self + .cpu_state(cpu_id) + .map_err(EmulationError::PlatformEmulationError)?; + let ip = state.ip(); let mut emulator = Emulator::new(self); - let new_state = emulator.emulate_insn_stream(cpu_id, insn, num_insn)?; + let new_state = emulator.emulate_insn_stream(&state, insn, num_insn)?; if num_insn.is_none() { assert_eq!(ip + insn.len() as u64, new_state.ip()); } diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 8ef150294c..43597ba6dc 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -715,14 +715,19 @@ impl cpu::Vcpu for MshvVcpu { map: (gva, gpa), }; + let old_state = context + .cpu_state(self.vp_index as usize) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; + // Create a new emulator. let mut emul = Emulator::new(&mut context); // Emulate the trapped instruction, and only the first one. let new_state = emul - .emulate_first_insn( - self.vp_index as usize, + .emulate_insn_stream( + &old_state, &info.instruction_bytes[..insn_len], + Some(1), ) .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; From de2d8f486b64d6c62e7fb30ae210d08baf5b83f1 Mon Sep 17 00:00:00 2001 From: Stanislav Kinsburskii Date: Tue, 13 Jan 2026 08:03:26 -0800 Subject: [PATCH 0454/1893] 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 --- hypervisor/src/arch/x86/emulator/mod.rs | 2 +- hypervisor/src/mshv/mod.rs | 2 +- hypervisor/src/mshv/x86_64/emulator.rs | 32 +++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/hypervisor/src/arch/x86/emulator/mod.rs b/hypervisor/src/arch/x86/emulator/mod.rs index 35a35a0ec4..4b60ceb716 100644 --- a/hypervisor/src/arch/x86/emulator/mod.rs +++ b/hypervisor/src/arch/x86/emulator/mod.rs @@ -711,7 +711,7 @@ mod mock_vmm { insn: &[u8], num_insn: Option, ) -> MockResult { - let mut state = self + let state = self .cpu_state(cpu_id) .map_err(EmulationError::PlatformEmulationError)?; let ip = state.ip(); diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 43597ba6dc..0c8d2632c2 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -733,7 +733,7 @@ impl cpu::Vcpu for MshvVcpu { // Set CPU state back. context - .set_cpu_state(self.vp_index as usize, new_state) + .update_cpu_state(self.vp_index as usize, old_state, new_state) .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; Ok(cpu::VmExit::Ignore) diff --git a/hypervisor/src/mshv/x86_64/emulator.rs b/hypervisor/src/mshv/x86_64/emulator.rs index 3000cdf59e..d668dedde2 100644 --- a/hypervisor/src/mshv/x86_64/emulator.rs +++ b/hypervisor/src/mshv/x86_64/emulator.rs @@ -106,6 +106,38 @@ impl MshvEmulatorContext<'_> { Ok(()) } + + pub fn update_cpu_state( + &self, + cpu_id: usize, + old_state: ::CpuState, + new_state: ::CpuState, + ) -> Result<(), PlatformError> { + if cpu_id != self.vcpu.vp_index as usize { + return Err(PlatformError::SetCpuStateFailure(anyhow!( + "CPU id mismatch {:?} {:?}", + cpu_id, + self.vcpu.vp_index + ))); + } + + debug!("mshv emulator: Updating CPU state"); + debug!("mshv emulator: {:#x?}", new_state.regs); + + self.vcpu + .set_regs(&new_state.regs) + .map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?; + + if old_state.sregs != new_state.sregs { + debug!("mshv emulator: Updating CPU special registers"); + debug!("mshv emulator: {:#x?}", new_state.sregs); + self.vcpu + .set_sregs(&new_state.sregs) + .map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?; + } + + Ok(()) + } } /// Platform emulation for Hyper-V From cb495959a8bea1b56e8fc82d15ba527a0e7fcf3c Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 6 Dec 2025 12:16:17 +0100 Subject: [PATCH 0455/1893] tests: qcow: Add testing for backing file in raw format Signed-off-by: Anatol Belski --- cloud-hypervisor/tests/integration.rs | 9 +++++++++ scripts/run_integration_tests_aarch64.sh | 10 ++++++++++ scripts/run_integration_tests_x86_64.sh | 10 ++++++++++ 3 files changed, 29 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 9f459267af..28a46f582c 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -49,6 +49,8 @@ mod x86_64 { "jammy-server-cloudimg-amd64-custom-20241017-0-backing-zstd.qcow2"; pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE: &str = "jammy-server-cloudimg-amd64-custom-20241017-0-backing-uncompressed.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-backing-raw.qcow2"; pub const WINDOWS_IMAGE_NAME: &str = "windows-server-2022-amd64-2.raw"; pub const OVMF_NAME: &str = "CLOUDHV.fd"; pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'IO-APIC.*ttyS0' /proc/interrupts || true"; @@ -74,6 +76,8 @@ mod aarch64 { "jammy-server-cloudimg-arm64-custom-20220329-0-backing-zstd.qcow2"; pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE: &str = "jammy-server-cloudimg-arm64-custom-20220329-0-backing-uncompressed.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-backing-raw.qcow2"; pub const WINDOWS_IMAGE_NAME: &str = "windows-11-iot-enterprise-aarch64.raw"; pub const OVMF_NAME: &str = "CLOUDHV_EFI.fd"; pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'GICv3.*uart-pl011' /proc/interrupts || true"; @@ -3574,6 +3578,11 @@ mod common_parallel { ); } + #[test] + fn test_virtio_block_qcow2_backing_raw_file() { + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE, false, false, true); + } + #[test] fn test_virtio_block_vhd() { let mut workload_path = dirs::home_dir().unwrap(); diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index c13d7b9ad5..f3fee60077 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -100,6 +100,16 @@ update_workloads() { popd || exit fi + JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0-backing-raw.qcow2" + JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME" + if [ ! -f "$JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img create -f qcow2 \ + -b "$JAMMY_OS_RAW_IMAGE" \ + -F raw $JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME + popd || exit + fi + ALPINE_MINIROOTFS_URL="http://dl-cdn.alpinelinux.org/alpine/v3.11/releases/aarch64/alpine-minirootfs-3.11.3-aarch64.tar.gz" ALPINE_MINIROOTFS_TARBALL="$WORKLOADS_DIR/alpine-minirootfs-aarch64.tar.gz" if [ ! -f "$ALPINE_MINIROOTFS_TARBALL" ]; then diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 32983672bb..5eb8531d4f 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -100,6 +100,16 @@ if [ ! -f "$JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE" ]; then popd || exit fi +JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-backing-raw.qcow2" +JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME" +if [ ! -f "$JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img create -f qcow2 \ + -b "$JAMMY_OS_RAW_IMAGE" \ + -F raw $JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME + popd || exit +fi + ALPINE_MINIROOTFS_URL="http://dl-cdn.alpinelinux.org/alpine/v3.11/releases/x86_64/alpine-minirootfs-3.11.3-x86_64.tar.gz" ALPINE_MINIROOTFS_TARBALL="$WORKLOADS_DIR/alpine-minirootfs-x86_64.tar.gz" if [ ! -f "$ALPINE_MINIROOTFS_TARBALL" ]; then From b3922dbd2c7c4ad45efcbd3ef1863c1430dd93db Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 1 Dec 2025 17:10:24 +0100 Subject: [PATCH 0456/1893] 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 --- block/src/qcow/mod.rs | 216 +++++++++++++++++++++++++++++++----------- 1 file changed, 160 insertions(+), 56 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 5e1ec8d548..cc3fb1d141 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -11,11 +11,12 @@ mod refcount; mod vec_cache; use std::cmp::{max, min}; +use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; use std::fs::OpenOptions; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::mem::size_of; use std::os::fd::{AsRawFd, RawFd}; -use std::str; +use std::str::{self, FromStr}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use libc::{EINVAL, EIO, ENOSPC}; @@ -111,6 +112,8 @@ pub enum Error { TooManyL1Entries(u64), #[error("Ref count table too large: {0}")] TooManyRefcounts(u64), + #[error("Unsupported backing file format: {0}")] + UnsupportedBackingFileFormat(String), #[error("Unsupported compression type")] UnsupportedCompressionType, #[error("Unsupported refcount order")] @@ -125,18 +128,46 @@ pub enum Error { pub type Result = std::result::Result; -#[derive(Copy, Clone)] +#[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 { + 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, +} + // Maximum data size supported. const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB. @@ -243,7 +274,7 @@ pub struct QcowHeader { pub compression_type: CompressionType, // Post-header entries - pub backing_file_path: Option, + pub backing_file: Option, } impl QcowHeader { @@ -307,7 +338,7 @@ impl QcowHeader { read_u32_from_file(f)? }, compression_type: CompressionType::Zlib, - backing_file_path: None, + backing_file: None, }; if version == 3 && header.header_size > V3_BARE_HEADER_SIZE { let raw_compression_type = read_u64_from_file(f)? >> (64 - 8); @@ -328,10 +359,9 @@ impl QcowHeader { let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize]; f.read_exact(&mut backing_file_name_bytes) .map_err(Error::ReadingHeader)?; - header.backing_file_path = Some( - String::from_utf8(backing_file_name_bytes) - .map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?, - ); + 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 }); } Ok(header) } @@ -407,7 +437,10 @@ impl QcowHeader { refcount_order: DEFAULT_REFCOUNT_ORDER, header_size, compression_type: CompressionType::Zlib, - backing_file_path: backing_file.map(String::from), + backing_file: backing_file.map(|path| BackingFileConfig { + path: String::from(path), + format: None, + }), }) } @@ -449,7 +482,7 @@ impl QcowHeader { write_u32_to_file(file, 0)?; // length of header extension data: 0 } - if let Some(backing_file_path) = self.backing_file_path.as_ref() { + if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) { write!(file, "{backing_file_path}").map_err(Error::WritingHeader)?; } @@ -479,6 +512,92 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u for_data + for_refcounts } +trait BackingFileOps: Send + Seek + Read { + fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { + self.seek(SeekFrom::Start(address))?; + self.read_exact(buf) + } + fn clone_box(&self) -> Box; +} + +impl BackingFileOps for QcowFile { + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl BackingFileOps for RawFile { + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +/// Backing file wrapper +struct BackingFile { + inner: Box, +} + +impl BackingFile { + fn new( + backing_file_config: Option<&BackingFileConfig>, + direct_io: bool, + max_nesting_depth: u32, + ) -> Result> { + let Some(config) = backing_file_config else { + return Ok(None); + }; + + // Check nesting depth - applies to any backing file + if max_nesting_depth == 0 { + return Err(Error::MaxNestingDepthExceeded); + } + + let backing_raw_file = OpenOptions::new() + .read(true) + .open(&config.path) + .map_err(Error::BackingFileIo)?; + + let mut raw_file = RawFile::new(backing_raw_file, direct_io); + + // Determine backing file format from header extension or auto-detect + let backing_format = match config.format { + Some(format) => format, + None => detect_image_type(&mut raw_file)?, + }; + + let inner: Box = match backing_format { + ImageType::Raw => Box::new(raw_file), + ImageType::Qcow2 => { + let backing_qcow = + QcowFile::from_with_nesting_depth(raw_file, max_nesting_depth - 1) + .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; + Box::new(backing_qcow) + } + }; + + Ok(Some(Self { inner })) + } + + #[inline] + fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { + self.inner.read_at(address, buf) + } +} + +impl Clone for BackingFile { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone_box(), + } + } +} + +impl Debug for BackingFile { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("BackingFile").finish() + } +} + /// Represents a qcow2 file. This is a sparse file format maintained by the qemu project. /// Full documentation of the format can be found in the qemu repository. /// @@ -509,7 +628,7 @@ pub struct QcowFile { // List of unreferenced clusters available to be used. unref clusters become available once the // removal of references to them have been synced to disk. avail_clusters: Vec, - backing_file: Option>, + backing_file: Option, } impl QcowFile { @@ -548,24 +667,8 @@ impl QcowFile { let direct_io = file.is_direct(); - let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() { - if max_nesting_depth == 0 { - return Err(Error::MaxNestingDepthExceeded); - } - let path = backing_file_path.clone(); - let backing_raw_file = OpenOptions::new() - .read(true) - .open(path) - .map_err(Error::BackingFileIo)?; - let backing_file = Self::from_with_nesting_depth( - RawFile::new(backing_raw_file, direct_io), - max_nesting_depth - 1, - ) - .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; - Some(Box::new(backing_file)) - } else { - None - }; + let backing_file = + BackingFile::new(header.backing_file.as_ref(), direct_io, max_nesting_depth)?; // Only support two byte refcounts. let refcount_bits: u64 = 0x01u64 @@ -692,28 +795,23 @@ impl QcowFile { QcowFile::new_from_header(file, &header) } - /// Creates a new QcowFile at the given path. + /// Creates a new QcowFile at the given path with a backing file. pub fn new_from_backing( file: RawFile, version: u32, - backing_file_name: &str, - backing_file_max_nesting_depth: u32, + backing_file_size: u64, + backing_config: &BackingFileConfig, ) -> Result { - let direct_io = file.is_direct(); - let backing_raw_file = OpenOptions::new() - .read(true) - .open(backing_file_name) - .map_err(Error::BackingFileIo)?; - let backing_file = Self::from_with_nesting_depth( - RawFile::new(backing_raw_file, direct_io), - backing_file_max_nesting_depth, - ) - .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; - let size = backing_file.virtual_size(); - let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?; - let mut result = QcowFile::new_from_header(file, &header)?; - result.backing_file = Some(Box::new(backing_file)); - Ok(result) + let mut header = QcowHeader::create_for_size_and_path( + version, + backing_file_size, + Some(&backing_config.path), + )?; + if let Some(backing_file) = &mut header.backing_file { + backing_file.format = backing_config.format; + } + QcowFile::new_from_header(file, &header) + // backing_file is loaded by new_from_header -> Self::from() based on the header } fn new_from_header(mut file: RawFile, header: &QcowHeader) -> Result { @@ -741,7 +839,9 @@ impl QcowFile { } pub fn set_backing_file(&mut self, backing: Option>) { - self.backing_file = backing; + self.backing_file = backing.map(|b| BackingFile { + inner: Box::new(*b), + }); } /// Returns the `QcowHeader` for this file. @@ -1246,8 +1346,7 @@ impl QcowFile { let cluster_size = self.raw_file.cluster_size(); let cluster_begin = address - (address % cluster_size); let mut cluster_data = vec![0u8; cluster_size as usize]; - backing.seek(SeekFrom::Start(cluster_begin))?; - backing.read_exact(&mut cluster_data)?; + backing.read_at(cluster_begin, &mut cluster_data)?; Some(cluster_data) } else { None @@ -1664,8 +1763,7 @@ impl Read for QcowFile { if (self.file_read(curr_addr, count, &mut buf[nread..(nread + count)])?).is_some() { // Data is successfully read from the cluster } else if let Some(backing) = self.backing_file.as_mut() { - backing.seek(SeekFrom::Start(curr_addr))?; - backing.read_exact(&mut buf[nread..(nread + count)])?; + backing.read_at(curr_addr, &mut buf[nread..(nread + count)])?; } else { // Previously unwritten region, return zeros for b in &mut buf[nread..(nread + count)] { @@ -2147,10 +2245,13 @@ mod unit_tests { disk_file.rewind().unwrap(); let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header."); assert_eq!( - header.backing_file_path, + header.backing_file.as_ref().map(|bf| bf.path.clone()), Some(String::from("/my/path/to/a/file")) ); - assert_eq!(read_header.backing_file_path, header.backing_file_path); + assert_eq!( + read_header.backing_file.as_ref().map(|bf| &bf.path), + header.backing_file.as_ref().map(|bf| &bf.path) + ); } #[test] @@ -2164,10 +2265,13 @@ mod unit_tests { disk_file.rewind().unwrap(); let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header."); assert_eq!( - header.backing_file_path, + header.backing_file.as_ref().map(|bf| bf.path.clone()), Some(String::from("/my/path/to/a/file")) ); - assert_eq!(read_header.backing_file_path, header.backing_file_path); + assert_eq!( + read_header.backing_file.as_ref().map(|bf| &bf.path), + header.backing_file.as_ref().map(|bf| &bf.path) + ); } #[test] From 10394da0c409f21f140d4541b0390189e6a1d7a2 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 15 Dec 2025 23:13:43 +0100 Subject: [PATCH 0457/1893] 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 --- performance-metrics/src/main.rs | 32 +++++++++++++++++++- performance-metrics/src/performance_tests.rs | 16 ++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index e25a84d34f..e99a7b097f 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -319,7 +319,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 32] = [ +const TEST_LIST: [PerformanceTest; 34] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -740,6 +740,36 @@ const TEST_LIST: [PerformanceTest; 32] = [ }, unit_adjuster: adjuster::Bps_to_MiBps, }, + PerformanceTest { + name: "block_qcow2_backing_raw_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: OVERLAY_WITH_RAW_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_backing_raw_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: OVERLAY_WITH_RAW_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 1407037017..573db69aef 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -35,6 +35,8 @@ enum Error { pub const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img"; const QCOW2_BACKING_FILE: &str = "/var/tmp/ch-blk-io-test-qcow2-backing.qcow2"; pub const OVERLAY_WITH_QCOW2_BACKING: &str = "/var/tmp/ch-blk-io-test-overlay-qcow2.qcow2"; +const RAW_BACKING_FILE: &str = "/var/tmp/ch-blk-io-test-raw-backing.raw"; +pub const OVERLAY_WITH_RAW_BACKING: &str = "/var/tmp/ch-blk-io-test-overlay-raw.qcow2"; pub fn init_tests(overrides: &PerformanceTestOverrides) { let mut cmd = format!("dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096"); @@ -66,6 +68,16 @@ pub fn init_tests(overrides: &PerformanceTestOverrides) { "qemu-img create -f qcow2 -b {QCOW2_BACKING_FILE} -F qcow2 {OVERLAY_WITH_QCOW2_BACKING} 4G" ); assert!(exec_host_command_output(&cmd).status.success()); + + // RAW backing file for backing file tests + cmd = format!("dd if=/dev/zero of={RAW_BACKING_FILE} bs=1M count=4096"); + assert!(exec_host_command_output(&cmd).status.success()); + + // QCOW2 overlay with RAW backing + cmd = format!( + "qemu-img create -f qcow2 -b {RAW_BACKING_FILE} -F raw {OVERLAY_WITH_RAW_BACKING} 4G" + ); + assert!(exec_host_command_output(&cmd).status.success()); } pub fn cleanup_tests() { @@ -75,6 +87,10 @@ pub fn cleanup_tests() { .unwrap_or_else(|_| panic!("Failed to remove file '{QCOW2_BACKING_FILE}'.")); fs::remove_file(OVERLAY_WITH_QCOW2_BACKING) .unwrap_or_else(|_| panic!("Failed to remove file '{OVERLAY_WITH_QCOW2_BACKING}'.")); + fs::remove_file(RAW_BACKING_FILE) + .unwrap_or_else(|_| panic!("Failed to remove file '{RAW_BACKING_FILE}'.")); + fs::remove_file(OVERLAY_WITH_RAW_BACKING) + .unwrap_or_else(|_| panic!("Failed to remove file '{OVERLAY_WITH_RAW_BACKING}'.")); } // Performance tests are expected to be executed sequentially, so we can From 9eb2b9b0e579094d86fa2d417bc6a5473689f43c Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 12 Jan 2026 15:09:14 +0100 Subject: [PATCH 0458/1893] 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 --- block/src/qcow/mod.rs | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index cc3fb1d141..c3a2041d1e 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -202,6 +202,11 @@ const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1; const COMPRESSION_TYPE_ZLIB: u64 = 0; // zlib/deflate const COMPRESSION_TYPE_ZSTD: u64 = 1; // zstd +// Header extension types +const HEADER_EXT_END: u32 = 0x00000000; +// Backing file format name (raw, qcow2) +const HEADER_EXT_BACKING_FORMAT: u32 = 0xe2792aca; + // The format supports a "header extension area", that crosvm does not use. const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8; @@ -278,6 +283,46 @@ pub struct QcowHeader { } impl QcowHeader { + fn read_header_extensions(f: &mut RawFile, header: &mut QcowHeader) -> Result<()> { + // Extensions start directly after the header + f.seek(SeekFrom::Start(header.header_size as u64)) + .map_err(Error::ReadingHeader)?; + + loop { + let ext_type = f.read_u32::().map_err(Error::ReadingHeader)?; + if ext_type == HEADER_EXT_END { + break; + } + + let ext_length = f.read_u32::().map_err(Error::ReadingHeader)?; + + match ext_type { + HEADER_EXT_BACKING_FORMAT => { + let mut format_bytes = vec![0u8; ext_length as usize]; + f.read_exact(&mut format_bytes) + .map_err(Error::ReadingHeader)?; + 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()?); + } + } + _ => { + // Skip unknown extension + f.seek(SeekFrom::Current(ext_length as i64)) + .map_err(Error::ReadingHeader)?; + } + } + + // Skip to the next 8 byte boundary + let padding = (8 - (ext_length % 8)) % 8; + f.seek(SeekFrom::Current(padding as i64)) + .map_err(Error::ReadingHeader)?; + } + + Ok(()) + } + /// Creates a QcowHeader from a reference to a file. pub fn new(f: &mut RawFile) -> Result { f.rewind().map_err(Error::ReadingHeader)?; @@ -363,6 +408,11 @@ impl QcowHeader { .map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?; header.backing_file = Some(BackingFileConfig { path, format: None }); } + + if version == 3 && header.header_size > V3_BARE_HEADER_SIZE { + Self::read_header_extensions(f, &mut header)?; + } + Ok(header) } From 3fed706d6a4412426d14793393eeded667ac1bb5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 12 Jan 2026 15:10:31 +0100 Subject: [PATCH 0459/1893] 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 --- block/src/qcow/mod.rs | 132 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 3 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index c3a2041d1e..ca0262924a 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -451,10 +451,13 @@ impl QcowHeader { Ok(QcowHeader { magic: QCOW_MAGIC, version, - backing_file_offset: (if backing_file.is_none() { - 0 - } else { + 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, @@ -528,11 +531,20 @@ impl QcowHeader { write_u64_to_file(file, self.autoclear_features)?; write_u32_to_file(file, self.refcount_order)?; write_u32_to_file(file, self.header_size)?; + + if self.header_size > V3_BARE_HEADER_SIZE { + write_u64_to_file(file, 0)?; // no compression + } + write_u32_to_file(file, 0)?; // header extension type: end of header extension area write_u32_to_file(file, 0)?; // length of header extension data: 0 } if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) { + if self.backing_file_offset > 0 { + file.seek(SeekFrom::Start(self.backing_file_offset)) + .map_err(Error::WritingHeader)?; + } write!(file, "{backing_file_path}").map_err(Error::WritingHeader)?; } @@ -2324,6 +2336,120 @@ mod unit_tests { ); } + /// Helper to create a test file with header extensions + fn create_header_with_extension(ext_type: u32, ext_data: &[u8]) -> (RawFile, QcowHeader) { + let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None) + .expect("Failed to create header."); + + let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false); + header.write_to(&mut disk_file).unwrap(); + + // Write extension + disk_file + .seek(SeekFrom::Start(header.header_size as u64)) + .unwrap(); + disk_file.write_u32::(ext_type).unwrap(); + disk_file + .write_u32::(ext_data.len() as u32) + .unwrap(); + disk_file.write_all(ext_data).unwrap(); + + // Add padding to 8-byte boundary + let padding = (8 - (ext_data.len() % 8)) % 8; + if padding > 0 { + disk_file.write_all(&vec![0u8; padding]).unwrap(); + } + + disk_file.write_u32::(HEADER_EXT_END).unwrap(); + + disk_file.rewind().unwrap(); + + (disk_file, header) + } + + #[test] + fn read_header_extensions_unknown_extension() { + let (mut disk_file, mut header) = create_header_with_extension( + 0x12345678, // unknown type + "test".as_bytes(), + ); + + // Extension parsing needs a backing file to set format on + header.backing_file = Some(BackingFileConfig { + path: "/test/backing".to_string(), + format: None, + }); + + QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap(); + assert_eq!(header.backing_file.as_ref().and_then(|bf| bf.format), None); + } + + #[test] + fn read_header_extensions_raw_format() { + let (mut disk_file, mut header) = + create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "raw".as_bytes()); + + header.backing_file = Some(BackingFileConfig { + path: "/test/backing".to_string(), + format: None, + }); + + QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap(); + assert_eq!( + header.backing_file.as_ref().and_then(|bf| bf.format), + Some(ImageType::Raw) + ); + } + + #[test] + fn read_header_extensions_qcow2_format() { + let (mut disk_file, mut header) = + create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "qcow2".as_bytes()); + + header.backing_file = Some(BackingFileConfig { + path: "/test/backing".to_string(), + format: None, + }); + + QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap(); + assert_eq!( + header.backing_file.as_ref().and_then(|bf| bf.format), + Some(ImageType::Qcow2) + ); + } + + #[test] + fn read_header_extensions_invalid_format() { + let (mut disk_file, mut header) = + create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "vmdk".as_bytes()); + + header.backing_file = Some(BackingFileConfig { + path: "/test/backing".to_string(), + format: None, + }); + + let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header); + assert!(matches!( + result.unwrap_err(), + Error::UnsupportedBackingFileFormat(_) + )); + } + + #[test] + fn read_header_extensions_invalid_utf8() { + let (mut disk_file, mut header) = create_header_with_extension( + HEADER_EXT_BACKING_FORMAT, + &[0xFF, 0xFE, 0xFD], // invalid UTF-8 + ); + + let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header); + // Should fail with InvalidBackingFileName error + assert!(matches!( + result.unwrap_err(), + Error::InvalidBackingFileName(_) + )); + } + #[test] fn no_backing_file() { // `backing_file` is `None` From 4f69cf604a66b7ab1529f7ca097dee03a3fbf204 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 19 Dec 2025 12:42:41 +0100 Subject: [PATCH 0460/1893] 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 --- Cargo.lock | 1 + cloud-hypervisor/Cargo.toml | 1 + cloud-hypervisor/tests/integration.rs | 186 +++++++++++++++++++++++--- 3 files changed, 170 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd1223bb80..7d576496de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -418,6 +418,7 @@ version = "50.0.0" dependencies = [ "anyhow", "api_client", + "block", "clap", "dhat", "dirs", diff --git a/cloud-hypervisor/Cargo.toml b/cloud-hypervisor/Cargo.toml index 0dd1f4788a..739f181d2d 100644 --- a/cloud-hypervisor/Cargo.toml +++ b/cloud-hypervisor/Cargo.toml @@ -41,6 +41,7 @@ vmm-sys-util = { workspace = true } zbus = { version = "5.13.1", optional = true } [dev-dependencies] +block = { path = "../block" } dirs = { workspace = true } net_util = { path = "../net_util" } serde_json = { workspace = true } diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 28a46f582c..c8b31030fa 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2527,8 +2527,9 @@ EOF } mod common_parallel { - use std::fs::OpenOptions; - use std::io::SeekFrom; + use std::cmp; + use std::fs::{File, OpenOptions}; + use std::io::{self, SeekFrom}; use crate::*; @@ -3425,6 +3426,12 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); + let initial_backing_checksum = if verify_os_disk { + compute_backing_checksum(guest.disk_config.disk(DiskType::OperatingSystem).unwrap()) + } else { + None + }; + let mut cloud_child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) .args(["--memory", "size=512M,shared=on"]) @@ -3498,7 +3505,10 @@ mod common_parallel { handle_child_output(r, &output); if verify_os_disk { - disk_check_consistency(guest.disk_config.disk(DiskType::OperatingSystem).unwrap()); + disk_check_consistency( + guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), + initial_backing_checksum, + ); } } @@ -3517,16 +3527,24 @@ mod common_parallel { _test_virtio_block(FOCAL_IMAGE_NAME, true, true, false); } - /// Uses `qemu-img check` to verify disk image consistency. - /// - /// Supported formats are `qcow2` (compressed and uncompressed), - /// `vhdx`, `qed`, `parallels`, `vmdk`, and `vdi`. See man page - /// for more details. - /// - /// It takes either a full path to the image or just the name of - /// the image located in the `workloads` directory. - fn disk_check_consistency(path_or_image_name: impl AsRef) { - let path = if path_or_image_name.as_ref().exists() { + fn run_qemu_img(path: &std::path::Path, args: &[&str]) -> std::process::Output { + std::process::Command::new("qemu-img") + .arg(args[0]) + .args(&args[1..]) + .arg(path.to_str().unwrap()) + .output() + .unwrap() + } + + fn get_image_info(path: &std::path::Path) -> Option { + let output = run_qemu_img(path, &["info", "--output=json"]); + + output.status.success().then(|| ())?; + serde_json::from_slice(&output.stdout).ok() + } + + fn resolve_disk_path(path_or_image_name: impl AsRef) -> std::path::PathBuf { + if path_or_image_name.as_ref().exists() { // A full path is provided path_or_image_name.as_ref().to_path_buf() } else { @@ -3534,18 +3552,150 @@ mod common_parallel { let mut workload_path = dirs::home_dir().unwrap(); workload_path.push("workloads"); workload_path.as_path().join(path_or_image_name.as_ref()) + } + } + + fn compute_file_checksum(reader: &mut dyn std::io::Read, size: u64) -> u32 { + // Read first 16MB or entire data if smaller + let read_size = cmp::min(size, 16 * 1024 * 1024) as usize; + + let mut buffer = vec![0u8; read_size]; + reader.read_exact(&mut buffer).unwrap(); + + // DJB2 hash + let mut hash: u32 = 5381; + for byte in buffer.iter() { + hash = hash.wrapping_mul(33).wrapping_add(*byte as u32); + } + hash + } + + #[test] + fn test_compute_file_checksum_empty() { + let mut reader = io::Cursor::new(vec![]); + let checksum = compute_file_checksum(&mut reader, 0); + assert_eq!(checksum, 5381); + } + + #[test] + fn test_compute_file_checksum_small() { + let data = b"hello world"; + let mut reader = io::Cursor::new(data); + let checksum = compute_file_checksum(&mut reader, data.len() as u64); + assert_eq!(checksum, 894552257); + } + + #[test] + fn test_compute_file_checksum_same_data() { + let data = b"test data 123"; + let mut reader1 = io::Cursor::new(data); + let mut reader2 = io::Cursor::new(data); + let checksum1 = compute_file_checksum(&mut reader1, data.len() as u64); + let checksum2 = compute_file_checksum(&mut reader2, data.len() as u64); + assert_eq!(checksum1, checksum2); + } + + #[test] + fn test_compute_file_checksum_different_data() { + let data1 = b"data1"; + let data2 = b"data2"; + let mut reader1 = io::Cursor::new(data1); + let mut reader2 = io::Cursor::new(data2); + let checksum1 = compute_file_checksum(&mut reader1, data1.len() as u64); + let checksum2 = compute_file_checksum(&mut reader2, data2.len() as u64); + assert_ne!(checksum1, checksum2); + } + + #[test] + fn test_compute_file_checksum_large_data() { + let size = 20 * 1024 * 1024; + let data = vec![0xABu8; size]; + let mut reader = io::Cursor::new(data); + let checksum = compute_file_checksum(&mut reader, size as u64); + // Should only read first 16MB + assert!(checksum != 5381); + + // Verify only 16MB was read + let position = reader.position(); + assert_eq!(position, 16 * 1024 * 1024); + } + + fn compute_backing_checksum( + path_or_image_name: impl AsRef, + ) -> Option<(std::path::PathBuf, String, u32)> { + let path = resolve_disk_path(path_or_image_name); + + let mut file = File::open(&path).ok()?; + if !matches!( + block::detect_image_type(&mut file).ok()?, + block::ImageType::Qcow2 + ) { + return None; + } + + let info = get_image_info(&path)?; + + let backing_file = info["backing-filename"].as_str()?; + let backing_path = if std::path::Path::new(backing_file).is_absolute() { + std::path::PathBuf::from(backing_file) + } else { + path.parent() + .unwrap_or_else(|| std::path::Path::new(".")) + .join(backing_file) }; - let output = std::process::Command::new("qemu-img") - .args(["check", path.to_str().unwrap()]) - .output() - .expect("should spawn and run command successfully"); + let backing_info = get_image_info(&backing_path)?; + let backing_format = backing_info["format"].as_str()?.to_string(); + let mut file = File::open(&backing_path).ok()?; + let file_size = file.metadata().ok()?.len(); + let checksum = compute_file_checksum(&mut file, file_size); + + Some((backing_path, backing_format, checksum)) + } + + /// Uses `qemu-img check` to verify disk image consistency. + /// + /// Supported formats are `qcow2` (compressed and uncompressed), + /// `vhdx`, `qed`, `parallels`, `vmdk`, and `vdi`. See man page + /// for more details. + /// + /// It takes either a full path to the image or just the name of + /// the image located in the `workloads` directory. + /// + /// For qcow2 images with backing files, also verifies the backing file + /// integrity and checks that the backing file hasn't been modified + /// during the test. + fn disk_check_consistency( + path_or_image_name: impl AsRef, + initial_backing_checksum: Option<(std::path::PathBuf, String, u32)>, + ) { + let path = resolve_disk_path(path_or_image_name); + let output = run_qemu_img(&path, &["check"]); assert!( output.status.success(), "qemu-img check failed: {}", String::from_utf8_lossy(&output.stderr) ); + + if let Some((backing_path, format, initial_checksum)) = initial_backing_checksum { + if format.parse::().ok() != Some(block::qcow::ImageType::Raw) { + let output = run_qemu_img(&backing_path, &["check"]); + + assert!( + output.status.success(), + "qemu-img check of backing file failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + let mut file = File::open(&backing_path).unwrap(); + let file_size = file.metadata().unwrap().len(); + assert_eq!( + initial_checksum, + compute_file_checksum(&mut file, file_size) + ); + } } #[test] @@ -3710,7 +3860,7 @@ mod common_parallel { handle_child_output(r, &output); - disk_check_consistency(vhdx_path); + disk_check_consistency(vhdx_path, None); } fn vhdx_image_size(disk_name: &str) -> u64 { From dee79935387e42633d75ec57a779116fd361c75d Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Thu, 15 Jan 2026 12:44:08 +0100 Subject: [PATCH 0461/1893] docs: fix gdb path option name Fixes: fa22cb0be ("docs: update command line options to use clap") Signed-off-by: Alyssa Ross --- docs/gdb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gdb.md b/docs/gdb.md index 49449b0192..d9d90ec584 100644 --- a/docs/gdb.md +++ b/docs/gdb.md @@ -8,7 +8,7 @@ To enable debugging with GDB, build with the `guest_debug` feature enabled: cargo build --features guest_debug ``` -To use the `--gdb` option, specify the Unix Domain Socket with `--path` that Cloud Hypervisor will use to communicate with the host's GDB: +To use the `--gdb` option, specify the Unix Domain Socket with `path` that Cloud Hypervisor will use to communicate with the host's GDB: ```bash ./cloud-hypervisor \ From cde7856d9e31b47d51a520efe2b6c2996446a638 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 16 Jan 2026 14:24:06 +0100 Subject: [PATCH 0462/1893] 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 --- performance-metrics/src/main.rs | 56 ++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index e99a7b097f..e27161683f 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -173,6 +173,7 @@ pub struct BlockControl { pub struct PerformanceTestControl { test_timeout: u32, test_iterations: u32, + warmup_iterations: u32, num_queues: Option, queue_size: Option, net_control: Option<(bool, bool)>, // First bool is for RX(true)/TX(false), second bool is for bandwidth or PPS @@ -183,8 +184,8 @@ pub struct PerformanceTestControl { impl fmt::Display for PerformanceTestControl { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut output = format!( - "test_timeout = {}s, test_iterations = {}", - self.test_timeout, self.test_iterations + "test_timeout = {}s, test_iterations = {}, warmup_iterations = {}", + self.test_timeout, self.test_iterations, self.warmup_iterations ); if let Some(o) = self.num_queues { output = format!("{output}, num_queues = {o}"); @@ -212,6 +213,7 @@ impl PerformanceTestControl { Self { test_timeout: 10, test_iterations: 5, + warmup_iterations: 0, num_queues: None, queue_size: None, net_control: None, @@ -233,6 +235,17 @@ struct PerformanceTest { impl PerformanceTest { pub fn run(&self, overrides: &PerformanceTestOverrides) -> PerformanceTestResult { + // Run warmup iterations if configured (results discarded) + for _ in 0..self.control.warmup_iterations { + if let Some(test_timeout) = overrides.test_timeout { + let mut control: PerformanceTestControl = self.control.clone(); + control.test_timeout = test_timeout; + let _ = (self.func_ptr)(&control); + } else { + let _ = (self.func_ptr)(&self.control); + } + } + let mut metrics = Vec::new(); for _ in 0..overrides .test_iterations @@ -265,8 +278,9 @@ impl PerformanceTest { // Calculate the timeout for each test // Note: To cover the setup/cleanup time, 20s is added for each iteration of the test pub fn calc_timeout(&self, test_iterations: &Option, test_timeout: &Option) -> u64 { - ((test_timeout.unwrap_or(self.control.test_timeout) + 20) - * test_iterations.unwrap_or(self.control.test_iterations)) as u64 + let total_iterations = test_iterations.unwrap_or(self.control.test_iterations) + + self.control.warmup_iterations; + ((test_timeout.unwrap_or(self.control.test_timeout) + 20) * total_iterations) as u64 } } @@ -319,7 +333,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 34] = [ +const TEST_LIST: [PerformanceTest; 36] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -770,6 +784,38 @@ const TEST_LIST: [PerformanceTest; 34] = [ }, unit_adjuster: adjuster::Bps_to_MiBps, }, + PerformanceTest { + name: "block_qcow2_backing_qcow2_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: OVERLAY_WITH_QCOW2_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_backing_raw_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: OVERLAY_WITH_RAW_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, ]; fn run_test_with_timeout( From ccf0b9ec7d6f53820dd6d2a81dfcb1ca98c44afb Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Fri, 2 Jan 2026 23:39:21 +0000 Subject: [PATCH 0463/1893] 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 --- vmm/src/acpi.rs | 252 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 181 insertions(+), 71 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 563a5f2e19..3554e869ee 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -514,86 +514,196 @@ fn create_dbg2_table(base_address: u64) -> Sdt { dbg2 } +#[cfg(target_arch = "aarch64")] +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(Default, IntoBytes, Immutable, FromBytes)] +struct IortBodyBase { + pub num_nodes: u32, + pub offset_first_node: u32, + _reserved: u32, +} + +#[cfg(target_arch = "aarch64")] +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(Default, IntoBytes, Immutable, FromBytes)] +struct IortNodeCommon { + pub type_: u8, + pub length: u16, + pub revision: u8, + pub node_id: u32, + pub num_id_mappings: u32, + pub id_mappings_array_offset: u32, +} + +#[cfg(target_arch = "aarch64")] +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(Default, IntoBytes, Immutable, FromBytes)] +struct IortIdMapping { + pub input_base: u32, + pub num_ids: u32, + pub output_base: u32, + pub output_reference: u32, + pub flags: u32, +} + +#[cfg(target_arch = "aarch64")] +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(Default, IntoBytes, Immutable, FromBytes)] +struct IortMemoryAccessProperties { + pub cca: u32, + pub ah: u8, + _reserved: u16, + pub maf: u8, +} + +#[cfg(target_arch = "aarch64")] +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(Default, IntoBytes, Immutable, FromBytes)] +struct IortItsGroupBase { + pub common: IortNodeCommon, + pub its_count: u32, + // GIC ITS identifiers follow: array of `u32` +} + +#[cfg(target_arch = "aarch64")] +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(Default, IntoBytes, Immutable, FromBytes)] +struct IortPciRootComplexBase { + pub common: IortNodeCommon, + pub mem_access_props: IortMemoryAccessProperties, + pub ats_attribute: u32, + pub pci_segment_number: u32, + pub memory_address_size_limit: u8, + _reserved: [u8; 3], + // ID mappings follow: array of `struct IortIdMapping` +} + +#[cfg(target_arch = "aarch64")] +#[inline] +fn align_to_8_bytes(len: usize) -> usize { + (8 - (len % 8)) % 8 +} + #[cfg(target_arch = "aarch64")] // Generate IORT table based on Spec Revision E.b: // https://developer.arm.com/documentation/den0049/eb/?lang=en fn create_iort_table(pci_segments: &[PciSegment]) -> Sdt { + const ACPI_IORT_HEADER_SIZE: u32 = 36; + const ACPI_IORT_REVISION: u8 = 3; const ACPI_IORT_NODE_ITS_GROUP: u8 = 0x00; const ACPI_IORT_NODE_PCI_ROOT_COMPLEX: u8 = 0x02; - const ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET: usize = 72; - const ACPI_IORT_NODE_ROOT_COMPLEX_SIZE: usize = 60; + + // IORT header + let mut iort = Sdt::new( + *b"IORT", + ACPI_IORT_HEADER_SIZE, + ACPI_IORT_REVISION, + *b"CLOUDH", + *b"CHIORT ", + 1, + ); + assert_eq!(iort.len(), ACPI_IORT_HEADER_SIZE as usize); // The IORT table contains: - // - Header (size = 40) - // - 1 x ITS Group Node (size = 24) - // - N x Root Complex Node (N = number of pci segments, size = 60 x N) - let iort_table_size: u32 = (ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET - + ACPI_IORT_NODE_ROOT_COMPLEX_SIZE * pci_segments.len()) - as u32; - let mut iort = Sdt::new(*b"IORT", iort_table_size, 3, *b"CLOUDH", *b"CHIORT ", 1); - iort.write(36, ((1 + pci_segments.len()) as u32).to_le()); - iort.write(40, (48u32).to_le()); - - // ITS group node - iort.write(48, ACPI_IORT_NODE_ITS_GROUP); - // Length of the ITS group node in bytes - iort.write(49, (24u16).to_le()); - // Revision - iort.write(51, (1u8).to_le()); - // ITS counts - iort.write(64, (1u32).to_le()); - // GIC ITS Identity Array - iort.write(68, (0u32).to_le()); // Value must match what's defined in MADT - - // Root Complex Nodes - for (i, segment) in pci_segments.iter().enumerate() { - let node_offset: usize = - ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET + i * ACPI_IORT_NODE_ROOT_COMPLEX_SIZE; - iort.write(node_offset, ACPI_IORT_NODE_PCI_ROOT_COMPLEX); - // Length of the root complex node in bytes - iort.write( - node_offset + 1, - (ACPI_IORT_NODE_ROOT_COMPLEX_SIZE as u16).to_le(), - ); - // Revision - iort.write(node_offset + 3, (3u8).to_le()); - // Node ID - iort.write(node_offset + 4, (segment.id as u32).to_le()); - // Mapping counts - iort.write(node_offset + 8, (1u32).to_le()); - // Offset from the start of the RC node to the start of its Array of ID mappings - iort.write(node_offset + 12, (36u32).to_le()); - // Fully coherent device - iort.write(node_offset + 16, (1u32).to_le()); - // CCA = CPM = DCAS = 1 - iort.write(node_offset + 23, 3u8); - // PCI segment number - iort.write(node_offset + 28, (segment.id as u32).to_le()); - // Memory address size limit - iort.write(node_offset + 32, (64u8).to_le()); - - // From offset 32 onward is the space for ID mappings Array. - // Now we have only one mapping. - let mapping_offset: usize = node_offset + 36; - // The lowest value in the input range - iort.write(mapping_offset, (0u32).to_le()); - // The number of IDs in the range minus one: - // This should cover all the devices of a segment: - // 1 (bus) x 32 (devices) x 8 (functions) = 256 - // Note: Currently only 1 bus is supported in a segment. - iort.write(mapping_offset + 4, (255_u32).to_le()); - // Output base maps to ITS device IDs which must match the - // device ID encoding used in KVM MSI routing setup, which - // shares the same limitation - only 1 bus per segment and - // up to 256 segments. - // See: https://github.com/cloud-hypervisor/cloud-hypervisor/commit/c9374d87ac453d49185aa7b734df089444166484 + // - IortBodyBase + // - 1 x ITS Group Node + // - N x PCI Root Complex Node (N = number of pci segments) + let num_nodes = (1 + pci_segments.len()) as u32; + // First node is the ITS Group Node located right after the IORT Body Base + let offset_its_node = iort.len() + std::mem::size_of::(); + assert!(align_to_8_bytes(offset_its_node) == 0); // Ensure the ITS node is 8-byte aligned + iort.append(IortBodyBase { + num_nodes, + offset_first_node: offset_its_node as u32, + _reserved: 0, + }); + assert!(iort.len() == offset_its_node); + + // ITS Group Node contains: + // - IortItsGroupBase + // - ITS Identifiers Array: Array of u32 ITS IDs + // Currently contains a single ITS with ID 0, which matches the + // `translation_id` field of the `GisIts`` structure in the MADT table. + let its_id_array = [0u32; 1]; + let its_count = its_id_array.len(); + let its_group_node_size = + std::mem::size_of::() + its_count * std::mem::size_of::(); + let padding = align_to_8_bytes(iort.len() + its_group_node_size); + iort.append(IortItsGroupBase { + common: IortNodeCommon { + type_: ACPI_IORT_NODE_ITS_GROUP, + length: (its_group_node_size + padding) as u16, + revision: 1, + node_id: 0, // todo + num_id_mappings: 0, + id_mappings_array_offset: 0, + }, + its_count: its_count as u32, + }); + iort.append(its_id_array); + iort.append_slice(&vec![0u8; padding]); // Add padding to align to 8 bytes + + // Create PCI Root Complex Node for each PCI segment + for segment in pci_segments.iter() { + assert!(align_to_8_bytes(iort.len()) == 0); // Ensure each node is 8-byte aligned + + // Each PCI Root Complex Node contains: + // - IortPciRootComplexBase + // - ID mapping Array: Array of IortIdMapping + // Currently contains a single mapping that maps all device IDs + // in the segment to the ITS Group Node. + let num_id_mappings = 1; + let node_size = std::mem::size_of::() + + num_id_mappings * std::mem::size_of::(); + let padding = align_to_8_bytes(iort.len() + node_size); + iort.append(IortPciRootComplexBase { + common: IortNodeCommon { + type_: ACPI_IORT_NODE_PCI_ROOT_COMPLEX, + length: (node_size + padding) as u16, + revision: 3, + node_id: segment.id as u32, // todo to avoid conflict with ITS node IDs + num_id_mappings: num_id_mappings as u32, + // ID mapping array starts right after `IortPciRootComplexBase` + id_mappings_array_offset: std::mem::size_of::() as u32, + }, + mem_access_props: IortMemoryAccessProperties { + cca: 1, // Fully coherent device + ah: 0, + _reserved: 0, + maf: 3, // CPM = DCAS = 1 + }, + ats_attribute: 0, + pci_segment_number: segment.id as u32, + memory_address_size_limit: 64u8, + _reserved: [0; 3], + }); + // ID Mapping for this Root Complex + // Maps 256 device IDs (1 bus × 32 devices × 8 functions) assert!(segment.id < 256, "Up to 256 PCI segments are supported."); - iort.write(mapping_offset + 8, ((256 * segment.id) as u32).to_le()); - // id_mapping_array_output_reference should be - // the ITS group node (the first node) if no SMMU - iort.write(mapping_offset + 12, (48u32).to_le()); - // Flags - iort.write(mapping_offset + 16, (0u32).to_le()); + iort.append(IortIdMapping { + input_base: 0, + // The number of IDs in the range minus one: + // This should cover all the devices of a segment: + // 1 (bus) x 32 (devices) x 8 (functions) = 256 + // Note: Currently only 1 bus is supported in a segment. + num_ids: 255, + // Output base maps to ITS device IDs which must match the + // device ID encoding used in KVM MSI routing setup, which + // shares the same limitation - only 1 bus per segment and + // up to 256 segments. + // See: https://github.com/cloud-hypervisor/cloud-hypervisor/commit/c9374d87ac453d49185aa7b734df089444166484 + output_base: (256 * segment.id) as u32, + // Output reference node is the ITS group node as there is no SMMU node + output_reference: offset_its_node as u32, + flags: 0, + }); + iort.append_slice(&vec![0u8; padding]); // Add padding to align to 8 bytes } iort.update_checksum(); From d5e35d9e5c9464b2f41c0fec66f0c03a64dbdbb7 Mon Sep 17 00:00:00 2001 From: Aastha Rawat Date: Mon, 19 Jan 2026 18:37:45 +0530 Subject: [PATCH 0464/1893] 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 --- .github/workflows/mshv-infra.yaml | 1 - .github/workflows/mshv-integration.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index ffd72713e1..e8d25fc12e 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -50,7 +50,6 @@ 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 }} diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index 2083c54361..261b84d546 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -23,7 +23,6 @@ jobs: 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 From e4f9610b5bc5c71af4a0243a102b6b4afdc1016f Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Sun, 18 Jan 2026 06:55:46 +0000 Subject: [PATCH 0465/1893] 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 --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- fuzz/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d576496de..75e4c8458d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1272,9 +1272,9 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mshv-bindings" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f415da68542aca92b33f55ac3e93031dc30a2941952b99679258f7e0527353" +checksum = "7752a74e9b4f95f20c5eec69ee7cf25aee5da6d87d18574254b44f22940151fb" dependencies = [ "libc", "num_enum", @@ -1286,9 +1286,9 @@ dependencies = [ [[package]] name = "mshv-ioctls" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e52a2a02c4107e08f46ba9dfc4e0f4461dffd44fbeca3e5631b4a047d15376c9" +checksum = "77e058608d09f2f8b106b06e6c58a09aa44915dd6a36cd4142d3a7d32e59c1fb" dependencies = [ "libc", "mshv-bindings", diff --git a/Cargo.toml b/Cargo.toml index c191a4db62..ea58f10d59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,8 +49,8 @@ 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" +mshv-bindings = "0.6.6" +mshv-ioctls = "0.6.6" seccompiler = "0.5.0" vfio-bindings = { version = "0.6.0", default-features = false } vfio-ioctls = { version = "0.5.1", default-features = false } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index a05812f188..6a0892244f 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -25,7 +25,7 @@ libc = "0.2.180" libfuzzer-sys = "0.4.10" linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } -mshv-bindings = "0.6.5" +mshv-bindings = "0.6.6" net_util = { path = "../net_util" } seccompiler = "0.5.0" virtio-devices = { path = "../virtio-devices" } From cdf14956e0422be2e7cca59580d3588904ed2b29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:25:33 +0000 Subject: [PATCH 0466/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 2458d82c9e..32011c9bc3 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.42.0 + - uses: crate-ci/typos@v1.42.1 From cda1ec5fbc3e4488ad2d52bbe1958ac8ff01b49e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:37:28 +0000 Subject: [PATCH 0467/1893] 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](https://github.com/microsoft/igvm/compare/81dd0e49cd3caf9e894e376969e33f2584e6f8ba...9848d1f2a8a44931395a30debeae287b6d01505d) Updates `igvm_defs` from `81dd0e4` to `9848d1f` - [Release notes](https://github.com/microsoft/igvm/releases) - [Commits](https://github.com/microsoft/igvm/compare/81dd0e49cd3caf9e894e376969e33f2584e6f8ba...9848d1f2a8a44931395a30debeae287b6d01505d) 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] --- Cargo.lock | 108 ++++++++++++++++++------------------ Cargo.toml | 2 +- cloud-hypervisor/Cargo.toml | 2 +- fuzz/Cargo.lock | 70 +++++++++++------------ vmm/Cargo.toml | 2 +- 5 files changed, 92 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75e4c8458d..6545d6013d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,7 +94,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" name = "api_client" version = "0.1.0" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", "vmm-sys-util", ] @@ -119,7 +119,7 @@ dependencies = [ "linux-loader", "log", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "uuid", "vm-fdt", "vm-memory", @@ -331,7 +331,7 @@ dependencies = [ "remain", "serde", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "uuid", "virtio-bindings", "virtio-queue", @@ -368,9 +368,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -434,7 +434,7 @@ dependencies = [ "serde_json", "signal-hook", "test_infra", - "thiserror 2.0.17", + "thiserror 2.0.18", "tpm", "tracer", "vm-memory", @@ -552,7 +552,7 @@ dependencies = [ "num_enum", "pci", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tpm", "vm-allocator", "vm-device", @@ -730,9 +730,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "flate2" @@ -967,7 +967,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -992,7 +992,7 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "igvm" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#81dd0e49cd3caf9e894e376969e33f2584e6f8ba" +source = "git+https://github.com/microsoft/igvm?branch=main#9848d1f2a8a44931395a30debeae287b6d01505d" dependencies = [ "bitfield-struct 0.10.1", "crc32fast", @@ -1001,7 +1001,7 @@ dependencies = [ "open-enum", "range_map_vec", "static_assertions", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "zerocopy", ] @@ -1009,7 +1009,7 @@ dependencies = [ [[package]] name = "igvm_defs" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#81dd0e49cd3caf9e894e376969e33f2584e6f8ba" +source = "git+https://github.com/microsoft/igvm?branch=main#9848d1f2a8a44931395a30debeae287b6d01505d" dependencies = [ "bitfield-struct 0.10.1", "open-enum", @@ -1143,7 +1143,7 @@ checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088" dependencies = [ "enumflags2", "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1292,7 +1292,7 @@ checksum = "77e058608d09f2f8b106b06e6c58a09aa44915dd6a36cd4142d3a7d32e59c1fb" dependencies = [ "libc", "mshv-bindings", - "thiserror 2.0.17", + "thiserror 2.0.18", "vmm-sys-util", ] @@ -1317,7 +1317,7 @@ dependencies = [ "rate_limiter", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "virtio-bindings", "virtio-queue", "vm-memory", @@ -1435,7 +1435,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" name = "option_parser" version = "0.1.0" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1493,7 +1493,7 @@ dependencies = [ "libc", "log", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-bindings", "vfio-ioctls", "vfio_user", @@ -1513,7 +1513,7 @@ dependencies = [ "serde", "serde_json", "test_infra", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1729,9 +1729,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -1749,7 +1749,7 @@ dependencies = [ "epoll", "libc", "log", - "thiserror 2.0.17", + "thiserror 2.0.18", "vmm-sys-util", ] @@ -1770,7 +1770,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1815,9 +1815,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -2059,7 +2059,7 @@ dependencies = [ "libc", "serde_json", "ssh2", - "thiserror 2.0.17", + "thiserror 2.0.18", "vmm-sys-util", "wait-timeout", ] @@ -2075,11 +2075,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2095,9 +2095,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2148,7 +2148,7 @@ dependencies = [ "libc", "log", "net_gen", - "thiserror 2.0.17", + "thiserror 2.0.18", "vmm-sys-util", ] @@ -2257,7 +2257,7 @@ dependencies = [ "log", "mshv-bindings", "mshv-ioctls", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2275,7 +2275,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2319,7 +2319,7 @@ dependencies = [ "libc", "log", "option_parser", - "thiserror 2.0.17", + "thiserror 2.0.18", "vhost", "vhost-user-backend", "virtio-bindings", @@ -2339,7 +2339,7 @@ dependencies = [ "log", "net_util", "option_parser", - "thiserror 2.0.17", + "thiserror 2.0.18", "vhost", "vhost-user-backend", "virtio-bindings", @@ -2372,7 +2372,7 @@ dependencies = [ "serde", "serde_with", "serial_buffer", - "thiserror 2.0.17", + "thiserror 2.0.18", "vhost", "virtio-bindings", "virtio-queue", @@ -2411,7 +2411,7 @@ version = "0.1.0" dependencies = [ "hypervisor", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -2443,7 +2443,7 @@ dependencies = [ "itertools", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "vm-memory", ] @@ -2495,7 +2495,7 @@ dependencies = [ "serde_json", "serial_buffer", "signal-hook", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracer", "uuid", "vfio-ioctls", @@ -2542,9 +2542,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] @@ -2729,15 +2729,15 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zbus" -version = "5.13.1" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b" +checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" dependencies = [ "async-broadcast", "async-executor", @@ -2770,9 +2770,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.13.1" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37" +checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2816,9 +2816,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" [[package]] name = "zstd" @@ -2850,9 +2850,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.9.1" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543" +checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" dependencies = [ "endi", "enumflags2", @@ -2864,9 +2864,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.9.1" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7" +checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index ea58f10d59..1e63a52968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ itertools = "0.14.0" libc = "0.2.180" log = "0.4.29" signal-hook = "0.4.1" -thiserror = "2.0.17" +thiserror = "2.0.18" uuid = { version = "1.19.0" } wait-timeout = "0.2.1" zerocopy = { version = "0.8.33", default-features = false } diff --git a/cloud-hypervisor/Cargo.toml b/cloud-hypervisor/Cargo.toml index 739f181d2d..69ddf345c2 100644 --- a/cloud-hypervisor/Cargo.toml +++ b/cloud-hypervisor/Cargo.toml @@ -38,7 +38,7 @@ tracer = { path = "../tracer" } vm-memory = { workspace = true } vmm = { path = "../vmm" } vmm-sys-util = { workspace = true } -zbus = { version = "5.13.1", optional = true } +zbus = { version = "5.13.2", optional = true } [dev-dependencies] block = { path = "../block" } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 5e0be02e5f..13621a31a0 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -99,7 +99,7 @@ dependencies = [ "linux-loader", "log", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "uuid", "vm-fdt", "vm-memory", @@ -147,7 +147,7 @@ dependencies = [ "remain", "serde", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "uuid", "virtio-bindings", "virtio-queue", @@ -171,9 +171,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -331,7 +331,7 @@ dependencies = [ "num_enum", "pci", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tpm", "vm-allocator", "vm-device", @@ -420,9 +420,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "flate2" @@ -539,7 +539,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -643,7 +643,7 @@ checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088" dependencies = [ "enumflags2", "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -725,9 +725,9 @@ dependencies = [ [[package]] name = "mshv-bindings" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f415da68542aca92b33f55ac3e93031dc30a2941952b99679258f7e0527353" +checksum = "7752a74e9b4f95f20c5eec69ee7cf25aee5da6d87d18574254b44f22940151fb" dependencies = [ "libc", "num_enum", @@ -755,7 +755,7 @@ dependencies = [ "net_gen", "rate_limiter", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "virtio-bindings", "virtio-queue", "vm-memory", @@ -830,7 +830,7 @@ dependencies = [ name = "option_parser" version = "0.1.0" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -849,7 +849,7 @@ dependencies = [ "libc", "log", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-bindings", "vfio-ioctls", "vfio_user", @@ -930,9 +930,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -944,7 +944,7 @@ dependencies = [ "epoll", "libc", "log", - "thiserror 2.0.17", + "thiserror 2.0.18", "vmm-sys-util", ] @@ -1124,11 +1124,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -1144,9 +1144,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1191,7 +1191,7 @@ dependencies = [ "libc", "log", "net_gen", - "thiserror 2.0.17", + "thiserror 2.0.18", "vmm-sys-util", ] @@ -1249,7 +1249,7 @@ dependencies = [ "kvm-ioctls", "libc", "log", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1267,7 +1267,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1310,7 +1310,7 @@ dependencies = [ "serde", "serde_with", "serial_buffer", - "thiserror 2.0.17", + "thiserror 2.0.18", "vhost", "virtio-bindings", "virtio-queue", @@ -1349,7 +1349,7 @@ version = "0.1.0" dependencies = [ "hypervisor", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -1381,7 +1381,7 @@ dependencies = [ "itertools", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "vm-memory", ] @@ -1425,7 +1425,7 @@ dependencies = [ "serde_json", "serial_buffer", "signal-hook", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracer", "uuid", "vfio-ioctls", @@ -1462,9 +1462,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] @@ -1575,9 +1575,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zerocopy" @@ -1601,9 +1601,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" [[package]] name = "zstd" diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index a50ec35434..eec935340f 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -91,7 +91,7 @@ vm-memory = { workspace = true, features = [ vm-migration = { path = "../vm-migration" } vm-virtio = { path = "../vm-virtio" } vmm-sys-util = { workspace = true, features = ["with-serde"] } -zbus = { version = "5.13.1", optional = true } +zbus = { version = "5.13.2", optional = true } zerocopy = { workspace = true, features = ["alloc", "derive"] } [lints] From 15d34ffebba5e8db159e1eb11628e51b35fffbf3 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 15 Jan 2026 07:01:56 -0800 Subject: [PATCH 0468/1893] 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 --- virtio-devices/src/vhost_user/blk.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 6576013e89..d26350c91a 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -11,8 +11,7 @@ use log::{error, info}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use vhost::vhost_user::message::{ - VHOST_USER_CONFIG_OFFSET, VhostUserConfigFlags, VhostUserProtocolFeatures, - VhostUserVirtioFeatures, + VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures, }; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; use virtio_bindings::virtio_blk::{ @@ -147,7 +146,7 @@ impl Blk { let (_, config_space) = vu .socket_handle() .get_config( - VHOST_USER_CONFIG_OFFSET, + 0, config_len as u32, VhostUserConfigFlags::WRITABLE, config_space.as_slice(), From 25e8e64a0136f335845bea1430e866c3c6dc1f8a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 19 Jan 2026 08:02:29 -0800 Subject: [PATCH 0469/1893] 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 --- vhost_user_block/src/lib.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 0e28377b6c..456af40d9e 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -21,7 +21,7 @@ use std::{convert, io, process, result}; use block::qcow::{self, ImageType, QcowFile}; use block::{Request, VirtioBlockConfig, build_serial}; use libc::EFD_NONBLOCK; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; use option_parser::{OptionParser, OptionParserError, Toggle}; use thiserror::Error; use vhost::vhost_user::Listener; @@ -395,8 +395,18 @@ impl VhostUserBackendMut for VhostUserBlkBackend { } } - fn get_config(&self, _offset: u32, _size: u32) -> Vec { - self.config.as_slice().to_vec() + fn get_config(&self, offset: u32, size: u32) -> Vec { + let subset = self + .config + .as_slice() + .get(offset as usize..(offset + size) as usize); + + if let Some(subset) = subset { + subset.to_vec() + } else { + warn!("Invalid config offset {offset} or size {size}"); + vec![] + } } fn set_config(&mut self, offset: u32, data: &[u8]) -> result::Result<(), io::Error> { From c9cd82b52b171d8cbd8109ff6e582213e64ef039 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 7 Dec 2025 18:08:19 -0800 Subject: [PATCH 0470/1893] 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 --- vmm/src/igvm/igvm_loader.rs | 9 ++- vmm/src/vm.rs | 151 ++++++++++++++++++++++++++---------- 2 files changed, 118 insertions(+), 42 deletions(-) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 32aeb0f714..4d454f8223 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -27,7 +27,7 @@ use crate::GuestMemoryMmap; use crate::cpu::CpuManager; use crate::igvm::loader::Loader; use crate::igvm::{BootPageAcceptance, HV_PAGE_SIZE, IgvmLoadedInfo, StartupMemoryType}; -use crate::memory_manager::MemoryManager; +use crate::memory_manager::{Error as MemoryManagerError, MemoryManager}; #[derive(Debug, Error)] pub enum Error { @@ -49,6 +49,8 @@ pub enum Error { CompleteIsolatedImport(#[source] hypervisor::HypervisorVmError), #[error("Error decoding host data")] FailedToDecodeHostData(#[source] hex::FromHexError), + #[error("Error allocating address space")] + MemoryManager(MemoryManagerError), } #[allow(dead_code)] @@ -421,6 +423,11 @@ pub fn load_igvm( #[cfg(feature = "sev_snp")] { + memory_manager + .lock() + .unwrap() + .allocate_address_space() + .map_err(Error::MemoryManager)?; use std::time::Instant; let mut now = Instant::now(); diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 441d0bcafb..d6f6b93e70 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -667,8 +667,49 @@ impl Vm { ) .map_err(Error::DeviceManager)?; - // For MSHV, we need to create the interrupt controller before we initialize the VM. - // Because we need to set the base address of GICD before we initialize the VM. + // Initialize the VM now that we have created the device manager. + // For MSHV and non aarch64, we need to initialize the VM before creating vCPUs. + // For aarch64, we need to initialize the VM after creating interrupt controller. + // Push down write after the IC(Interrupt Controller) creation for MSHV aarch64. + #[cfg(all(feature = "mshv", not(target_arch = "aarch64")))] + { + if is_mshv { + vm.init().map_err(Error::InitializeVm)?; + } + } + #[cfg(feature = "sev_snp")] + if sev_snp_enabled { + cpu_manager + .lock() + .unwrap() + .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) + .map_err(Error::CpuManager)?; + + // This initial SEV-SNP configuration must be done immediately after + // vCPUs are created. As part of this initialization we are + // transitioning the guest into secure state. + vm.sev_snp_init().map_err(Error::InitializeSevSnpVm)?; + } + + #[cfg(feature = "sev_snp")] + // Loading the igvm file is pushed down here because + // igvm parser needs cpu_manager to retrieve cpuid leaf. + // Currently, Microsoft Hypervisor does not provide any + // Hypervisor specific common cpuid, we need to call get_cpuid_values + // per cpuid through cpu_manager. + let _load_payload_handle = if snapshot.is_none() && sev_snp_enabled { + Self::load_payload_async( + &memory_manager, + &config, + #[cfg(feature = "igvm")] + &cpu_manager, + #[cfg(feature = "sev_snp")] + sev_snp_enabled, + )? + } else { + None + }; + #[cfg(feature = "mshv")] { if is_mshv { @@ -677,9 +718,8 @@ impl Vm { .unwrap() .create_interrupt_controller() .map_err(Error::DeviceManager)?; - + #[cfg(target_arch = "aarch64")] vm.init().map_err(Error::InitializeVm)?; - device_manager .lock() .unwrap() @@ -692,12 +732,23 @@ impl Vm { .map_err(Error::DeviceManager)?; } } - - memory_manager - .lock() - .unwrap() - .allocate_address_space() - .map_err(Error::MemoryManager)?; + cfg_if::cfg_if! { + if #[cfg(feature = "sev_snp")] { + if !sev_snp_enabled { + memory_manager + .lock() + .unwrap() + .allocate_address_space() + .map_err(Error::MemoryManager)?; + } + } else { + memory_manager + .lock() + .unwrap() + .allocate_address_space() + .map_err(Error::MemoryManager)?; + } + } #[cfg(target_arch = "aarch64")] memory_manager @@ -706,29 +757,55 @@ impl Vm { .add_uefi_flash() .map_err(Error::MemoryManager)?; - // Loading the igvm file is pushed down here because - // igvm parser needs cpu_manager to retrieve cpuid leaf. - // Currently, Microsoft Hypervisor does not provide any - // Hypervisor specific common cpuid, we need to call get_cpuid_values - // per cpuid through cpu_manager. - let load_payload_handle = if snapshot.is_none() { - Self::load_payload_async( - &memory_manager, - &config, - #[cfg(feature = "igvm")] - &cpu_manager, - #[cfg(feature = "sev_snp")] - sev_snp_enabled, - )? - } else { - None - }; + // First case is when sev_snp is enabled(compiled), but run time non-cvn + // guest boot. 2nd case is when sev_snp is not compiled in, KVM and MSHV regular guest boot. + cfg_if::cfg_if! { + if #[cfg(feature = "sev_snp")] { + let _load_payload_handle = if snapshot.is_none() && !sev_snp_enabled { + Self::load_payload_async( + &memory_manager, + &config, + #[cfg(feature = "igvm")] + &cpu_manager, + #[cfg(feature = "sev_snp")] + sev_snp_enabled, + )? + } else { + None + }; + } else { + let _load_payload_handle = if snapshot.is_none() { + Self::load_payload_async( + &memory_manager, + &config, + #[cfg(feature = "igvm")] + &cpu_manager, + )? + } else { + None + }; + } + } - cpu_manager - .lock() - .unwrap() - .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) - .map_err(Error::CpuManager)?; + // First case is when sev_snp is enabled(compiled), but run time non-cvn + // guest boot. 2nd case is when sev_snp is not compiled in, KVM and MSHV regular guest boot. + cfg_if::cfg_if! { + if #[cfg(feature = "sev_snp")] { + if !sev_snp_enabled { + cpu_manager + .lock() + .unwrap() + .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) + .map_err(Error::CpuManager)?; + } + } else { + cpu_manager + .lock() + .unwrap() + .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) + .map_err(Error::CpuManager)?; + } + } // For KVM, we need to create interrupt controller after we create boot vcpus. // Because we restore GIC state from the snapshot as part of boot vcpu creation. @@ -752,14 +829,6 @@ impl Vm { } } - // This initial SEV-SNP configuration must be done immediately after - // vCPUs are created. As part of this initialization we are - // transitioning the guest into secure state. - #[cfg(feature = "sev_snp")] - if sev_snp_enabled { - vm.sev_snp_init().map_err(Error::InitializeSevSnpVm)?; - } - #[cfg(feature = "fw_cfg")] { let fw_cfg_config = config @@ -830,7 +899,7 @@ impl Vm { #[cfg(not(target_arch = "riscv64"))] hypervisor, stop_on_boot, - load_payload_handle, + load_payload_handle: _load_payload_handle, }) } From 298da5590207970d642ffc314c2bfd5917a34912 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Wed, 21 Jan 2026 00:35:03 +0000 Subject: [PATCH 0471/1893] 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 --- docs/uefi.md | 43 +++++++++++++++++++++++++++++++-------- scripts/common-aarch64.sh | 4 ++-- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/docs/uefi.md b/docs/uefi.md index a93fb929cf..cabbdf2568 100644 --- a/docs/uefi.md +++ b/docs/uefi.md @@ -35,13 +35,31 @@ After the successful build, the resulting firmware binaries are available under # On an AArch64 machine: $ sudo apt-get update $ sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git -$ git clone --depth 1 https://github.com/tianocore/edk2.git -b master -$ cd edk2 -$ git submodule update --init -$ cd .. -$ git clone --depth 1 https://github.com/tianocore/edk2-platforms.git -b master -$ git clone --depth 1 https://github.com/acpica/acpica.git -b master - +# Master branches for these repos can be unstable, and newer GCC versions +# enforce strict warning-as-error policies that break builds +# These specific commit # are verified to compile cleanly with GCC 13.3.0 +# Shallow clone edk2 repo +$ mkdir -p edk2 && cd edk2 && \ +git init -q && \ +git remote add origin https://github.com/tianocore/edk2.git && \ +git fetch -q --depth 1 origin 22130dcd98b4d4b76ac8d922adb4a2dbc86fa52c && \ +git checkout -q FETCH_HEAD && \ +git submodule update --init --recursive --depth 1 && \ +cd .. +# Shallow clone edk2-platforms repo +$ mkdir -p edk2-platforms && cd edk2-platforms && \ +git init -q && \ +git remote add origin https://github.com/tianocore/edk2-platforms.git && \ +git fetch -q --depth 1 origin 8227e9e9f6a8aefbd772b40138f835121ccb2307 && \ +git checkout -q FETCH_HEAD && \ +cd .. +# Shallow clone acpica repo +$ mkdir -p acpica && cd acpica && \ +git init -q && \ +git remote add origin https://github.com/acpica/acpica.git && \ +git fetch -q --depth 1 origin e80cbd7b52de20aa8c75bfba9845e9cb61f2e681 && \ +git checkout -q FETCH_HEAD && \ +cd .. # Build tools $ export PACKAGES_PATH="$PWD/edk2:$PWD/edk2-platforms" $ export IASL_PREFIX="$PWD/acpica/generate/unix/bin/" @@ -53,10 +71,19 @@ $ make -C edk2/BaseTools # Build EDK2 $ build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtCloudHv.dsc -b RELEASE + +# Alternate method +# Launch developer container from AArch64 machine +$ ./scripts/dev_cli.sh shell +# Inside the container +$ source scripts/test-util.sh +$ source scripts/common-aarch64.sh +$ build_edk2 ``` If the build goes well, the EDK2 binary is available at -`edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd`. +`edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd` or `workloads/CLOUDHV_EFI.fd` +when using developer container to produce firmware. ## Using OVMF Binaries diff --git a/scripts/common-aarch64.sh b/scripts/common-aarch64.sh index b44770efea..ae7f632921 100644 --- a/scripts/common-aarch64.sh +++ b/scripts/common-aarch64.sh @@ -21,12 +21,12 @@ build_edk2() { fi # Prepare source code - checkout_repo "$EDK2_DIR" "$EDK2_REPO" master "46b4606ba23498d3d0e66b53e498eb3d5d592586" + checkout_repo "$EDK2_DIR" "$EDK2_REPO" master "22130dcd98b4d4b76ac8d922adb4a2dbc86fa52c" pushd "$EDK2_DIR" || exit git submodule update --init popd || exit checkout_repo "$EDK2_PLAT_DIR" "$EDK2_PLAT_REPO" master "8227e9e9f6a8aefbd772b40138f835121ccb2307" - checkout_repo "$ACPICA_DIR" "$ACPICA_REPO" master "b9c69f81a05c45611c91ea9cbce8756078d76233" + checkout_repo "$ACPICA_DIR" "$ACPICA_REPO" master "e80cbd7b52de20aa8c75bfba9845e9cb61f2e681" if [[ ! -f "$EDK2_DIR/.built" || ! -f "$EDK2_PLAT_DIR/.built" || From d58e421eea8db086aba0f4a7c049894024c2d642 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Wed, 21 Jan 2026 00:56:02 +0000 Subject: [PATCH 0472/1893] 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 --- scripts/common-aarch64.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/common-aarch64.sh b/scripts/common-aarch64.sh index ae7f632921..1c0b875193 100644 --- a/scripts/common-aarch64.sh +++ b/scripts/common-aarch64.sh @@ -38,10 +38,14 @@ build_edk2() { source edk2/edksetup.sh make -C edk2/BaseTools -j "$(nproc)" build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtCloudHv.dsc -b RELEASE -n 0 - cp Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd "$WORKLOADS_DIR" - touch "$EDK2_DIR"/.built - touch "$EDK2_PLAT_DIR"/.built - touch "$ACPICA_DIR"/.built + if cp Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd "$WORKLOADS_DIR"; then + touch "$EDK2_DIR"/.built + touch "$EDK2_PLAT_DIR"/.built + touch "$ACPICA_DIR"/.built + else + echo "Failed to produce aarch64 UEFI firmware. Built markers not created." + exit 1 + fi popd || exit fi } From 13198777dde74fdac9944c43d7e02ae5be168826 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 21 Jan 2026 19:43:57 +0100 Subject: [PATCH 0473/1893] 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 --- block/src/qcow/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index ca0262924a..732c10e4b5 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -193,6 +193,7 @@ const V3_BARE_HEADER_SIZE: u32 = 104; const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; // Flags +const ZERO_FLAG: u64 = 1 << 0; const COMPRESSED_FLAG: u64 = 1 << 62; const COMPRESSED_SECTOR_SIZE: u64 = 512; const CLUSTER_USED_FLAG: u64 = 1 << 63; @@ -217,6 +218,11 @@ fn l2_entry_is_empty(l2_entry: u64) -> bool { l2_entry == 0 } +// Check bit 0 - only valid for standard clusters. +fn l2_entry_is_zero(l2_entry: u64) -> bool { + l2_entry & ZERO_FLAG != 0 +} + fn l2_entry_is_compressed(l2_entry: u64) -> bool { l2_entry & COMPRESSED_FLAG != 0 } @@ -1333,6 +1339,9 @@ impl QcowFile { return Err(err_inval); } buf[..count].copy_from_slice(&decompressed_cluster[start..end.unwrap()]); + } else if l2_entry_is_zero(l2_entry) { + // Cluster with zero flag reads as zeros without accessing disk. + return Ok(None); } else { let start = l2_entry_std_cluster_addr(l2_entry) + self.raw_file.cluster_offset(address); let raw_file = self.raw_file.file_mut(); From eaafe426a647cd9b1d063fc4f1afe88e2ab846f0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 22 Jan 2026 11:21:07 +0100 Subject: [PATCH 0474/1893] 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 --- block/src/qcow/mod.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 732c10e4b5..e3aa04349f 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -2597,6 +2597,26 @@ mod unit_tests { }); } + #[test] + fn test_l2_entry_zero_flag() { + let empty_entry: u64 = 0; + let standard_entry: u64 = 0x1000; + let zero_flag_entry: u64 = 0x1000 | ZERO_FLAG; + let compressed_entry: u64 = COMPRESSED_FLAG; + + assert!(l2_entry_is_empty(empty_entry)); + assert!(!l2_entry_is_empty(standard_entry)); + + assert!(!l2_entry_is_compressed(standard_entry)); + assert!(l2_entry_is_compressed(compressed_entry)); + + assert!(!l2_entry_is_zero(standard_entry)); + assert!(l2_entry_is_zero(zero_flag_entry)); + + // Note: l2_entry_is_zero() only checks bit 0, so compressed entries + // must be checked first as the code does in file_read. + } + #[test] fn test_header_1_tb_file() { let mut header = test_huge_header(); From 7c99c169baa4e95a50d5cf5f023759cf5d4085a4 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 19 Jan 2026 10:43:16 +0100 Subject: [PATCH 0475/1893] 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 Co-developed-by: Philipp Schuster --- Cargo.lock | 1 + block/Cargo.toml | 1 + block/src/qcow/mod.rs | 127 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 121 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6545d6013d..8b3208e293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,6 +322,7 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" name = "block" version = "0.1.0" dependencies = [ + "bitflags 2.10.0", "byteorder", "crc-any", "flate2", diff --git a/block/Cargo.toml b/block/Cargo.toml index c038b0cd83..70a731a731 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -9,6 +9,7 @@ default = [] io_uring = ["dep:io-uring"] [dependencies] +bitflags = { workspace = true } byteorder = { workspace = true } crc-any = "2.5.0" flate2 = "1.1" diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index e3aa04349f..ae4dd4eed8 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -18,6 +18,7 @@ use std::mem::size_of; use std::os::fd::{AsRawFd, RawFd}; use std::str::{self, FromStr}; +use bitflags::bitflags; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use libc::{EINVAL, EIO, ENOSPC}; use log::error; @@ -116,6 +117,8 @@ pub enum Error { UnsupportedBackingFileFormat(String), #[error("Unsupported compression type")] UnsupportedCompressionType, + #[error("Unsupported qcow2 feature(s)")] + UnsupportedFeature(#[source] MissingFeatureError), #[error("Unsupported refcount order")] UnsupportedRefcountOrder, #[error("Unsupported version: {0}")] @@ -207,6 +210,76 @@ const COMPRESSION_TYPE_ZSTD: u64 = 1; // zstd const HEADER_EXT_END: u32 = 0x00000000; // Backing file format name (raw, qcow2) 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. + const SUPPORTED: IncompatFeatures = 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, Error)] +pub struct MissingFeatureError { + /// Unsupported feature bits. + features: IncompatFeatures, + /// Feature name table from the qcow2 image. + feature_names: Vec<(u8, String)>, +} + +impl MissingFeatureError { + 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 = (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; @@ -289,7 +362,12 @@ pub struct QcowHeader { } impl QcowHeader { - fn read_header_extensions(f: &mut RawFile, header: &mut QcowHeader) -> Result<()> { + /// Read header extensions, optionally collecting feature names for error reporting. + fn read_header_extensions( + f: &mut RawFile, + header: &mut QcowHeader, + mut feature_table: Option<&mut Vec<(u8, String)>>, + ) -> Result<()> { // Extensions start directly after the header f.seek(SeekFrom::Start(header.header_size as u64)) .map_err(Error::ReadingHeader)?; @@ -313,6 +391,21 @@ impl QcowHeader { 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(&mut data).map_err(Error::ReadingHeader)?; + let table = feature_table.as_mut().unwrap(); + for entry in data.chunks_exact(FEATURE_NAME_ENTRY_SIZE) { + 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 f.seek(SeekFrom::Current(ext_length as i64)) @@ -415,8 +508,26 @@ impl QcowHeader { header.backing_file = Some(BackingFileConfig { path, format: None }); } - if version == 3 && header.header_size > V3_BARE_HEADER_SIZE { - Self::read_header_extensions(f, &mut header)?; + 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) @@ -2389,7 +2500,7 @@ mod unit_tests { format: None, }); - QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap(); + QcowHeader::read_header_extensions(&mut disk_file, &mut header, None).unwrap(); assert_eq!(header.backing_file.as_ref().and_then(|bf| bf.format), None); } @@ -2403,7 +2514,7 @@ mod unit_tests { format: None, }); - QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap(); + QcowHeader::read_header_extensions(&mut disk_file, &mut header, None).unwrap(); assert_eq!( header.backing_file.as_ref().and_then(|bf| bf.format), Some(ImageType::Raw) @@ -2420,7 +2531,7 @@ mod unit_tests { format: None, }); - QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap(); + QcowHeader::read_header_extensions(&mut disk_file, &mut header, None).unwrap(); assert_eq!( header.backing_file.as_ref().and_then(|bf| bf.format), Some(ImageType::Qcow2) @@ -2437,7 +2548,7 @@ mod unit_tests { format: None, }); - let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header); + let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header, None); assert!(matches!( result.unwrap_err(), Error::UnsupportedBackingFileFormat(_) @@ -2451,7 +2562,7 @@ mod unit_tests { &[0xFF, 0xFE, 0xFD], // invalid UTF-8 ); - let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header); + let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header, None); // Should fail with InvalidBackingFileName error assert!(matches!( result.unwrap_err(), From e61901dfdceb071e2d12134423409fac25b7db97 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 19 Jan 2026 10:43:23 +0100 Subject: [PATCH 0476/1893] 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 --- block/src/qcow/mod.rs | 94 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index ae4dd4eed8..aebeeae64e 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -3455,4 +3455,98 @@ mod unit_tests { .expect("Failed to rebuild recounts."); }); } + + // Helper to create a v3 header with specific incompatible feature bits set + fn header_v3_with_incompat_features(features: u64) -> Vec { + let mut header = valid_header_v3(); + // incompatible_features is at offset 72, big-endian u64 + header[72..80].copy_from_slice(&features.to_be_bytes()); + header + } + + #[test] + fn reject_unsupported_incompat_dirty_bit() { + // Bit 0: dirty - image not closed cleanly + let header = header_v3_with_incompat_features(1 << 0); + with_basic_file(&header, |disk_file: RawFile| { + let result = QcowFile::from(disk_file); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("dirty")), + "Expected UnsupportedFeature error mentioning dirty, got: {err:?}" + ); + }); + } + + #[test] + fn reject_unsupported_incompat_corrupt_bit() { + // Bit 1: corrupt - image metadata is corrupted + let header = header_v3_with_incompat_features(1 << 1); + with_basic_file(&header, |disk_file: RawFile| { + let result = QcowFile::from(disk_file); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("corrupt")), + "Expected UnsupportedFeature error mentioning corrupt, got: {err:?}" + ); + }); + } + + #[test] + fn reject_unsupported_incompat_external_data_bit() { + // Bit 2: external data file + let header = header_v3_with_incompat_features(1 << 2); + with_basic_file(&header, |disk_file: RawFile| { + let result = QcowFile::from(disk_file); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("external")), + "Expected UnsupportedFeature error mentioning external, got: {err:?}" + ); + }); + } + + #[test] + fn reject_unsupported_incompat_extended_l2_bit() { + // Bit 4: extended L2 entries + let header = header_v3_with_incompat_features(1 << 4); + with_basic_file(&header, |disk_file: RawFile| { + let result = QcowFile::from(disk_file); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("extended")), + "Expected UnsupportedFeature error mentioning extended, got: {err:?}" + ); + }); + } + + #[test] + fn reject_multiple_unsupported_incompat_bits() { + // Multiple unsupported bits: dirty (0) + corrupt (1) + let header = header_v3_with_incompat_features((1 << 0) | (1 << 1)); + with_basic_file(&header, |disk_file: RawFile| { + let result = QcowFile::from(disk_file); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), Error::UnsupportedFeature(_))); + }); + } + + #[test] + fn reject_unknown_incompat_bit() { + // Unknown bit 5 (not defined in spec) + let header = header_v3_with_incompat_features(1 << 5); + with_basic_file(&header, |disk_file: RawFile| { + let result = QcowFile::from(disk_file); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("unknown")), + "Expected UnsupportedFeature error mentioning unknown, got: {err:?}" + ); + }); + } } From f8008191d23add5018d6f31183982287b4b7b4ce Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 23 Jan 2026 11:20:58 +0100 Subject: [PATCH 0477/1893] 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 --- block/src/qcow/mod.rs | 64 ++++++------ block/src/qcow/qcow_raw_file.rs | 166 +++++++++++++++++++++++++++++--- block/src/qcow/refcount.rs | 10 +- 3 files changed, 185 insertions(+), 55 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index aebeeae64e..77824039bd 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -186,7 +186,7 @@ const MAX_CLUSTER_BITS: u32 = 21; // 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. const MAX_RAM_POINTER_TABLE_SIZE: u64 = 35_000_000; -// Only support 2 byte refcounts, 2^refcount_order bits. +// 16-bit refcounts. const DEFAULT_REFCOUNT_ORDER: u32 = 4; const V2_BARE_HEADER_SIZE: u32 = 72; @@ -682,12 +682,10 @@ impl QcowHeader { 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_bytes = (0x01 << u64::from(refcount_order)) / 8; - let for_data = div_round_up_u64( - u64::from(num_clusters) * refcount_bytes, - u64::from(cluster_size), - ); - let for_refcounts = div_round_up_u64(for_data * refcount_bytes, u64::from(cluster_size)); + 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 } @@ -849,14 +847,13 @@ impl QcowFile { let backing_file = BackingFile::new(header.backing_file.as_ref(), direct_io, max_nesting_depth)?; - // Only support two byte refcounts. + // Validate refcount order to be 0..6 let refcount_bits: u64 = 0x01u64 .checked_shl(header.refcount_order) .ok_or(Error::UnsupportedRefcountOrder)?; - if refcount_bits != 16 { + if refcount_bits > 64 { return Err(Error::UnsupportedRefcountOrder); } - let refcount_bytes = refcount_bits.div_ceil(8); // Need at least one refcount cluster if header.refcount_table_clusters == 0 { @@ -891,8 +888,8 @@ impl QcowFile { refcount_rebuild_required = true; } - let mut raw_file = - QcowRawFile::from(file, cluster_size).ok_or(Error::InvalidClusterSize)?; + let mut raw_file = QcowRawFile::from(file, cluster_size, refcount_bits) + .ok_or(Error::InvalidClusterSize)?; if refcount_rebuild_required { QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?; } @@ -928,7 +925,7 @@ impl QcowFile { if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE { return Err(Error::TooManyRefcounts(refcount_clusters)); } - let refcount_block_entries = cluster_size / refcount_bytes; + let refcount_block_entries = cluster_size * 8 / refcount_bits; let refcounts = RefCount::new( &mut raw_file, header.refcount_table_offset, @@ -1067,7 +1064,7 @@ impl QcowFile { } /// Returns the `index`th refcount block from the file. - pub fn refcount_block(&mut self, index: usize) -> Result> { + pub fn refcount_block(&mut self, index: usize) -> Result> { self.refcounts .refcount_block(&mut self.raw_file, index) .map_err(Error::ReadingRefCountBlock) @@ -1122,7 +1119,7 @@ impl QcowFile { /// Rebuild the reference count tables. fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> Result<()> { - fn add_ref(refcounts: &mut [u16], cluster_size: u64, cluster_address: u64) -> Result<()> { + fn add_ref(refcounts: &mut [u64], cluster_size: u64, cluster_address: u64) -> Result<()> { let idx = (cluster_address / cluster_size) as usize; if idx >= refcounts.len() { return Err(Error::InvalidClusterIndex); @@ -1132,13 +1129,13 @@ impl QcowFile { } // Add a reference to the first cluster (header plus extensions). - fn set_header_refcount(refcounts: &mut [u16], cluster_size: u64) -> Result<()> { + fn set_header_refcount(refcounts: &mut [u64], cluster_size: u64) -> Result<()> { add_ref(refcounts, cluster_size, 0) } // Add references to the L1 table clusters. fn set_l1_refcounts( - refcounts: &mut [u16], + refcounts: &mut [u64], header: &QcowHeader, cluster_size: u64, ) -> Result<()> { @@ -1153,7 +1150,7 @@ impl QcowFile { // Traverse the L1 and L2 tables to find all reachable data clusters. fn set_data_refcounts( - refcounts: &mut [u16], + refcounts: &mut [u64], header: &QcowHeader, cluster_size: u64, raw_file: &mut QcowRawFile, @@ -1192,7 +1189,7 @@ impl QcowFile { // Add references to the top-level refcount table clusters. fn set_refcount_table_refcounts( - refcounts: &mut [u16], + refcounts: &mut [u64], header: &QcowHeader, cluster_size: u64, ) -> Result<()> { @@ -1211,7 +1208,7 @@ impl QcowFile { // This needs to be done last so that we have the correct refcounts for all other // clusters. fn alloc_refblocks( - refcounts: &mut [u16], + refcounts: &mut [u64], cluster_size: u64, refblock_clusters: u64, ) -> Result> { @@ -1239,7 +1236,7 @@ impl QcowFile { // Write the updated reference count blocks and reftable. fn write_refblocks( - refcounts: &[u16], + refcounts: &[u64], mut header: QcowHeader, ref_table: &[u64], raw_file: &mut QcowRawFile, @@ -1265,12 +1262,11 @@ impl QcowFile { // If this is the last (partial) cluster, pad it out to a full refblock cluster. if refblock.len() < refcount_block_entries as usize { let refblock_padding = - vec![0u16; refcount_block_entries as usize - refblock.len()]; + vec![0u64; refcount_block_entries as usize - refblock.len()]; + let byte_offset = + refblock.len() as u64 * raw_file.cluster_size() / refcount_block_entries; raw_file - .write_refcount_block( - *refblock_addr + refblock.len() as u64 * 2, - &refblock_padding, - ) + .write_refcount_block(*refblock_addr + byte_offset, &refblock_padding) .map_err(Error::WritingHeader)?; } } @@ -1297,8 +1293,7 @@ impl QcowFile { .len(); let refcount_bits = 1u64 << header.refcount_order; - let refcount_bytes = div_round_up_u64(refcount_bits, 8); - let refcount_block_entries = cluster_size / refcount_bytes; + let refcount_block_entries = cluster_size * 8 / refcount_bits; let pointers_per_cluster = cluster_size / size_of::() as u64; let data_clusters = div_round_up_u64(header.size, cluster_size); let l2_clusters = div_round_up_u64(data_clusters, pointers_per_cluster); @@ -1554,7 +1549,7 @@ impl QcowFile { l1_index: usize, l2_index: usize, cluster_addr: u64, - set_refcounts: &mut Vec<(u64, u16)>, + set_refcounts: &mut Vec<(u64, u64)>, ) -> io::Result<()> { if !self.l2_cache.get(l1_index).unwrap().dirty() { // Free the previously used cluster if one exists. Modified tables are always @@ -1804,7 +1799,7 @@ impl QcowFile { fn set_cluster_refcount_track_freed( &mut self, address: u64, - refcount: u16, + refcount: u64, ) -> std::io::Result<()> { let mut newly_unref = self.set_cluster_refcount(address, refcount)?; self.unref_clusters.append(&mut newly_unref); @@ -1814,7 +1809,7 @@ impl QcowFile { // Set the refcount for a cluster with the given address. // Returns a list of any refblocks that can be reused, this happens when a refblock is moved, // the old location can be reused. - fn set_cluster_refcount(&mut self, address: u64, refcount: u16) -> std::io::Result> { + fn set_cluster_refcount(&mut self, address: u64, refcount: u64) -> std::io::Result> { let mut added_clusters = Vec::new(); let mut unref_clusters = Vec::new(); let mut refcount_set = false; @@ -2655,7 +2650,7 @@ mod unit_tests { #[test] fn invalid_refcount_order() { let mut header = valid_header_v3(); - header[99] = 2; + header[99] = 7; with_basic_file(&header, |disk_file: RawFile| { QcowFile::from(disk_file).expect_err("Invalid refcount order worked."); }); @@ -3449,8 +3444,9 @@ mod unit_tests { with_basic_file(&valid_header_v3(), |mut disk_file: RawFile| { let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header."); let cluster_size = 65536; - let mut raw_file = - QcowRawFile::from(disk_file, cluster_size).expect("Failed to create QcowRawFile."); + let refcount_bits = 1u64 << header.refcount_order; + let mut raw_file = QcowRawFile::from(disk_file, cluster_size, refcount_bits) + .expect("Failed to create QcowRawFile."); QcowFile::rebuild_refcounts(&mut raw_file, header) .expect("Failed to rebuild recounts."); }); diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index 8d5dba2e40..5b8cf73562 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -4,7 +4,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use std::io::{self, BufWriter, Seek, SeekFrom, Write}; +use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write}; use std::mem::size_of; use std::os::fd::{AsRawFd, RawFd}; @@ -13,25 +13,163 @@ use vmm_sys_util::write_zeroes::WriteZeroes; use super::RawFile; +// Type aliases for the refcount read/write function pointers +type RefcountReader = fn(&mut RawFile, usize) -> io::Result>; +type RefcountWriter = fn(&mut RawFile, &[u64]) -> io::Result<()>; + +/// Big-endian file access trait. +trait BeUint: Sized + Copy { + fn from_slice(bytes: &[u8]) -> u64; + fn write(w: &mut W, val: u64) -> io::Result<()>; +} + +impl BeUint for u8 { + #[inline(always)] + fn from_slice(bytes: &[u8]) -> u64 { + bytes[0] as u64 + } + #[inline(always)] + fn write(w: &mut W, val: u64) -> io::Result<()> { + w.write_u8(val as u8) + } +} + +impl BeUint for u16 { + #[inline(always)] + fn from_slice(bytes: &[u8]) -> u64 { + u16::from_be_bytes([bytes[0], bytes[1]]) as u64 + } + #[inline(always)] + fn write(w: &mut W, val: u64) -> io::Result<()> { + w.write_u16::(val as u16) + } +} + +impl BeUint for u32 { + #[inline(always)] + fn from_slice(bytes: &[u8]) -> u64 { + u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64 + } + #[inline(always)] + fn write(w: &mut W, val: u64) -> io::Result<()> { + w.write_u32::(val as u32) + } +} + +impl BeUint for u64 { + #[inline(always)] + fn from_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(w: &mut W, val: u64) -> io::Result<()> { + w.write_u64::(val) + } +} + +/// Read byte-aligned refcounts. +fn read_refcount(file: &mut RawFile, count: usize) -> io::Result> { + let bytes_per_entry = size_of::(); + let mut data = vec![0u8; count * bytes_per_entry]; + file.read_exact(&mut data)?; + Ok(data + .chunks_exact(bytes_per_entry) + .map(T::from_slice) + .collect()) +} + +/// Write byte-aligned refcounts. +fn write_refcount(file: &mut RawFile, table: &[u64]) -> io::Result<()> { + let bytes_per_entry = size_of::(); + let mut buffer = BufWriter::with_capacity(table.len() * bytes_per_entry, file); + for &val in table { + T::write(&mut buffer, val)?; + } + buffer.flush() +} + +/// Read sub-byte refcounts. Bit 0 is the least significant bit. +fn read_refcount_subbyte( + file: &mut RawFile, + count: usize, +) -> io::Result> { + 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(&mut bytes)?; + + 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(file: &mut RawFile, 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 = BufWriter::with_capacity(table.len().div_ceil(entries_per_byte), file); + + 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.write_u8(byte)?; + } + buffer.flush() +} + /// A qcow file. Allows reading/writing clusters and appending clusters. #[derive(Debug)] pub struct QcowRawFile { file: RawFile, 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. - pub fn from(file: RawFile, cluster_size: u64) -> Option { + /// a power of two or refcount_bits is invalid. + pub fn from(file: RawFile, cluster_size: u64, refcount_bits: u64) -> Option { 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::, write_refcount::), + 16 => (read_refcount::, write_refcount::), + 32 => (read_refcount::, write_refcount::), + 64 => (read_refcount::, write_refcount::), + _ => 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, }) } @@ -109,24 +247,17 @@ impl QcowRawFile { /// Read a refcount block from the file and returns a Vec containing the block. /// Always returns a cluster's worth of data. - pub fn read_refcount_block(&mut self, offset: u64) -> io::Result> { - let count = self.cluster_size / size_of::() as u64; - let mut table = vec![0; count as usize]; + #[inline] + pub fn read_refcount_block(&mut self, offset: u64) -> io::Result> { self.file.seek(SeekFrom::Start(offset))?; - self.file.read_u16_into::(&mut table)?; - Ok(table) + (self.read_refcount_fn)(&mut self.file, self.refcount_block_entries as usize) } /// Writes a refcount block to the file. - pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> { + #[inline] + pub fn write_refcount_block(&mut self, offset: u64, table: &[u64]) -> io::Result<()> { self.file.seek(SeekFrom::Start(offset))?; - let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file); - - for count in table { - buffer.write_u16::(*count)?; - } - buffer.flush()?; - Ok(()) + (self.write_refcount_fn)(&mut self.file, table) } /// Allocates a new cluster at the end of the current file, return the address. @@ -191,6 +322,9 @@ impl Clone for 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, } } } diff --git a/block/src/qcow/refcount.rs b/block/src/qcow/refcount.rs index 7ef33c1b32..5009fef341 100644 --- a/block/src/qcow/refcount.rs +++ b/block/src/qcow/refcount.rs @@ -38,7 +38,7 @@ pub type Result = std::result::Result; pub struct RefCount { ref_table: VecCache, refcount_table_offset: u64, - refblock_cache: CacheMap>, + refblock_cache: CacheMap>, refcount_block_entries: u64, // number of refcounts in a cluster. cluster_size: u64, max_valid_cluster_offset: u64, @@ -92,8 +92,8 @@ impl RefCount { &mut self, raw_file: &mut QcowRawFile, cluster_address: u64, - refcount: u16, - mut new_cluster: Option<(u64, VecCache)>, + refcount: u64, + mut new_cluster: Option<(u64, VecCache)>, ) -> Result> { let (table_index, block_index) = self.get_refcount_index(cluster_address); @@ -170,7 +170,7 @@ impl RefCount { &mut self, raw_file: &mut QcowRawFile, address: u64, - ) -> Result { + ) -> Result { 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 { @@ -202,7 +202,7 @@ impl RefCount { &mut self, raw_file: &mut QcowRawFile, table_index: usize, - ) -> Result> { + ) -> Result> { let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?; if block_addr_disk == 0 { return Ok(None); From 6e7f888f5d17cd20ea51b8df641022b88899d0ed Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 23 Jan 2026 19:50:10 +0100 Subject: [PATCH 0478/1893] 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 --- block/src/qcow/mod.rs | 112 +++++++++++++++++++++++++++++++++---- block/src/qcow/refcount.rs | 26 +++++++++ 2 files changed, 126 insertions(+), 12 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 77824039bd..79581b52e4 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -97,6 +97,8 @@ pub enum Error { ReadingRefCounts(#[source] io::Error), #[error("Failed to rebuild ref counts")] RebuildingRefCounts(#[source] io::Error), + #[error("Refcount overflow")] + RefcountOverflow(#[source] refcount::Error), #[error("Refcount table offset past file end")] RefcountTableOffEnd, #[error("Too many clusters specified for refcount")] @@ -932,6 +934,7 @@ impl QcowFile { refcount_clusters, refcount_block_entries, cluster_size, + refcount_bits, ) .map_err(Error::ReadingRefCounts)?; @@ -1119,18 +1122,36 @@ impl QcowFile { /// Rebuild the reference count tables. fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> Result<()> { - fn add_ref(refcounts: &mut [u64], cluster_size: u64, cluster_address: u64) -> Result<()> { + fn add_ref( + refcounts: &mut [u64], + cluster_size: u64, + cluster_address: u64, + max_refcount: u64, + refcount_bits: u64, + ) -> Result<()> { let idx = (cluster_address / cluster_size) as usize; if idx >= refcounts.len() { return Err(Error::InvalidClusterIndex); } + if refcounts[idx] >= max_refcount { + return Err(Error::RefcountOverflow(refcount::Error::RefcountOverflow { + value: refcounts[idx] + 1, + max: max_refcount, + refcount_bits, + })); + } refcounts[idx] += 1; Ok(()) } // Add a reference to the first cluster (header plus extensions). - fn set_header_refcount(refcounts: &mut [u64], cluster_size: u64) -> Result<()> { - add_ref(refcounts, cluster_size, 0) + fn set_header_refcount( + refcounts: &mut [u64], + cluster_size: u64, + max_refcount: u64, + refcount_bits: u64, + ) -> Result<()> { + add_ref(refcounts, cluster_size, 0, max_refcount, refcount_bits) } // Add references to the L1 table clusters. @@ -1138,12 +1159,20 @@ impl QcowFile { refcounts: &mut [u64], header: &QcowHeader, cluster_size: u64, + max_refcount: u64, + refcount_bits: u64, ) -> Result<()> { let entries_per_cluster = cluster_size / size_of::() as u64; let l1_clusters = div_round_up_u64(u64::from(header.l1_size), entries_per_cluster); let l1_table_offset = header.l1_table_offset; for i in 0..l1_clusters { - add_ref(refcounts, cluster_size, l1_table_offset + i * cluster_size)?; + add_ref( + refcounts, + cluster_size, + l1_table_offset + i * cluster_size, + max_refcount, + refcount_bits, + )?; } Ok(()) } @@ -1154,6 +1183,8 @@ impl QcowFile { header: &QcowHeader, cluster_size: u64, raw_file: &mut QcowRawFile, + max_refcount: u64, + refcount_bits: u64, ) -> Result<()> { let l1_table = raw_file .read_pointer_table( @@ -1166,7 +1197,13 @@ impl QcowFile { let l2_addr_disk = *l1_table.get(l1_index).ok_or(Error::InvalidIndex)?; if l2_addr_disk != 0 { // Add a reference to the L2 table cluster itself. - add_ref(refcounts, cluster_size, l2_addr_disk)?; + add_ref( + refcounts, + cluster_size, + l2_addr_disk, + max_refcount, + refcount_bits, + )?; // Read the L2 table and find all referenced data clusters. let l2_table = raw_file @@ -1178,7 +1215,13 @@ impl QcowFile { .map_err(Error::ReadingPointers)?; for data_cluster_addr in l2_table { if data_cluster_addr != 0 { - add_ref(refcounts, cluster_size, data_cluster_addr)?; + add_ref( + refcounts, + cluster_size, + data_cluster_addr, + max_refcount, + refcount_bits, + )?; } } } @@ -1192,6 +1235,8 @@ impl QcowFile { refcounts: &mut [u64], header: &QcowHeader, cluster_size: u64, + max_refcount: u64, + refcount_bits: u64, ) -> Result<()> { let refcount_table_offset = header.refcount_table_offset; for i in 0..u64::from(header.refcount_table_clusters) { @@ -1199,6 +1244,8 @@ impl QcowFile { refcounts, cluster_size, refcount_table_offset + i * cluster_size, + max_refcount, + refcount_bits, )?; } Ok(()) @@ -1211,6 +1258,8 @@ impl QcowFile { refcounts: &mut [u64], cluster_size: u64, refblock_clusters: u64, + max_refcount: u64, + refcount_bits: u64, ) -> Result> { let mut ref_table = vec![0; refblock_clusters as usize]; let mut first_free_cluster: u64 = 0; @@ -1226,7 +1275,13 @@ impl QcowFile { } *refblock_addr = first_free_cluster * cluster_size; - add_ref(refcounts, cluster_size, *refblock_addr)?; + add_ref( + refcounts, + cluster_size, + *refblock_addr, + max_refcount, + refcount_bits, + )?; first_free_cluster += 1; } @@ -1293,6 +1348,11 @@ impl QcowFile { .len(); let refcount_bits = 1u64 << header.refcount_order; + let max_refcount = if refcount_bits == 64 { + u64::MAX + } else { + (1u64 << refcount_bits) - 1 + }; let refcount_block_entries = cluster_size * 8 / refcount_bits; let pointers_per_cluster = cluster_size / size_of::() as u64; let data_clusters = div_round_up_u64(header.size, cluster_size); @@ -1325,13 +1385,38 @@ impl QcowFile { let mut refcounts = vec![0; max_valid_cluster_index as usize]; // Find all references clusters and rebuild refcounts. - set_header_refcount(&mut refcounts, cluster_size)?; - set_l1_refcounts(&mut refcounts, &header, cluster_size)?; - set_data_refcounts(&mut refcounts, &header, cluster_size, raw_file)?; - set_refcount_table_refcounts(&mut refcounts, &header, cluster_size)?; + set_header_refcount(&mut refcounts, cluster_size, max_refcount, refcount_bits)?; + set_l1_refcounts( + &mut refcounts, + &header, + cluster_size, + max_refcount, + refcount_bits, + )?; + set_data_refcounts( + &mut refcounts, + &header, + cluster_size, + raw_file, + max_refcount, + refcount_bits, + )?; + set_refcount_table_refcounts( + &mut refcounts, + &header, + cluster_size, + max_refcount, + refcount_bits, + )?; // Allocate clusters to store the new reference count blocks. - let ref_table = alloc_refblocks(&mut refcounts, cluster_size, refblock_clusters)?; + let ref_table = alloc_refblocks( + &mut refcounts, + cluster_size, + refblock_clusters, + max_refcount, + refcount_bits, + )?; // Write updated reference counts and point the reftable at them. write_refblocks( @@ -1856,6 +1941,9 @@ impl QcowFile { Err(refcount::Error::ReadingRefCounts(e)) => { return Err(e); } + Err(refcount::Error::RefcountOverflow { .. }) => { + return Err(std::io::Error::from_raw_os_error(EINVAL)); + } } } diff --git a/block/src/qcow/refcount.rs b/block/src/qcow/refcount.rs index 5009fef341..8fa3d5bfed 100644 --- a/block/src/qcow/refcount.rs +++ b/block/src/qcow/refcount.rs @@ -29,6 +29,13 @@ 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 = std::result::Result; @@ -42,12 +49,15 @@ pub struct RefCount { 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( raw_file: &mut QcowRawFile, @@ -55,6 +65,7 @@ impl RefCount { refcount_table_entries: u64, refcount_block_entries: u64, cluster_size: u64, + refcount_bits: u64, ) -> io::Result { let ref_table = VecCache::from_vec(raw_file.read_pointer_table( refcount_table_offset, @@ -63,6 +74,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,6 +86,8 @@ impl RefCount { refcount_block_entries, cluster_size, max_valid_cluster_offset, + max_refcount, + refcount_bits, }) } @@ -95,6 +113,14 @@ impl RefCount { refcount: u64, mut new_cluster: Option<(u64, VecCache)>, ) -> Result> { + 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)?; From a6aecad635f45d22853cd6e7e2aad12cb9a0c624 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 23 Jan 2026 11:21:35 +0100 Subject: [PATCH 0479/1893] 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 --- block/src/qcow/mod.rs | 214 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 79581b52e4..5f91aa3bd2 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -2744,6 +2744,220 @@ mod unit_tests { }); } + /// Test all valid refcount orders (0-6) can be opened. + #[test] + fn refcount_all_orders() { + for order in 0..=6u8 { + let mut header = valid_header_v3(); + header[99] = order; + with_basic_file(&header, |disk_file: RawFile| { + QcowFile::from(disk_file).expect("refcount order should work"); + }); + } + } + + /// Test write/read roundtrip for all refcount orders. + #[test] + fn refcount_all_orders_write_read() { + for order in 0..=6u8 { + let mut header = valid_header_v3(); + header[99] = order; + with_basic_file(&header, |disk_file: RawFile| { + let mut q = QcowFile::from(disk_file).unwrap(); + let test_data = b"test data for refcount"; + + // Write and read back + q.write_all(test_data).unwrap(); + q.rewind().unwrap(); + let mut buf = vec![0u8; test_data.len()]; + q.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, test_data); + + // Write to another cluster + q.seek(SeekFrom::Start(0x10000)).unwrap(); + q.write_all(test_data).unwrap(); + q.seek(SeekFrom::Start(0x10000)).unwrap(); + q.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, test_data); + }); + } + } + + /// Test overwrite and multi-cluster allocation for all refcount orders. + #[test] + fn refcount_all_orders_overwrite() { + for order in 0..=6u8 { + let mut header = valid_header_v3(); + header[99] = order; + with_basic_file(&header, |disk_file: RawFile| { + let mut q = QcowFile::from(disk_file).unwrap(); + + // Write then overwrite + q.write_all(b"initial data here!!!").unwrap(); + q.rewind().unwrap(); + let new_data = b"overwritten data!!!!"; + q.write_all(new_data).unwrap(); + q.rewind().unwrap(); + let mut buf = vec![0u8; new_data.len()]; + q.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, new_data); + + // Allocate multiple clusters + let cluster_size = 0x10000u64; + for i in 1..4u64 { + q.seek(SeekFrom::Start(i * cluster_size)).unwrap(); + q.write_all(b"cluster data").unwrap(); + } + for i in 1..4u64 { + let mut cluster_buf = vec![0u8; 12]; + q.seek(SeekFrom::Start(i * cluster_size)).unwrap(); + q.read_exact(&mut cluster_buf).unwrap(); + assert_eq!(&cluster_buf, b"cluster data"); + } + }); + } + } + + /// Test L2 cache eviction for all refcount orders. + #[test] + fn refcount_all_orders_l2_eviction() { + for order in 0..=6u8 { + let mut header = valid_header_v3(); + header[99] = order; + with_basic_file(&header, |disk_file: RawFile| { + let mut q = QcowFile::from(disk_file).unwrap(); + + // L2 cache has 100 entries. Write to >100 regions to force eviction. + let cluster_size = 0x10000u64; + let l2_coverage = cluster_size * (cluster_size / 8); + + for i in 0..110u64 { + q.seek(SeekFrom::Start(i * l2_coverage)).unwrap(); + q.write_all(b"eviction test").unwrap(); + } + + // Verify evicted regions can be re-read + for i in [0u64, 1, 50, 100, 109] { + let mut buf = vec![0u8; 13]; + q.seek(SeekFrom::Start(i * l2_coverage)).unwrap(); + q.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"eviction test"); + } + }); + } + } + + /// Test sub-byte refcount read/write roundtrip with max values. + #[test] + fn refcount_subbyte_max_values() { + for (bits, max_val) in [(1u64, 1u64), (2, 3), (4, 15)] { + let file = vmm_sys_util::tempfile::TempFile::new().unwrap().into_file(); + let cluster_size = 0x10000u64; + file.set_len(cluster_size * 2).unwrap(); + let raw = RawFile::new(file, false); + let mut qcow_raw = QcowRawFile::from(raw, cluster_size, bits).unwrap(); + + let entries = (cluster_size * 8 / bits) as usize; + let mut table: Vec = (0..entries as u64).map(|i| i % (max_val + 1)).collect(); + table[0] = max_val; + table[entries - 1] = max_val; + + qcow_raw.write_refcount_block(cluster_size, &table).unwrap(); + let read_table = qcow_raw.read_refcount_block(cluster_size).unwrap(); + + assert_eq!(read_table.len(), entries); + for (i, (&written, &read)) in table.iter().zip(read_table.iter()).enumerate() { + assert_eq!(read, written & max_val, "{bits}-bit entry {i} mismatch"); + } + } + } + + /// Test byte-aligned refcounts with max values. + #[test] + fn refcount_byte_aligned_large_values() { + for (bits, test_val) in [ + (8u64, 0xFFu64), + (16, 0xFFFFu64), + (32, 0xFFFF_FFFFu64), + (64, u64::MAX), + ] { + let file = vmm_sys_util::tempfile::TempFile::new().unwrap().into_file(); + let cluster_size = 0x10000u64; + file.set_len(cluster_size * 2).unwrap(); + let raw = RawFile::new(file, false); + let mut qcow_raw = QcowRawFile::from(raw, cluster_size, bits).unwrap(); + + let entries = (cluster_size * 8 / bits) as usize; + let mut table: Vec = vec![0; entries]; + table[0] = test_val; + table[1] = 1; + table[entries - 1] = test_val; + + qcow_raw.write_refcount_block(cluster_size, &table).unwrap(); + let read_table = qcow_raw.read_refcount_block(cluster_size).unwrap(); + + assert_eq!(read_table[0], test_val); + assert_eq!(read_table[1], 1); + assert_eq!(read_table[entries - 1], test_val); + } + } + + /// Test RefcountOverflow error when exceeding max refcount value. + #[test] + fn refcount_overflow_returns_error() { + use super::refcount::Error as RefcountError; + + for (refcount_bits, max_val) in [(1u64, 1u64), (2, 3), (4, 15)] { + let file = vmm_sys_util::tempfile::TempFile::new().unwrap().into_file(); + let cluster_size = 0x10000u64; + let refcount_block_entries = cluster_size * 8 / refcount_bits; + file.set_len(cluster_size * 3).unwrap(); + + let raw = RawFile::new(file, false); + let mut qcow_raw = QcowRawFile::from(raw, cluster_size, refcount_bits).unwrap(); + + // Set up refcount table pointing to refcount block + let refcount_table_offset = cluster_size; + qcow_raw + .file_mut() + .seek(SeekFrom::Start(refcount_table_offset)) + .unwrap(); + qcow_raw + .file_mut() + .write_all(&(cluster_size * 2).to_be_bytes()) + .unwrap(); + + let zeros = vec![0u64; refcount_block_entries as usize]; + qcow_raw + .write_refcount_block(cluster_size * 2, &zeros) + .unwrap(); + + let mut refcount = RefCount::new( + &mut qcow_raw, + refcount_table_offset, + 1, + refcount_block_entries, + cluster_size, + refcount_bits, + ) + .unwrap(); + + // Overflow should fail + let result = refcount.set_cluster_refcount(&mut qcow_raw, 0, max_val + 1, None); + assert!( + matches!(result, Err(RefcountError::RefcountOverflow { .. })), + "{refcount_bits}-bit: expected overflow error" + ); + + // Max value should not overflow + let result = refcount.set_cluster_refcount(&mut qcow_raw, 0, max_val, None); + assert!( + !matches!(result, Err(RefcountError::RefcountOverflow { .. })), + "{refcount_bits}-bit: max value should not overflow" + ); + } + } + #[test] fn invalid_cluster_bits() { let mut header = valid_header_v3(); From cc96fc14b449af600a2d2ab2ed899b7577aa0433 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 25 Jan 2026 17:30:13 +0100 Subject: [PATCH 0480/1893] 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 --- block/src/qcow/mod.rs | 60 +++++++++++++++++++++++++++++++-- block/src/qcow/qcow_raw_file.rs | 5 +++ block/src/qcow/raw_file.rs | 18 ++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 5f91aa3bd2..99c8083091 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -231,7 +231,8 @@ bitflags! { impl IncompatFeatures { /// Features supported by this implementation. - const SUPPORTED: IncompatFeatures = IncompatFeatures::COMPRESSION; + const SUPPORTED: IncompatFeatures = + IncompatFeatures::DIRTY.union(IncompatFeatures::COMPRESSION); /// Get the fallback name for a known feature bit. fn flag_name(bit: u8) -> Option<&'static str> { @@ -680,6 +681,39 @@ impl QcowHeader { Ok(()) } + + /// Write only the incompatible_features field to the file at its fixed offset. + fn write_incompatible_features(&self, file: &mut F) -> Result<()> { + if self.version != 3 { + return Ok(()); + } + file.seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) + .map_err(Error::WritingHeader)?; + file.write_u64::(self.incompatible_features) + .map_err(Error::WritingHeader)?; + 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 F, + dirty: bool, + ) -> Result<()> { + 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(Error::WritingHeader)?; + } + Ok(()) + } } fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> u64 { @@ -892,7 +926,18 @@ impl QcowFile { let mut raw_file = QcowRawFile::from(file, cluster_size, refcount_bits) .ok_or(Error::InvalidClusterSize)?; - if refcount_rebuild_required { + let is_writable = raw_file.file().is_writable(); + + // Image already has dirty bit set. Refcounts may be invalid. + if IncompatFeatures::from_bits_truncate(header.incompatible_features) + .contains(IncompatFeatures::DIRTY) + { + log::warn!("QCOW2 image not cleanly closed, rebuilding refcounts"); + refcount_rebuild_required = true; + } + + // Skip refcount rebuilding for readonly files. + if refcount_rebuild_required && is_writable { QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?; } @@ -965,6 +1010,13 @@ impl QcowFile { qcow.find_avail_clusters()?; + if !IncompatFeatures::from_bits_truncate(qcow.header.incompatible_features) + .contains(IncompatFeatures::DIRTY) + && is_writable + { + qcow.header.set_dirty_bit(qcow.raw_file.file_mut(), true)?; + } + Ok(qcow) } @@ -1999,6 +2051,7 @@ impl QcowFile { if sync_required { self.raw_file.file_mut().sync_data()?; } + Ok(()) } } @@ -2012,6 +2065,9 @@ impl AsRawFd for QcowFile { impl Drop for QcowFile { fn drop(&mut self) { let _ = self.sync_caches(); + if self.raw_file.file().is_writable() { + let _ = self.header.set_dirty_bit(self.raw_file.file_mut(), false); + } } } diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index 5b8cf73562..af9823306f 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -276,6 +276,11 @@ impl QcowRawFile { Ok(Some(new_cluster_address)) } + /// Returns a reference to the underlying file. + pub fn file(&self) -> &RawFile { + &self.file + } + /// Returns a mutable reference to the underlying file. pub fn file_mut(&mut self) -> &mut RawFile { &mut self.file diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index b80a30adc2..eda7751c3f 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -15,6 +15,7 @@ use std::os::unix::io::{AsRawFd, RawFd}; use std::slice; use libc::c_void; +use vmm_sys_util::file_traits::FileSync; use vmm_sys_util::seek_hole::SeekHole; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; @@ -122,6 +123,17 @@ impl RawFile { pub fn is_direct(&self) -> bool { self.direct_io } + + /// Returns true if the file was opened with write access. + 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 + } } impl Read for RawFile { @@ -327,6 +339,12 @@ impl PunchHole for RawFile { } } +impl FileSync for RawFile { + fn fsync(&mut self) -> std::io::Result<()> { + self.file.fsync() + } +} + impl SeekHole for RawFile { fn seek_hole(&mut self, offset: u64) -> std::io::Result> { match self.file.seek_hole(offset) { From 82dc9bfaee14ca6faaf697ee7467dd9fab958ed0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 25 Jan 2026 17:31:45 +0100 Subject: [PATCH 0481/1893] 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 --- block/src/qcow/mod.rs | 123 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 8 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 99c8083091..f5b139ee7f 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -3819,16 +3819,13 @@ mod unit_tests { } #[test] - fn reject_unsupported_incompat_dirty_bit() { - // Bit 0: dirty - image not closed cleanly + fn accept_incompat_dirty_bit() { let header = header_v3_with_incompat_features(1 << 0); with_basic_file(&header, |disk_file: RawFile| { let result = QcowFile::from(disk_file); - assert!(result.is_err()); - let err = result.unwrap_err(); assert!( - matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("dirty")), - "Expected UnsupportedFeature error mentioning dirty, got: {err:?}" + result.is_ok(), + "Expected dirty bit to be accepted, got: {result:?}" ); }); } @@ -3880,8 +3877,8 @@ mod unit_tests { #[test] fn reject_multiple_unsupported_incompat_bits() { - // Multiple unsupported bits: dirty (0) + corrupt (1) - let header = header_v3_with_incompat_features((1 << 0) | (1 << 1)); + // Multiple unsupported bits: corrupt (1) + external data (2) + let header = header_v3_with_incompat_features((1 << 1) | (1 << 2)); with_basic_file(&header, |disk_file: RawFile| { let result = QcowFile::from(disk_file); assert!(result.is_err()); @@ -3903,4 +3900,114 @@ mod unit_tests { ); }); } + + #[test] + fn dirty_bit_set_on_open_cleared_on_close_v3() { + // Test that the dirty bit is set when a v3 image is opened and cleared when it's closed + let header = valid_header_v3(); + with_basic_file(&header, |mut disk_file: RawFile| { + // Verify dirty bit is not set initially + disk_file + .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) + .unwrap(); + let features_before = disk_file.read_u64::().unwrap(); + assert_eq!( + features_before & IncompatFeatures::DIRTY.bits(), + 0, + "Dirty bit should not be set initially" + ); + + // Open the file - this should set the dirty bit + disk_file.rewind().unwrap(); + { + let qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + + // Verify dirty bit is set while file is open + disk_file + .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) + .unwrap(); + let features_during = disk_file.read_u64::().unwrap(); + assert_ne!( + features_during & IncompatFeatures::DIRTY.bits(), + 0, + "Dirty bit should be set while file is open" + ); + + drop(qcow); // Close the file + } + + // Verify dirty bit is cleared after close + disk_file + .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) + .unwrap(); + let features_after = disk_file.read_u64::().unwrap(); + assert_eq!( + features_after & IncompatFeatures::DIRTY.bits(), + 0, + "Dirty bit should be cleared after close" + ); + }); + } + + #[test] + fn dirty_bit_not_used_for_v2() { + // Test that v2 images don't use the dirty bit (no incompatible_features field) + let header = valid_header_v2(); + with_basic_file(&header, |mut disk_file: RawFile| { + // Open and close v2 file - should work without touching offset 72 + disk_file.rewind().unwrap(); + let qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + assert_eq!(qcow.header.version, 2, "Should be a v2 file"); + drop(qcow); + }); + } + + #[test] + fn dirty_bit_not_set_for_readonly_v3() { + // Test that read-only v3 files don't set the dirty bit (e.g., backing files) + let header = valid_header_v3(); + + // Create a temp file with a valid v3 qcow header + let temp_file = TempFile::new().unwrap(); + let temp_path = temp_file.as_path().to_owned(); + { + let mut file = temp_file.as_file().try_clone().unwrap(); + file.write_all(&header).unwrap(); + file.set_len(0x1_0000_0000).unwrap(); + } + + // Open the file read-only + let readonly_file = OpenOptions::new() + .read(true) + .write(false) + .open(&temp_path) + .unwrap(); + let raw_file = RawFile::new(readonly_file, false); + + // Verify the file is detected as read-only + assert!( + !raw_file.is_writable(), + "File should be detected as read-only" + ); + + // Open as QcowFile - should not set dirty bit for read-only files + let qcow = QcowFile::from(raw_file).unwrap(); + assert!( + !qcow.raw_file.file().is_writable(), + "File should be read-only" + ); + + // Verify dirty bit was not written to disk + let verify_file = OpenOptions::new().read(true).open(&temp_path).unwrap(); + let mut verify_raw = RawFile::new(verify_file, false); + verify_raw + .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) + .unwrap(); + let features = verify_raw.read_u64::().unwrap(); + assert_eq!( + features & IncompatFeatures::DIRTY.bits(), + 0, + "Dirty bit should not be written for read-only files" + ); + } } From 919cce9b5824ec3ae8174bf362a495d115d02e64 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 25 Jan 2026 21:35:47 +0100 Subject: [PATCH 0482/1893] 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 --- cloud-hypervisor/tests/integration.rs | 159 +++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index c8b31030fa..6a7acfaa6b 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2528,7 +2528,7 @@ EOF mod common_parallel { use std::cmp; - use std::fs::{File, OpenOptions}; + use std::fs::{File, OpenOptions, copy}; use std::io::{self, SeekFrom}; use crate::*; @@ -3499,7 +3499,13 @@ mod common_parallel { ); }); - let _ = cloud_child.kill(); + if verify_os_disk { + // Use clean shutdown to allow cloud-hypervisor to clear + // the dirty bit in the QCOW2 v3 image. + kill_child(&mut cloud_child); + } else { + let _ = cloud_child.kill(); + } let output = cloud_child.wait_with_output().unwrap(); handle_child_output(r, &output); @@ -3537,12 +3543,25 @@ mod common_parallel { } fn get_image_info(path: &std::path::Path) -> Option { - let output = run_qemu_img(path, &["info", "--output=json"]); + let output = run_qemu_img(path, &["info", "-U", "--output=json"]); output.status.success().then(|| ())?; serde_json::from_slice(&output.stdout).ok() } + fn check_dirty_flag(path: &std::path::Path) -> Result, String> { + let info = get_image_info(path) + .ok_or_else(|| format!("qemu-img info failed for {}", path.display()))?; + if info["format"].as_str() != Some("qcow2") { + return Ok(None); + } + // QCOW2 v3 has compat "1.1", v2 has "0.10" and doesn't support dirty flag + if info["format-specific"]["data"]["compat"].as_str() != Some("1.1") { + return Ok(None); + } + Ok(info["dirty-flag"].as_bool()) + } + fn resolve_disk_path(path_or_image_name: impl AsRef) -> std::path::PathBuf { if path_or_image_name.as_ref().exists() { // A full path is provided @@ -3662,9 +3681,11 @@ mod common_parallel { /// It takes either a full path to the image or just the name of /// the image located in the `workloads` directory. /// - /// For qcow2 images with backing files, also verifies the backing file + /// For QCOW2 images with backing files, also verifies the backing file /// integrity and checks that the backing file hasn't been modified /// during the test. + /// + /// For QCOW2 v3 images, also verifies the dirty bit is cleared. fn disk_check_consistency( path_or_image_name: impl AsRef, initial_backing_checksum: Option<(std::path::PathBuf, String, u32)>, @@ -3678,6 +3699,14 @@ mod common_parallel { String::from_utf8_lossy(&output.stderr) ); + match check_dirty_flag(&path) { + Ok(Some(dirty)) => { + assert!(!dirty, "QCOW2 image shutdown unclean"); + } + Ok(None) => {} // Not a QCOW2 v3 image, skip dirty flag check + Err(e) => panic!("Failed to check dirty flag: {e}"), + } + if let Some((backing_path, format, initial_checksum)) = initial_backing_checksum { if format.parse::().ok() != Some(block::qcow::ImageType::Raw) { let output = run_qemu_img(&backing_path, &["check"]); @@ -3733,6 +3762,128 @@ mod common_parallel { _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE, false, false, true); } + #[test] + fn test_virtio_block_qcow2_dirty_bit_unclean_shutdown() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_image_path = guest.tmp_dir.as_path().join("test-dirty.qcow2"); + let original_image = guest.disk_config.disk(DiskType::OperatingSystem).unwrap(); + + copy(original_image, &test_image_path).expect("Failed to copy qcow2 image"); + + assert_eq!( + check_dirty_flag(&test_image_path).expect("Failed to check dirty flag"), + Some(false), + "Image should start with dirty bit cleared" + ); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + &format!("path={}", test_image_path.to_str().unwrap()), + &format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot(None).unwrap(); + + assert_eq!( + check_dirty_flag(&test_image_path).expect("Failed to check dirty flag"), + Some(true), + "Dirty bit should be set while VM is running" + ); + }); + + if r.is_err() { + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + return; + } + + // Simulate unclean shutdown with SIGKILL + let _ = unsafe { libc::kill(child.id() as i32, libc::SIGKILL) }; + let _ = child.wait(); + + assert_eq!( + check_dirty_flag(&test_image_path).expect("Failed to check dirty flag"), + Some(true), + "Dirty bit should remain set after unclean shutdown" + ); + } + + #[test] + fn test_virtio_block_qcow2_dirty_bit_clean_shutdown() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_image_path = guest.tmp_dir.as_path().join("test-dirty.qcow2"); + let original_image = guest.disk_config.disk(DiskType::OperatingSystem).unwrap(); + + copy(original_image, &test_image_path).expect("Failed to copy qcow2 image"); + + assert_eq!( + check_dirty_flag(&test_image_path).expect("Failed to check dirty flag"), + Some(false), + "Image should start with dirty bit cleared" + ); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + &format!("path={}", test_image_path.to_str().unwrap()), + &format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot(None).unwrap(); + + assert_eq!( + check_dirty_flag(&test_image_path).expect("Failed to check dirty flag"), + Some(true), + "Dirty bit should be set while VM is running" + ); + }); + + // Clean shutdown using SIGTERM + kill_child(&mut child); + + if r.is_err() { + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + return; + } + + let _ = child.wait(); + + disk_check_consistency(&test_image_path, None); + } + #[test] fn test_virtio_block_vhd() { let mut workload_path = dirs::home_dir().unwrap(); From 76822d853a7ab315d6635ce9a62ac80ee7ce8e9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:52:44 +0000 Subject: [PATCH 0483/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 32011c9bc3..b8739ac08b 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.42.1 + - uses: crate-ci/typos@v1.42.2 From 79953fece013287dd0d3b0cc276a625041383e7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 01:04:19 +0000 Subject: [PATCH 0484/1893] 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] --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 6 +++--- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 37 +++++++++++++++++++------------------ vmm/Cargo.toml | 2 +- 5 files changed, 42 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b3208e293..508ec7a3ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -369,9 +369,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.53" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "jobserver", @@ -867,9 +867,9 @@ dependencies = [ [[package]] name = "gdbstub" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72742d2b395902caf8a5d520d0dd3334ba6d1138938429200e58d5174e275f3f" +checksum = "6bf845b08f7c2ef3b5ad19f80779d43ae20d278652b91bb80adda65baf2d8ed6" dependencies = [ "bitflags 2.10.0", "cfg-if", @@ -1686,18 +1686,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1948,9 +1948,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952" +checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e" dependencies = [ "libc", "signal-hook-registry", @@ -2219,9 +2219,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -2797,18 +2797,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" dependencies = [ "proc-macro2", "quote", @@ -2817,9 +2817,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 1e63a52968..91c4ea92d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,11 +87,11 @@ flume = "0.12.0" itertools = "0.14.0" libc = "0.2.180" log = "0.4.29" -signal-hook = "0.4.1" +signal-hook = "0.4.3" thiserror = "2.0.18" -uuid = { version = "1.19.0" } +uuid = { version = "1.20.0" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.33", default-features = false } +zerocopy = { version = "0.8.34", default-features = false } [workspace.lints.clippy] # Any clippy lint (group) in alphabetical order: diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 12b2fd1af6..2dd56b8f77 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.33", features = [ +zerocopy = { version = "0.8.34", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 13621a31a0..4363adfd8b 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -139,6 +139,7 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" name = "block" version = "0.1.0" dependencies = [ + "bitflags 2.10.0", "byteorder", "crc-any", "flate2", @@ -171,9 +172,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.53" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "jobserver", @@ -466,9 +467,9 @@ checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "gdbstub" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72742d2b395902caf8a5d520d0dd3334ba6d1138938429200e58d5174e275f3f" +checksum = "6bf845b08f7c2ef3b5ad19f80779d43ae20d278652b91bb80adda65baf2d8ed6" dependencies = [ "bitflags 2.10.0", "cfg-if", @@ -886,18 +887,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1057,9 +1058,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952" +checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e" dependencies = [ "libc", "signal-hook-registry", @@ -1219,9 +1220,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -1581,18 +1582,18 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" dependencies = [ "proc-macro2", "quote", @@ -1601,9 +1602,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" [[package]] name = "zstd" diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index eec935340f..6310e3428b 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -48,7 +48,7 @@ epoll = { workspace = true } event_monitor = { path = "../event_monitor" } flume = { workspace = true } futures = { version = "0.3.31", optional = true } -gdbstub = { version = "0.7.8", optional = true } +gdbstub = { version = "0.7.9", optional = true } gdbstub_arch = { version = "0.3.2", optional = true } hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } From 8cdc3b53c01c0fdc2aff4defb89470cadc60c80b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 26 Jan 2026 15:33:52 +0100 Subject: [PATCH 0485/1893] 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 Signed-off-by: Anatol Belski --- block/src/qcow/qcow_raw_file.rs | 60 ++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index af9823306f..92a569d347 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -4,6 +4,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +use std::fmt::Debug; use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write}; use std::mem::size_of; use std::os::fd::{AsRawFd, RawFd}; @@ -18,53 +19,70 @@ type RefcountReader = fn(&mut RawFile, usize) -> io::Result>; type RefcountWriter = fn(&mut RawFile, &[u64]) -> io::Result<()>; /// Big-endian file access trait. -trait BeUint: Sized + Copy { - fn from_slice(bytes: &[u8]) -> u64; - fn write(w: &mut W, val: u64) -> io::Result<()>; +pub(super) trait BeUint: Sized + Copy { + fn from_be_slice(bytes: &[u8]) -> u64; + fn read_be(r: &mut R) -> io::Result; + fn write_be(w: &mut W, val: Self) -> io::Result<()>; } impl BeUint for u8 { #[inline(always)] - fn from_slice(bytes: &[u8]) -> u64 { + fn from_be_slice(bytes: &[u8]) -> u64 { bytes[0] as u64 } #[inline(always)] - fn write(w: &mut W, val: u64) -> io::Result<()> { - w.write_u8(val as u8) + fn read_be(r: &mut R) -> io::Result { + r.read_u8() + } + #[inline(always)] + fn write_be(w: &mut W, val: Self) -> io::Result<()> { + w.write_u8(val) } } impl BeUint for u16 { #[inline(always)] - fn from_slice(bytes: &[u8]) -> u64 { + fn from_be_slice(bytes: &[u8]) -> u64 { u16::from_be_bytes([bytes[0], bytes[1]]) as u64 } #[inline(always)] - fn write(w: &mut W, val: u64) -> io::Result<()> { - w.write_u16::(val as u16) + fn read_be(r: &mut R) -> io::Result { + r.read_u16::() + } + #[inline(always)] + fn write_be(w: &mut W, val: Self) -> io::Result<()> { + w.write_u16::(val) } } impl BeUint for u32 { #[inline(always)] - fn from_slice(bytes: &[u8]) -> u64 { + 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(w: &mut W, val: u64) -> io::Result<()> { - w.write_u32::(val as u32) + fn read_be(r: &mut R) -> io::Result { + r.read_u32::() + } + #[inline(always)] + fn write_be(w: &mut W, val: Self) -> io::Result<()> { + w.write_u32::(val) } } impl BeUint for u64 { #[inline(always)] - fn from_slice(bytes: &[u8]) -> u64 { + 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(w: &mut W, val: u64) -> io::Result<()> { + fn read_be(r: &mut R) -> io::Result { + r.read_u64::() + } + #[inline(always)] + fn write_be(w: &mut W, val: Self) -> io::Result<()> { w.write_u64::(val) } } @@ -76,16 +94,20 @@ fn read_refcount(file: &mut RawFile, count: usize) -> io::Result(file: &mut RawFile, table: &[u64]) -> io::Result<()> { +fn write_refcount>(file: &mut RawFile, table: &[u64]) -> io::Result<()> +where + >::Error: Debug, +{ let bytes_per_entry = size_of::(); let mut buffer = BufWriter::with_capacity(table.len() * bytes_per_entry, file); for &val in table { - T::write(&mut buffer, val)?; + let converted = T::try_from(val).expect("refcount values are validated on increment"); + T::write_be(&mut buffer, converted)?; } buffer.flush() } @@ -224,7 +246,7 @@ impl QcowRawFile { for addr in entries { let entry = f(self, *addr)?; - buffer.write_u64::(entry)?; + u64::write_be(&mut buffer, entry)?; } buffer.flush()?; Ok(()) @@ -239,7 +261,7 @@ impl QcowRawFile { let mut buffer = self.setup_pointer_table_writer(offset, &entries)?; for &entry in entries { - buffer.write_u64::(entry)?; + u64::write_be(&mut buffer, entry)?; } buffer.flush()?; Ok(()) From 87e8ac3f1fa1e6fdeb9850da576fffaaea4109ac Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 26 Jan 2026 15:34:20 +0100 Subject: [PATCH 0486/1893] 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 Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 147 ++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 83 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index f5b139ee7f..308014c1a1 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -19,7 +19,6 @@ use std::os::fd::{AsRawFd, RawFd}; use std::str::{self, FromStr}; use bitflags::bitflags; -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use libc::{EINVAL, EIO, ENOSPC}; use log::error; use remain::sorted; @@ -30,7 +29,7 @@ use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::BlockBackend; use crate::qcow::decoder::{Decoder, ZlibDecoder, ZstdDecoder}; -use crate::qcow::qcow_raw_file::QcowRawFile; +use crate::qcow::qcow_raw_file::{BeUint, QcowRawFile}; pub use crate::qcow::raw_file::RawFile; use crate::qcow::refcount::RefCount; use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache}; @@ -376,12 +375,12 @@ impl QcowHeader { .map_err(Error::ReadingHeader)?; loop { - let ext_type = f.read_u32::().map_err(Error::ReadingHeader)?; + let ext_type = u32::read_be(f).map_err(Error::ReadingHeader)?; if ext_type == HEADER_EXT_END { break; } - let ext_length = f.read_u32::().map_err(Error::ReadingHeader)?; + let ext_length = u32::read_be(f).map_err(Error::ReadingHeader)?; match ext_type { HEADER_EXT_BACKING_FORMAT => { @@ -428,67 +427,55 @@ impl QcowHeader { /// Creates a QcowHeader from a reference to a file. pub fn new(f: &mut RawFile) -> Result { f.rewind().map_err(Error::ReadingHeader)?; - let magic = f.read_u32::().map_err(Error::ReadingHeader)?; + let magic = u32::read_be(f).map_err(Error::ReadingHeader)?; if magic != QCOW_MAGIC { return Err(Error::InvalidMagic); } // Reads the next u32 from the file. - fn read_u32_from_file(f: &mut RawFile) -> Result { - f.read_u32::().map_err(Error::ReadingHeader) + fn read_u32_be(f: &mut RawFile) -> Result { + u32::read_be(f).map_err(Error::ReadingHeader) } // Reads the next u64 from the file. - fn read_u64_from_file(f: &mut RawFile) -> Result { - f.read_u64::().map_err(Error::ReadingHeader) + fn read_u64_be(f: &mut RawFile) -> Result { + u64::read_be(f).map_err(Error::ReadingHeader) } - let version = read_u32_from_file(f)?; + let version = read_u32_be(f)?; let mut header = QcowHeader { magic, version, - backing_file_offset: read_u64_from_file(f)?, - backing_file_size: read_u32_from_file(f)?, - cluster_bits: read_u32_from_file(f)?, - size: read_u64_from_file(f)?, - crypt_method: read_u32_from_file(f)?, - l1_size: read_u32_from_file(f)?, - l1_table_offset: read_u64_from_file(f)?, - refcount_table_offset: read_u64_from_file(f)?, - refcount_table_clusters: read_u32_from_file(f)?, - nb_snapshots: read_u32_from_file(f)?, - snapshots_offset: read_u64_from_file(f)?, - incompatible_features: if version == 2 { - 0 - } else { - read_u64_from_file(f)? - }, - compatible_features: if version == 2 { - 0 - } else { - read_u64_from_file(f)? - }, - autoclear_features: if version == 2 { - 0 - } else { - read_u64_from_file(f)? - }, + backing_file_offset: read_u64_be(f)?, + backing_file_size: read_u32_be(f)?, + cluster_bits: read_u32_be(f)?, + size: read_u64_be(f)?, + crypt_method: read_u32_be(f)?, + l1_size: read_u32_be(f)?, + l1_table_offset: read_u64_be(f)?, + refcount_table_offset: read_u64_be(f)?, + refcount_table_clusters: read_u32_be(f)?, + nb_snapshots: read_u32_be(f)?, + snapshots_offset: read_u64_be(f)?, + incompatible_features: if version == 2 { 0 } else { read_u64_be(f)? }, + compatible_features: if version == 2 { 0 } else { read_u64_be(f)? }, + autoclear_features: if version == 2 { 0 } else { read_u64_be(f)? }, refcount_order: if version == 2 { DEFAULT_REFCOUNT_ORDER } else { - read_u32_from_file(f)? + read_u32_be(f)? }, header_size: if version == 2 { V2_BARE_HEADER_SIZE } else { - read_u32_from_file(f)? + read_u32_be(f)? }, compression_type: CompressionType::Zlib, backing_file: None, }; if version == 3 && header.header_size > V3_BARE_HEADER_SIZE { - let raw_compression_type = read_u64_from_file(f)? >> (64 - 8); + let raw_compression_type = read_u64_be(f)? >> (64 - 8); header.compression_type = if raw_compression_type == COMPRESSION_TYPE_ZLIB { Ok(CompressionType::Zlib) } else if raw_compression_type == COMPRESSION_TYPE_ZSTD { @@ -620,44 +607,42 @@ impl QcowHeader { /// Write the header to `file`. pub fn write_to(&self, file: &mut F) -> Result<()> { // Writes the next u32 to the file. - fn write_u32_to_file(f: &mut F, value: u32) -> Result<()> { - f.write_u32::(value) - .map_err(Error::WritingHeader) + fn write_u32_be(f: &mut F, value: u32) -> Result<()> { + u32::write_be(f, value).map_err(Error::WritingHeader) } // Writes the next u64 to the file. - fn write_u64_to_file(f: &mut F, value: u64) -> Result<()> { - f.write_u64::(value) - .map_err(Error::WritingHeader) - } - - write_u32_to_file(file, self.magic)?; - write_u32_to_file(file, self.version)?; - write_u64_to_file(file, self.backing_file_offset)?; - write_u32_to_file(file, self.backing_file_size)?; - write_u32_to_file(file, self.cluster_bits)?; - write_u64_to_file(file, self.size)?; - write_u32_to_file(file, self.crypt_method)?; - write_u32_to_file(file, self.l1_size)?; - write_u64_to_file(file, self.l1_table_offset)?; - write_u64_to_file(file, self.refcount_table_offset)?; - write_u32_to_file(file, self.refcount_table_clusters)?; - write_u32_to_file(file, self.nb_snapshots)?; - write_u64_to_file(file, self.snapshots_offset)?; + fn write_u64_be(f: &mut F, value: u64) -> Result<()> { + u64::write_be(f, value).map_err(Error::WritingHeader) + } + + write_u32_be(file, self.magic)?; + write_u32_be(file, self.version)?; + write_u64_be(file, self.backing_file_offset)?; + write_u32_be(file, self.backing_file_size)?; + write_u32_be(file, self.cluster_bits)?; + write_u64_be(file, self.size)?; + write_u32_be(file, self.crypt_method)?; + write_u32_be(file, self.l1_size)?; + write_u64_be(file, self.l1_table_offset)?; + write_u64_be(file, self.refcount_table_offset)?; + write_u32_be(file, self.refcount_table_clusters)?; + write_u32_be(file, self.nb_snapshots)?; + write_u64_be(file, self.snapshots_offset)?; if self.version == 3 { - write_u64_to_file(file, self.incompatible_features)?; - write_u64_to_file(file, self.compatible_features)?; - write_u64_to_file(file, self.autoclear_features)?; - write_u32_to_file(file, self.refcount_order)?; - write_u32_to_file(file, self.header_size)?; + write_u64_be(file, self.incompatible_features)?; + write_u64_be(file, self.compatible_features)?; + write_u64_be(file, self.autoclear_features)?; + write_u32_be(file, self.refcount_order)?; + write_u32_be(file, self.header_size)?; if self.header_size > V3_BARE_HEADER_SIZE { - write_u64_to_file(file, 0)?; // no compression + write_u64_be(file, 0)?; // no compression } - write_u32_to_file(file, 0)?; // header extension type: end of header extension area - write_u32_to_file(file, 0)?; // length of header extension data: 0 + write_u32_be(file, 0)?; // header extension type: end of header extension area + write_u32_be(file, 0)?; // length of header extension data: 0 } if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) { @@ -689,8 +674,7 @@ impl QcowHeader { } file.seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) .map_err(Error::WritingHeader)?; - file.write_u64::(self.incompatible_features) - .map_err(Error::WritingHeader)?; + u64::write_be(file, self.incompatible_features).map_err(Error::WritingHeader)?; Ok(()) } @@ -909,12 +893,11 @@ impl QcowFile { let mut refcount_rebuild_required = true; file.seek(SeekFrom::Start(header.refcount_table_offset)) .map_err(Error::SeekingFile)?; - let first_refblock_addr = file.read_u64::().map_err(Error::ReadingHeader)?; + let first_refblock_addr = u64::read_be(&mut file).map_err(Error::ReadingHeader)?; if first_refblock_addr != 0 { file.seek(SeekFrom::Start(first_refblock_addr)) .map_err(Error::SeekingFile)?; - let first_cluster_refcount = - file.read_u16::().map_err(Error::ReadingHeader)?; + let first_cluster_refcount = u16::read_be(&mut file).map_err(Error::ReadingHeader)?; if first_cluster_refcount != 0 { refcount_rebuild_required = false; } @@ -2371,7 +2354,7 @@ pub fn convert( pub fn detect_image_type(file: &mut RawFile) -> Result { let orig_seek = file.stream_position().map_err(Error::SeekingFile)?; file.rewind().map_err(Error::SeekingFile)?; - let magic = file.read_u32::().map_err(Error::ReadingHeader)?; + let magic = u32::read_be(file).map_err(Error::ReadingHeader)?; let image_type = if magic == QCOW_MAGIC { ImageType::Qcow2 } else { @@ -2607,10 +2590,8 @@ mod unit_tests { disk_file .seek(SeekFrom::Start(header.header_size as u64)) .unwrap(); - disk_file.write_u32::(ext_type).unwrap(); - disk_file - .write_u32::(ext_data.len() as u32) - .unwrap(); + u32::write_be(&mut disk_file, ext_type).unwrap(); + u32::write_be(&mut disk_file, ext_data.len() as u32).unwrap(); disk_file.write_all(ext_data).unwrap(); // Add padding to 8-byte boundary @@ -2619,7 +2600,7 @@ mod unit_tests { disk_file.write_all(&vec![0u8; padding]).unwrap(); } - disk_file.write_u32::(HEADER_EXT_END).unwrap(); + u32::write_be(&mut disk_file, HEADER_EXT_END).unwrap(); disk_file.rewind().unwrap(); @@ -3910,7 +3891,7 @@ mod unit_tests { disk_file .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) .unwrap(); - let features_before = disk_file.read_u64::().unwrap(); + let features_before = u64::read_be(&mut disk_file).unwrap(); assert_eq!( features_before & IncompatFeatures::DIRTY.bits(), 0, @@ -3926,7 +3907,7 @@ mod unit_tests { disk_file .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) .unwrap(); - let features_during = disk_file.read_u64::().unwrap(); + let features_during = u64::read_be(&mut disk_file).unwrap(); assert_ne!( features_during & IncompatFeatures::DIRTY.bits(), 0, @@ -3940,7 +3921,7 @@ mod unit_tests { disk_file .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) .unwrap(); - let features_after = disk_file.read_u64::().unwrap(); + let features_after = u64::read_be(&mut disk_file).unwrap(); assert_eq!( features_after & IncompatFeatures::DIRTY.bits(), 0, @@ -4003,7 +3984,7 @@ mod unit_tests { verify_raw .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) .unwrap(); - let features = verify_raw.read_u64::().unwrap(); + let features = u64::read_be(&mut verify_raw).unwrap(); assert_eq!( features & IncompatFeatures::DIRTY.bits(), 0, From 929df76e1a4633743c94f8bd8e60105e333a1acc Mon Sep 17 00:00:00 2001 From: Thomas Leroy Date: Mon, 1 Dec 2025 15:22:59 +0000 Subject: [PATCH 0487/1893] 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 --- virtio-devices/src/vsock/device.rs | 11 +++- virtio-devices/src/vsock/packet.rs | 84 +++++++++++++++++++----------- 2 files changed, 65 insertions(+), 30 deletions(-) diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 506e2dcbab..27a0af1ff2 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -130,7 +130,16 @@ where ) { Ok(mut pkt) => { if self.backend.write().unwrap().recv_pkt(&mut pkt).is_ok() { - pkt.hdr().len() as u32 + pkt.len() + match pkt.commit_hdr(&*self.mem.memory()) { + Ok(()) => pkt.hdr().len() as u32 + pkt.len(), + Err(err) => { + warn!( + "vsock: Error writing packet header to guest memory: \ + {err:?}. Discarding the package." + ); + 0 + } + } } else { // We are using a consuming iterator over the virtio buffers, so, if we can't // fill in this buffer, we'll need to undo the last iterator step. diff --git a/virtio-devices/src/vsock/packet.rs b/virtio-devices/src/vsock/packet.rs index a9e477c1da..65d41b06bc 100644 --- a/virtio-devices/src/vsock/packet.rs +++ b/virtio-devices/src/vsock/packet.rs @@ -20,7 +20,7 @@ use std::ops::Deref; use byteorder::{ByteOrder, LittleEndian}; use virtio_queue::DescriptorChain; -use vm_memory::{Address, GuestMemory}; +use vm_memory::{Address, Bytes, GuestAddress, GuestMemory}; use vm_virtio::{AccessPlatform, Translatable}; use super::{Result, VsockError, defs}; @@ -96,7 +96,10 @@ const HDROFF_FWD_CNT: usize = 40; /// - (an optional) data/buffer descriptor, only present for data packets (VSOCK_OP_RW). /// pub struct VsockPacket { - hdr: *mut u8, + // We still hold the header address in guest memory. We need to write back the modified + // header in RX buffers. + guest_hdr_addr: GuestAddress, + hdr: [u8; VSOCK_PKT_HDR_SIZE], buf: Option<*mut u8>, buf_size: usize, } @@ -129,14 +132,23 @@ impl VsockPacket { return Err(VsockError::HdrDescTooSmall(head.len())); } + let guest_hdr_addr = head + .addr() + .translate_gva(access_platform, VSOCK_PKT_HDR_SIZE); + + // To avoid TOCTOU issues when reading/writing the VSock packet header in guest memory, + // we need to copy the content of the header in the VMM's memory. + // After the copy, the hdr content can be trusted since the guest can't change its + // content anymore. + let mut hdr = [0u8; VSOCK_PKT_HDR_SIZE]; + desc_chain + .memory() + .read_slice(hdr.as_mut_slice(), guest_hdr_addr) + .map_err(|_| VsockError::GuestMemory)?; + let mut pkt = Self { - hdr: get_host_address_range( - desc_chain.memory(), - head.addr() - .translate_gva(access_platform, VSOCK_PKT_HDR_SIZE), - VSOCK_PKT_HDR_SIZE, - ) - .ok_or(VsockError::GuestMemory)?, + guest_hdr_addr, + hdr, buf: None, buf_size: 0, }; @@ -221,19 +233,28 @@ impl VsockPacket { return Err(VsockError::HdrDescTooSmall(head.len())); } + let guest_hdr_addr = head + .addr() + .translate_gva(access_platform, VSOCK_PKT_HDR_SIZE); + + // To avoid TOCTOU issues when reading/writing the VSock packet header in guest memory, + // we need to copy the content of the header in the VMM's memory. + // After the copy, the hdr content can be trusted since the guest can't change its + // content anymore. + let mut hdr = [0u8; VSOCK_PKT_HDR_SIZE]; + desc_chain + .memory() + .read_slice(hdr.as_mut_slice(), guest_hdr_addr) + .map_err(|_| VsockError::GuestMemory)?; + // Prior to Linux v6.3 there are two descriptors if head.has_next() { let buf_desc = desc_chain.next().ok_or(VsockError::BufDescMissing)?; let buf_size = buf_desc.len() as usize; Ok(Self { - hdr: get_host_address_range( - desc_chain.memory(), - head.addr() - .translate_gva(access_platform, VSOCK_PKT_HDR_SIZE), - VSOCK_PKT_HDR_SIZE, - ) - .ok_or(VsockError::GuestMemory)?, + guest_hdr_addr, + hdr, buf: Some( get_host_address_range( desc_chain.memory(), @@ -247,13 +268,8 @@ impl VsockPacket { } else { let buf_size: usize = head.len() as usize - VSOCK_PKT_HDR_SIZE; Ok(Self { - hdr: get_host_address_range( - desc_chain.memory(), - head.addr() - .translate_gva(access_platform, VSOCK_PKT_HDR_SIZE), - VSOCK_PKT_HDR_SIZE, - ) - .ok_or(VsockError::GuestMemory)?, + guest_hdr_addr, + hdr, buf: Some( get_host_address_range( desc_chain.memory(), @@ -273,17 +289,27 @@ impl VsockPacket { /// Provides in-place, byte-slice, access to the vsock packet header. /// pub fn hdr(&self) -> &[u8] { - // SAFETY: bound checks have already been performed when creating the packet - // from the virtq descriptor. - unsafe { std::slice::from_raw_parts(self.hdr as *const u8, VSOCK_PKT_HDR_SIZE) } + self.hdr.as_slice() } /// Provides in-place, byte-slice, mutable access to the vsock packet header. /// pub fn hdr_mut(&mut self) -> &mut [u8] { - // SAFETY: bound checks have already been performed when creating the packet - // from the virtq descriptor. - unsafe { std::slice::from_raw_parts_mut(self.hdr, VSOCK_PKT_HDR_SIZE) } + self.hdr.as_mut_slice() + } + + /// Writes the local copy of the packet header to the guest memory. + /// + pub fn commit_hdr(&mut self, guest_mem: &M) -> Result<()> { + if self.len() as usize > defs::MAX_PKT_BUF_SIZE { + return Err(VsockError::InvalidPktLen(self.len())); + } + + guest_mem + .write(self.hdr(), self.guest_hdr_addr) + .map_err(|_| VsockError::GuestMemory)?; + + Ok(()) } /// Provides in-place, byte-slice access to the vsock packet data buffer. From 111f37f9262d8dcaa9a478037bb1533953aea1b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 23:54:07 +0000 Subject: [PATCH 0488/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index b8739ac08b..2eff41f4c6 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.42.2 + - uses: crate-ci/typos@v1.42.3 From 771ab1d5a35a6074994813d816a6e19b0d4cdd0f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 26 Jan 2026 14:49:18 +0100 Subject: [PATCH 0489/1893] 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 --- block/src/qcow/mod.rs | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 308014c1a1..a2f81cec12 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -12,7 +12,7 @@ mod vec_cache; use std::cmp::{max, min}; use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; -use std::fs::OpenOptions; +use std::fs::{OpenOptions, read_link}; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::mem::size_of; use std::os::fd::{AsRawFd, RawFd}; @@ -20,7 +20,7 @@ use std::str::{self, FromStr}; use bitflags::bitflags; use libc::{EINVAL, EIO, ENOSPC}; -use log::error; +use log::{error, warn}; use remain::sorted; use thiserror::Error; use vmm_sys_util::file_traits::{FileSetLen, FileSync}; @@ -46,6 +46,8 @@ pub enum Error { BackingFileOpen(#[source] Box), #[error("Backing file name is too long: {0} bytes over")] BackingFileTooLong(usize), + #[error("Image is marked corrupt and cannot be opened for writing")] + CorruptImage, #[error("Failed to evict cache")] EvictingCache(#[source] io::Error), #[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")] @@ -230,8 +232,9 @@ bitflags! { impl IncompatFeatures { /// Features supported by this implementation. - const SUPPORTED: IncompatFeatures = - IncompatFeatures::DIRTY.union(IncompatFeatures::COMPRESSION); + 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> { @@ -698,6 +701,24 @@ impl QcowHeader { } 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 F) -> Result<()> { + if self.version == 3 { + self.incompatible_features |= IncompatFeatures::CORRUPT.bits(); + self.write_incompatible_features(file)?; + file.fsync().map_err(Error::WritingHeader)?; + } + Ok(()) + } + + pub fn is_corrupt(&self) -> bool { + IncompatFeatures::from_bits_truncate(self.incompatible_features) + .contains(IncompatFeatures::CORRUPT) + } } fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> u64 { @@ -911,6 +932,15 @@ impl QcowFile { .ok_or(Error::InvalidClusterSize)?; let is_writable = raw_file.file().is_writable(); + if header.is_corrupt() { + if is_writable { + return Err(Error::CorruptImage); + } + let path = read_link(format!("/proc/self/fd/{}", raw_file.file().as_raw_fd())) + .map_or_else(|_| "".to_string(), |p| p.display().to_string()); + warn!("QCOW2 image is marked corrupt, opening read-only: {path}"); + } + // Image already has dirty bit set. Refcounts may be invalid. if IncompatFeatures::from_bits_truncate(header.incompatible_features) .contains(IncompatFeatures::DIRTY) From c2fcb9bac98ed854da32e4020c364d3e9d464614 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 26 Jan 2026 14:50:57 +0100 Subject: [PATCH 0490/1893] 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 --- block/src/qcow/mod.rs | 124 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 5 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index a2f81cec12..64ab151951 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -3842,7 +3842,7 @@ mod unit_tests { } #[test] - fn reject_unsupported_incompat_corrupt_bit() { + fn reject_corrupt_bit_for_writable_open() { // Bit 1: corrupt - image metadata is corrupted let header = header_v3_with_incompat_features(1 << 1); with_basic_file(&header, |disk_file: RawFile| { @@ -3850,8 +3850,8 @@ mod unit_tests { assert!(result.is_err()); let err = result.unwrap_err(); assert!( - matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("corrupt")), - "Expected UnsupportedFeature error mentioning corrupt, got: {err:?}" + matches!(err, Error::CorruptImage), + "Expected CorruptImage error, got: {err:?}" ); }); } @@ -3888,8 +3888,8 @@ mod unit_tests { #[test] fn reject_multiple_unsupported_incompat_bits() { - // Multiple unsupported bits: corrupt (1) + external data (2) - let header = header_v3_with_incompat_features((1 << 1) | (1 << 2)); + // Multiple unsupported bits: external data (2) + extended L2 (4) + let header = header_v3_with_incompat_features((1 << 2) | (1 << 4)); with_basic_file(&header, |disk_file: RawFile| { let result = QcowFile::from(disk_file); assert!(result.is_err()); @@ -4021,4 +4021,118 @@ mod unit_tests { "Dirty bit should not be written for read-only files" ); } + + #[test] + fn corrupt_image_rejected_for_write() { + // Test that a corrupt image cannot be opened for writing + let header = header_v3_with_incompat_features(IncompatFeatures::CORRUPT.bits()); + with_basic_file(&header, |disk_file: RawFile| { + assert!(disk_file.is_writable(), "File should be writable"); + + let result = QcowFile::from(disk_file); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + matches!(err, Error::CorruptImage), + "Expected CorruptImage error, got: {err:?}" + ); + }); + } + + #[test] + fn corrupt_image_allowed_readonly() { + // Test that a corrupt image can be opened read-only + let header = header_v3_with_incompat_features(IncompatFeatures::CORRUPT.bits()); + + // Create a temp file with the corrupt header + let temp_file = TempFile::new().unwrap(); + let temp_path = temp_file.as_path().to_owned(); + { + let mut file = temp_file.as_file().try_clone().unwrap(); + file.write_all(&header).unwrap(); + file.set_len(0x1_0000_0000).unwrap(); + } + + let readonly_file = OpenOptions::new() + .read(true) + .write(false) + .open(&temp_path) + .unwrap(); + let raw_file = RawFile::new(readonly_file, false); + assert!(!raw_file.is_writable(), "File should be read-only"); + + let result = QcowFile::from(raw_file); + assert!( + result.is_ok(), + "Corrupt image should be openable read-only, got: {:?}", + result.err() + ); + + let qcow = result.unwrap(); + assert!(qcow.header.is_corrupt(), "Corrupt bit should be set"); + } + + #[test] + fn set_corrupt_bit() { + // Test that set_corrupt_bit correctly sets the corrupt bit + let header = valid_header_v3(); + with_basic_file(&header, |mut disk_file: RawFile| { + let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + + assert!(!qcow.header.is_corrupt(), "Should not be corrupt initially"); + + qcow.header + .set_corrupt_bit(qcow.raw_file.file_mut()) + .unwrap(); + + // Verify in memory + assert!(qcow.header.is_corrupt(), "Should be corrupt after set"); + + // Verify on disk + disk_file + .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) + .unwrap(); + let features = u64::read_be(&mut disk_file).unwrap(); + assert!( + IncompatFeatures::from_bits_retain(features).contains(IncompatFeatures::CORRUPT), + "Corrupt bit should be set on disk" + ); + }); + } + + #[test] + fn corrupt_bit_persists_with_dirty() { + // Test that both corrupt and dirty bits can coexist + let header = header_v3_with_incompat_features( + IncompatFeatures::CORRUPT.bits() | IncompatFeatures::DIRTY.bits(), + ); + + let temp_file = TempFile::new().unwrap(); + let temp_path = temp_file.as_path().to_owned(); + { + let mut file = temp_file.as_file().try_clone().unwrap(); + file.write_all(&header).unwrap(); + file.set_len(0x1_0000_0000).unwrap(); + } + + // Writable would be rejected due to corrupt bit + let readonly_file = OpenOptions::new() + .read(true) + .write(false) + .open(&temp_path) + .unwrap(); + let raw_file = RawFile::new(readonly_file, false); + + let qcow = QcowFile::from(raw_file).unwrap(); + + let features = IncompatFeatures::from_bits_truncate(qcow.header.incompatible_features); + assert!( + features.contains(IncompatFeatures::CORRUPT), + "Corrupt bit should be set" + ); + assert!( + features.contains(IncompatFeatures::DIRTY), + "Dirty bit should also be set" + ); + } } From 2d86fc8422cac81c8dcff0bb4662fb5f52fc94a3 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 26 Jan 2026 15:00:18 +0100 Subject: [PATCH 0491/1893] 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 --- block/src/qcow/mod.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 64ab151951..d44b7b7eb6 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1529,6 +1529,19 @@ impl QcowFile { (address / self.raw_file.cluster_size()) % self.l2_entries } + /// Attempts to set the corrupt bit, logging failures without propagating them. + /// + /// This is "best effort" because the write may fail due to various reasons like + /// disk full, readonly storage, etc. This method is called just before returning + /// EIO to the caller. The error is not propagated because the original corruption + /// error is more important to return to the call site than a secondary I/O + /// failure from marking the image. + fn set_corrupt_bit_best_effort(&mut self) { + if let Err(e) = self.header.set_corrupt_bit(self.raw_file.file_mut()) { + warn!("Failed to persist corrupt bit: {e}"); + } + } + // Decompress the cluster, return EIO on failure fn decompress_l2_cluster(&mut self, l2_entry: u64) -> std::io::Result> { let (compressed_cluster_addr, compressed_cluster_size) = @@ -1547,8 +1560,12 @@ impl QcowFile { let mut decompressed_cluster = vec![0; cluster_size]; let decompressed_size = decoder .decode(&compressed_cluster, &mut decompressed_cluster) - .map_err(|_| std::io::Error::from_raw_os_error(EIO))?; + .map_err(|_| { + self.set_corrupt_bit_best_effort(); + io::Error::from_raw_os_error(EIO) + })?; if decompressed_size as u64 != self.raw_file.cluster_size() { + self.set_corrupt_bit_best_effort(); return Err(std::io::Error::from_raw_os_error(EIO)); } Ok(decompressed_cluster) @@ -1645,6 +1662,7 @@ impl QcowFile { .seek(SeekFrom::Start(cluster_addr))?; let nwritten = self.raw_file.file_mut().write(&decompressed_cluster)?; if nwritten != decompressed_cluster.len() { + self.set_corrupt_bit_best_effort(); return Err(std::io::Error::from_raw_os_error(EIO)); } @@ -1985,6 +2003,7 @@ impl QcowFile { return Err(e); } Err(refcount::Error::InvalidIndex) => { + self.set_corrupt_bit_best_effort(); return Err(std::io::Error::from_raw_os_error(EINVAL)); } Err(refcount::Error::NeedCluster(addr)) => { @@ -2027,6 +2046,7 @@ impl QcowFile { self.raw_file .write_pointer_table_direct(addr, l2_table.iter())?; } else { + self.set_corrupt_bit_best_effort(); return Err(std::io::Error::from_raw_os_error(EINVAL)); } l2_table.mark_clean(); From 9baa904a5c58e4dec182acf253797d8771173641 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 26 Jan 2026 15:08:51 +0100 Subject: [PATCH 0492/1893] 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 --- block/src/qcow/mod.rs | 41 +++++++++++++++++++++++++++++++++++--- block/src/qcow/refcount.rs | 6 ++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index d44b7b7eb6..2343354ca1 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1616,7 +1616,12 @@ impl QcowFile { // Cluster with zero flag reads as zeros without accessing disk. return Ok(None); } else { - let start = l2_entry_std_cluster_addr(l2_entry) + self.raw_file.cluster_offset(address); + let cluster_addr = l2_entry_std_cluster_addr(l2_entry); + if cluster_addr & (self.raw_file.cluster_size() - 1) != 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + let start = cluster_addr + self.raw_file.cluster_offset(address); let raw_file = self.raw_file.file_mut(); raw_file.seek(SeekFrom::Start(start))?; raw_file.read_exact(buf)?; @@ -1678,7 +1683,12 @@ impl QcowFile { let refcount = self .refcounts .get_cluster_refcount(&mut self.raw_file, addr) - .map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?; + .map_err(|e| { + if matches!(e, refcount::Error::RefblockUnaligned(_)) { + self.set_corrupt_bit_best_effort(); + } + io::Error::other(Error::GettingRefcount(e)) + })?; if refcount > 0 { self.set_cluster_refcount_track_freed(addr, refcount - 1)?; } @@ -1701,7 +1711,12 @@ impl QcowFile { self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?; cluster_addr } else { - l2_entry_std_cluster_addr(l2_entry) + let cluster_addr = l2_entry_std_cluster_addr(l2_entry); + if cluster_addr & (self.raw_file.cluster_size() - 1) != 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + cluster_addr }; for (addr, count) in set_refcounts { @@ -1748,6 +1763,10 @@ impl QcowFile { fn get_new_cluster(&mut self, initial_data: Option>) -> std::io::Result { // First use a pre allocated cluster if one is available. if let Some(free_cluster) = self.avail_clusters.pop() { + if free_cluster == 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } if let Some(initial_data) = initial_data { self.raw_file.write_cluster(free_cluster, &initial_data)?; } else { @@ -1758,6 +1777,10 @@ impl QcowFile { let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset(); if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? { + if new_cluster == 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } if let Some(initial_data) = initial_data { self.raw_file.write_cluster(new_cluster, &initial_data)?; } @@ -1868,6 +1891,9 @@ impl QcowFile { .refcounts .get_cluster_refcount(&mut self.raw_file, cluster_addr) .map_err(|e| { + if matches!(e, refcount::Error::RefblockUnaligned(_)) { + self.set_corrupt_bit_best_effort(); + } io::Error::new( io::ErrorKind::InvalidData, format!("failed to get cluster refcount: {e}"), @@ -1952,6 +1978,11 @@ impl QcowFile { self.l1_table[l1_index] = new_addr; VecCache::new(self.l2_entries as usize) } else { + let cluster_size = self.raw_file.cluster_size(); + if l2_addr_disk & (cluster_size - 1) != 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?) }; let l1_table = &self.l1_table; @@ -2028,6 +2059,10 @@ impl QcowFile { Err(refcount::Error::RefcountOverflow { .. }) => { return Err(std::io::Error::from_raw_os_error(EINVAL)); } + Err(refcount::Error::RefblockUnaligned(_)) => { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } } } diff --git a/block/src/qcow/refcount.rs b/block/src/qcow/refcount.rs index 8fa3d5bfed..5cd61c09b2 100644 --- a/block/src/qcow/refcount.rs +++ b/block/src/qcow/refcount.rs @@ -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), @@ -202,6 +205,9 @@ impl RefCount { 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 From edaeaed5f7e77663f43625e06a0511ea187a78cd Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 26 Jan 2026 16:39:06 +0100 Subject: [PATCH 0493/1893] 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 --- cloud-hypervisor/tests/integration.rs | 166 +++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 3 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 6a7acfaa6b..38b970c1a4 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -3549,17 +3549,48 @@ mod common_parallel { serde_json::from_slice(&output.stdout).ok() } - fn check_dirty_flag(path: &std::path::Path) -> Result, String> { + fn get_qcow2_v3_info(path: &Path) -> Result, String> { let info = get_image_info(path) .ok_or_else(|| format!("qemu-img info failed for {}", path.display()))?; if info["format"].as_str() != Some("qcow2") { return Ok(None); } - // QCOW2 v3 has compat "1.1", v2 has "0.10" and doesn't support dirty flag + // QCOW2 v3 has compat "1.1", v2 has "0.10" if info["format-specific"]["data"]["compat"].as_str() != Some("1.1") { return Ok(None); } - Ok(info["dirty-flag"].as_bool()) + Ok(Some(info)) + } + + fn check_dirty_flag(path: &Path) -> Result, String> { + Ok(get_qcow2_v3_info(path)?.and_then(|info| info["dirty-flag"].as_bool())) + } + + fn check_corrupt_flag(path: &Path) -> Result, String> { + Ok(get_qcow2_v3_info(path)? + .and_then(|info| info["format-specific"]["data"]["corrupt"].as_bool())) + } + + const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72; + + fn set_corrupt_flag(path: &Path, corrupt: bool) -> io::Result<()> { + let mut file = OpenOptions::new().read(true).write(true).open(path)?; + + file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; + let mut buf = [0u8; 8]; + file.read_exact(&mut buf)?; + let mut features = u64::from_be_bytes(buf); + + if corrupt { + features |= 0x02; + } else { + features &= !0x02; + } + + file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; + file.write_all(&features.to_be_bytes())?; + file.sync_all()?; + Ok(()) } fn resolve_disk_path(path_or_image_name: impl AsRef) -> std::path::PathBuf { @@ -3884,6 +3915,135 @@ mod common_parallel { disk_check_consistency(&test_image_path, None); } + #[test] + fn test_virtio_block_qcow2_corrupt_bit_rejected_for_write() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_image_path = guest.tmp_dir.as_path().join("test-corrupt.qcow2"); + let original_image = guest.disk_config.disk(DiskType::OperatingSystem).unwrap(); + + copy(original_image, &test_image_path).expect("Failed to copy qcow2 image"); + + assert_eq!( + check_corrupt_flag(&test_image_path).expect("Failed to check corrupt flag"), + Some(false), + "Image should start with corrupt bit cleared" + ); + + set_corrupt_flag(&test_image_path, true).expect("Failed to set corrupt flag"); + + assert_eq!( + check_corrupt_flag(&test_image_path).expect("Failed to check corrupt flag"), + Some(true), + "Corrupt bit should be set" + ); + + let child = GuestCommand::new(&guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + &format!("path={}", test_image_path.to_str().unwrap()), + &format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let output = child.wait_with_output().unwrap(); + assert!( + !output.status.success(), + "VM should fail to start with corrupt disk image" + ); + + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("corrupt") || stderr.contains("Corrupt"), + "Error message should mention corruption: {stderr}" + ); + } + + #[test] + fn test_virtio_block_qcow2_corrupt_bit_allowed_readonly() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_image_path = guest.tmp_dir.as_path().join("test-corrupt-ro.qcow2"); + let original_image = guest.disk_config.disk(DiskType::OperatingSystem).unwrap(); + + copy(original_image, &test_image_path).expect("Failed to copy qcow2 image"); + + set_corrupt_flag(&test_image_path, true).expect("Failed to set corrupt flag"); + + assert_eq!( + check_corrupt_flag(&test_image_path).expect("Failed to check corrupt flag"), + Some(true), + "Corrupt bit should be set" + ); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + &format!("path={},readonly=on", test_image_path.to_str().unwrap()), + &format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + thread::sleep(Duration::from_secs(5)); + + match child.try_wait() { + Ok(Some(status)) => { + let output = child.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + panic!( + "VM should not have exited when opening corrupt image as readonly. Exit status: {}, stderr: {}", + status, stderr + ); + } + Ok(None) => { + // VM is still running as expected + } + Err(e) => { + panic!("Error checking process status: {}", e); + } + } + + let _ = unsafe { libc::kill(child.id() as i32, libc::SIGKILL) }; + let output = child.wait_with_output().unwrap(); + + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("QCOW2 image is marked corrupt, opening read-only"), + "Expected warning about corrupt image being opened read-only. stderr: {}", + stderr + ); + + assert_eq!( + check_corrupt_flag(&test_image_path).expect("Failed to check corrupt flag"), + Some(true), + "Corrupt bit should remain set for read-only access" + ); + } + #[test] fn test_virtio_block_vhd() { let mut workload_path = dirs::home_dir().unwrap(); From 323b8230a3092dd68fa2fe0dfd22efa75f65160e Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Sat, 23 Aug 2025 10:17:52 +0000 Subject: [PATCH 0494/1893] 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 --- cloud-hypervisor/tests/integration.rs | 38 ++++++++++++++++----------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 38b970c1a4..27a7f2c764 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1216,6 +1216,21 @@ fn _test_power_button(acpi: bool) { handle_child_output(r, &output); } +fn get_msi_interrupt_pattern() -> String { + #[cfg(target_arch = "x86_64")] + { + "PCI-MSI".to_string() + } + #[cfg(target_arch = "aarch64")] + { + if cfg!(feature = "mshv") { + "GICv2m-PCI-MSIX".to_string() + } else { + "ITS-PCI-MSIX".to_string() + } + } +} + type PrepareNetDaemon = dyn Fn( &TempDir, &str, @@ -1349,13 +1364,11 @@ fn test_vhost_user_net( // Since virtio-net has 2 queue pairs, its vectors is as follows: // 1 virtio-net with 5 vectors: config, Rx (2), Tx (2) // Based on the above, the total vectors should 14. - #[cfg(target_arch = "x86_64")] - let grep_cmd = "grep -c PCI-MSI /proc/interrupts"; - #[cfg(target_arch = "aarch64")] - let grep_cmd = "grep -c ITS-PCI-MSIX /proc/interrupts"; + let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); + assert_eq!( guest - .ssh_command(grep_cmd) + .ssh_command(&grep_cmd) .unwrap() .trim() .parse::() @@ -3064,15 +3077,12 @@ mod common_parallel { guest.wait_vm_boot(None).unwrap(); - #[cfg(target_arch = "x86_64")] - let grep_cmd = "grep -c PCI-MSI /proc/interrupts"; - #[cfg(target_arch = "aarch64")] - let grep_cmd = "grep -c ITS-PCI-MSIX /proc/interrupts"; + let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); let r = std::panic::catch_unwind(|| { assert_eq!( guest - .ssh_command(grep_cmd) + .ssh_command(&grep_cmd) .unwrap() .trim() .parse::() @@ -3341,14 +3351,10 @@ mod common_parallel { assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); - let grep_cmd = if cfg!(target_arch = "x86_64") { - "grep -c PCI-MSI /proc/interrupts" - } else { - "grep -c ITS-PCI-MSIX /proc/interrupts" - }; + let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); assert_eq!( guest - .ssh_command(grep_cmd) + .ssh_command(&grep_cmd) .unwrap() .trim() .parse::() From 2da05d42587c67352dbf1a1c22c0191424799d1d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 28 Jan 2026 19:27:25 +0100 Subject: [PATCH 0495/1893] 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 --- block/src/qcow/mod.rs | 203 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 2343354ca1..efd4c5b701 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -4190,4 +4190,207 @@ mod unit_tests { "Dirty bit should also be set" ); } + + /// Helper to check if corrupt bit is set on disk by re-reading the header + fn is_corrupt_on_disk(disk_file: &mut RawFile) -> bool { + disk_file.rewind().unwrap(); + QcowHeader::new(disk_file).unwrap().is_corrupt() + } + + /// Helper to clear the corrupt bit on disk while preserving other bits + fn clear_corrupt_bit_on_disk(disk_file: &mut RawFile) { + disk_file + .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) + .unwrap(); + let features = u64::read_be(disk_file).unwrap(); + let mut flags = IncompatFeatures::from_bits_retain(features); + flags.remove(IncompatFeatures::CORRUPT); + disk_file + .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) + .unwrap(); + u64::write_be(disk_file, flags.bits()).unwrap(); + assert!( + !is_corrupt_on_disk(disk_file), + "Corrupt bit should be cleared" + ); + } + + /// Helper to corrupt L1 entry by making L2 table address unaligned. + /// + /// Returns true if corruption was applied, i.e. the L1 entry was allocated. + fn corrupt_l1_entry(disk_file: &mut RawFile) -> bool { + let l1_table_offset = 0x0004_0000u64; + disk_file.seek(SeekFrom::Start(l1_table_offset)).unwrap(); + let l1_entry = u64::read_be(disk_file).unwrap(); + if l1_entry != 0 { + let unaligned = l1_entry | 0x200; // Make unaligned + disk_file.seek(SeekFrom::Start(l1_table_offset)).unwrap(); + u64::write_be(disk_file, unaligned).unwrap(); + disk_file.sync_all().unwrap(); + true + } else { + false + } + } + + /// Helper to corrupt L2 entry by making cluster address unaligned. + /// + /// Returns true if corruption was applied, i.e. an allocated non-compressed + /// L2 entry was found. + fn corrupt_l2_entry(disk_file: &mut RawFile) -> bool { + let l1_table_offset = 0x0004_0000u64; + disk_file.seek(SeekFrom::Start(l1_table_offset)).unwrap(); + let l1_entry = u64::read_be(disk_file).unwrap(); + if l1_entry == 0 { + return false; + } + let l2_table_addr = l1_entry & L1_TABLE_OFFSET_MASK; + disk_file.seek(SeekFrom::Start(l2_table_addr)).unwrap(); + let l2_entry = u64::read_be(disk_file).unwrap(); + if l2_entry != 0 && !l2_entry_is_compressed(l2_entry) { + let unaligned = l2_entry | 0x200; + disk_file.seek(SeekFrom::Start(l2_table_addr)).unwrap(); + u64::write_be(disk_file, unaligned).unwrap(); + disk_file.sync_all().unwrap(); + true + } else { + false + } + } + + /// Asserts that read on corrupted disk sets the corrupt bit + fn assert_corruption_on_read(disk_file: &mut RawFile) { + clear_corrupt_bit_on_disk(disk_file); + + disk_file.rewind().unwrap(); + let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + let mut buf = [0u8; 16]; + let result = qcow.read(&mut buf); + + assert_eq!( + result.map_err(|e| e.raw_os_error()), + Err(Some(libc::EIO)), + "read should fail with EIO on corrupted image" + ); + assert!( + is_corrupt_on_disk(disk_file), + "Corrupt bit should be set after read" + ); + } + + /// Asserts that write on corrupted disk sets the corrupt bit + fn assert_corruption_on_write(disk_file: &mut RawFile) { + clear_corrupt_bit_on_disk(disk_file); + + disk_file.rewind().unwrap(); + let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + let result = qcow.write_all(b"overwrite"); + + assert_eq!( + result.map_err(|e| e.raw_os_error()), + Err(Some(libc::EIO)), + "write should fail with EIO on corrupted image" + ); + assert!( + is_corrupt_on_disk(disk_file), + "Corrupt bit should be set after write" + ); + } + + #[test] + fn corrupt_bit_on_unaligned_l2_address() { + let header = valid_header_v3(); + with_basic_file(&header, |mut disk_file: RawFile| { + { + let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + qcow.write_all(b"test data").unwrap(); + } + + assert!( + corrupt_l1_entry(&mut disk_file), + "Failed to corrupt L1 entry - was data written?" + ); + assert_corruption_on_read(&mut disk_file); + }); + } + + #[test] + fn corrupt_bit_on_unaligned_cluster_address_read() { + let header = valid_header_v3(); + with_basic_file(&header, |mut disk_file: RawFile| { + { + let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + qcow.write_all(b"test data to allocate cluster").unwrap(); + } + + assert!( + corrupt_l2_entry(&mut disk_file), + "Failed to corrupt L2 entry - was cluster allocated?" + ); + assert_corruption_on_read(&mut disk_file); + }); + } + + #[test] + fn corrupt_bit_on_unaligned_cluster_address_write() { + let header = valid_header_v3(); + with_basic_file(&header, |mut disk_file: RawFile| { + { + let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + qcow.write_all(b"test data to allocate cluster").unwrap(); + } + + assert!( + corrupt_l2_entry(&mut disk_file), + "Failed to corrupt L2 entry - was cluster allocated?" + ); + assert_corruption_on_write(&mut disk_file); + }); + } + + #[test] + fn corrupt_bit_not_set_on_normal_operations() { + let header = valid_header_v3(); + with_basic_file(&header, |mut disk_file: RawFile| { + { + let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + + qcow.write_all(b"test data 1234567890").unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + + let mut buf = [0u8; 20]; + qcow.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"test data 1234567890"); + + qcow.seek(SeekFrom::Start(0x10000)).unwrap(); + qcow.write_all(b"more data").unwrap(); + + qcow.flush().unwrap(); + } + + assert!( + !is_corrupt_on_disk(&mut disk_file), + "Corrupt bit should NOT be set after normal operations" + ); + }); + } + + #[test] + fn corrupt_bit_v2_image_not_affected() { + let header = valid_header_v2(); + with_basic_file(&header, |mut disk_file: RawFile| { + { + let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + qcow.write_all(b"test data").unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + let mut buf = [0u8; 9]; + qcow.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"test data"); + } + + disk_file.rewind().unwrap(); + let qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + assert_eq!(qcow.header.version, 2); + }); + } } From 28686bba46b4d62a9a220cc66e667b32bac5a957 Mon Sep 17 00:00:00 2001 From: Zhibin Li Date: Thu, 29 Jan 2026 18:06:08 +0800 Subject: [PATCH 0496/1893] 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 --- vmm/src/vm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index d6f6b93e70..4ed3a1f3df 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2516,7 +2516,7 @@ impl Vm { #[cfg(not(target_arch = "riscv64"))] { - #[cfg(not(feature = "sev_snp"))] + #[cfg(not(any(feature = "sev_snp", feature = "tdx")))] assert!(rsdp_addr.is_some()); // Configure shared state based on loaded kernel if let Some(rsdp_adr) = rsdp_addr { From 01bd6b8b1a5ef0720512f3e23b01309266175dac Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 2 Feb 2026 01:40:01 -0800 Subject: [PATCH 0497/1893] 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 Reported-by: Daniel Farina --- virtio-devices/src/vhost_user/net.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index e25e8ef112..e0a71c7342 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -28,11 +28,10 @@ use vmm_sys_util::eventfd::EventFd; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::vu_common_ctrl::{VhostUserConfig, VhostUserHandle}; -use crate::vhost_user::{Error, Result, VhostUserCommon}; +use crate::vhost_user::{DEFAULT_VIRTIO_FEATURES, Error, Result, VhostUserCommon}; use crate::{ ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, VIRTIO_F_IOMMU_PLATFORM, - VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, - VirtioInterrupt, + VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, }; const DEFAULT_QUEUE_NUMBER: usize = 2; @@ -122,9 +121,7 @@ impl Net { // Filling device and vring features VMM supports. let mut avail_features = (1 << VIRTIO_NET_F_MRG_RXBUF) | (1 << VIRTIO_NET_F_CTRL_VQ) - | (1 << VIRTIO_F_RING_EVENT_IDX) - | (1 << VIRTIO_F_VERSION_1) - | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); + | DEFAULT_VIRTIO_FEATURES; if mtu.is_some() { avail_features |= 1u64 << VIRTIO_NET_F_MTU; From 009af915bb408f20f5d3dc60fd26494f9ee174c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 04:44:36 +0000 Subject: [PATCH 0498/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 2eff41f4c6..bb5fc10bfd 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.42.3 + - uses: crate-ci/typos@v1.43.0 From 57fd672db6cb82c40b527c45249256246b6f41a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 04:56:52 +0000 Subject: [PATCH 0499/1893] 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] --- Cargo.lock | 247 +++++++++++++++++++++++++++++++++++++----- Cargo.toml | 4 +- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 32 +++--- hypervisor/Cargo.toml | 2 +- net_util/Cargo.toml | 2 +- 6 files changed, 242 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 508ec7a3ef..cbca77a69c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" dependencies = [ "rustversion", ] @@ -369,9 +369,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.54" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -387,18 +387,18 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" dependencies = [ "anstream", "anstyle", @@ -731,9 +731,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" @@ -763,6 +763,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "futures" version = "0.3.31" @@ -914,6 +920,19 @@ dependencies = [ "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd66c162c2a0609c0507f49814877c621d00cb85978e6fbbf04e88b8048c07c8" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "gimli" version = "0.32.3" @@ -926,12 +945,27 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.5.2" @@ -984,6 +1018,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -1025,7 +1065,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1153,6 +1195,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.180" @@ -1309,7 +1357,7 @@ name = "net_util" version = "0.1.0" dependencies = [ "epoll", - "getrandom 0.3.4", + "getrandom 0.4.0", "libc", "log", "net_gen", @@ -1406,9 +1454,9 @@ dependencies = [ [[package]] name = "openssl-src" -version = "300.5.4+3.5.4" +version = "300.5.5+3.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" dependencies = [ "cc", ] @@ -1653,15 +1701,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -1675,6 +1723,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -1860,6 +1918,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -1974,9 +2038,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -2211,6 +2275,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2550,6 +2620,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2608,6 +2687,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2733,6 +2846,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "zbus" @@ -2797,18 +2992,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.34" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.34" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", @@ -2817,9 +3012,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 91c4ea92d6..750506096d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ anyhow = "1.0.99" bitflags = "2.10.0" byteorder = "1.5.0" cfg-if = "1.0.4" -clap = "4.5.54" +clap = "4.5.56" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" @@ -91,7 +91,7 @@ signal-hook = "0.4.3" thiserror = "2.0.18" uuid = { version = "1.20.0" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.34", default-features = false } +zerocopy = { version = "0.8.37", default-features = false } [workspace.lints.clippy] # Any clippy lint (group) in alphabetical order: diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 2dd56b8f77..ab1a70cd86 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.34", features = [ +zerocopy = { version = "0.8.37", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 4363adfd8b..b757f7dfe3 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -80,9 +80,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" dependencies = [ "rustversion", ] @@ -172,9 +172,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.54" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -190,18 +190,18 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" dependencies = [ "anstream", "anstyle", @@ -421,9 +421,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" @@ -1582,18 +1582,18 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zerocopy" -version = "0.8.34" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.34" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", @@ -1602,9 +1602,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" [[package]] name = "zstd" diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 308406443e..f7336aadc7 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -14,7 +14,7 @@ tdx = [] [dependencies] anyhow = { workspace = true } -arc-swap = "1.8.0" +arc-swap = "1.8.1" bitfield-struct = "0.12.0" byteorder = { workspace = true } cfg-if = { workspace = true } diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml index 9d800419b0..780389e4d2 100644 --- a/net_util/Cargo.toml +++ b/net_util/Cargo.toml @@ -6,7 +6,7 @@ version = "0.1.0" [dependencies] epoll = { workspace = true } -getrandom = "0.3.4" +getrandom = "0.4.0" libc = { workspace = true } log = { workspace = true } net_gen = { path = "../net_gen" } From 4545fe113eecf07ed60ba65fb9d950e12227bfe1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 28 Jan 2026 20:13:20 +0100 Subject: [PATCH 0500/1893] 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 --- block/src/qcow/mod.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index efd4c5b701..6d5a9c2d4f 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -194,6 +194,7 @@ const DEFAULT_REFCOUNT_ORDER: u32 = 4; const V2_BARE_HEADER_SIZE: u32 = 72; const V3_BARE_HEADER_SIZE: u32 = 104; +const AUTOCLEAR_FEATURES_OFFSET: u64 = 88; // bits 0-8 and 56-63 are reserved. const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; @@ -719,6 +720,24 @@ impl QcowHeader { 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 F, + ) -> Result<()> { + if self.version == 3 && self.autoclear_features != 0 { + self.autoclear_features = 0; + file.seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET)) + .map_err(Error::WritingHeader)?; + u64::write_be(file, 0).map_err(Error::WritingHeader)?; + file.fsync().map_err(Error::WritingHeader)?; + } + Ok(()) + } } fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> u64 { @@ -1023,11 +1042,15 @@ impl QcowFile { qcow.find_avail_clusters()?; - if !IncompatFeatures::from_bits_truncate(qcow.header.incompatible_features) - .contains(IncompatFeatures::DIRTY) - && is_writable - { - qcow.header.set_dirty_bit(qcow.raw_file.file_mut(), true)?; + if is_writable { + if !IncompatFeatures::from_bits_truncate(qcow.header.incompatible_features) + .contains(IncompatFeatures::DIRTY) + { + qcow.header.set_dirty_bit(qcow.raw_file.file_mut(), true)?; + } + + qcow.header + .clear_autoclear_features(qcow.raw_file.file_mut())?; } Ok(qcow) From 4ba0db5948f3d43a49358d96884998ea0009e358 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 28 Jan 2026 20:13:34 +0100 Subject: [PATCH 0501/1893] 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 --- block/src/qcow/mod.rs | 81 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 6d5a9c2d4f..5300b56e2b 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -3907,6 +3907,14 @@ mod unit_tests { header } + // Helper to create a v3 header with specific autoclear feature bits set + fn header_v3_with_autoclear_features(features: u64) -> Vec { + let mut header = valid_header_v3(); + let offset = AUTOCLEAR_FEATURES_OFFSET as usize; + header[offset..offset + 8].copy_from_slice(&features.to_be_bytes()); + header + } + #[test] fn accept_incompat_dirty_bit() { let header = header_v3_with_incompat_features(1 << 0); @@ -4100,6 +4108,79 @@ mod unit_tests { ); } + #[test] + fn autoclear_features_cleared_on_open() { + let header = header_v3_with_autoclear_features(0xFFFF_FFFF_FFFF_FFFF); + with_basic_file(&header, |mut disk_file: RawFile| { + disk_file + .seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET)) + .unwrap(); + let features_before = u64::read_be(&mut disk_file).unwrap(); + assert_eq!( + features_before, 0xFFFF_FFFF_FFFF_FFFF, + "Autoclear features should be set initially" + ); + + disk_file.rewind().unwrap(); + { + let _qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap(); + } + + disk_file + .seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET)) + .unwrap(); + let features_after = u64::read_be(&mut disk_file).unwrap(); + assert_eq!( + features_after, 0, + "Autoclear features should be cleared after open for write" + ); + }); + } + + #[test] + fn autoclear_features_not_cleared_for_readonly() { + let header = header_v3_with_autoclear_features(0xFFFF_FFFF_FFFF_FFFF); + + let temp_file = TempFile::new().unwrap(); + let temp_path = temp_file.as_path().to_owned(); + { + let mut file = temp_file.as_file().try_clone().unwrap(); + file.write_all(&header).unwrap(); + file.set_len(0x1_0000_0000).unwrap(); + } + + let readonly_file = OpenOptions::new() + .read(true) + .write(false) + .open(&temp_path) + .unwrap(); + let raw_file = RawFile::new(readonly_file, false); + let _qcow = QcowFile::from(raw_file).unwrap(); + drop(_qcow); + + let verify_file = OpenOptions::new().read(true).open(&temp_path).unwrap(); + let mut verify_raw = RawFile::new(verify_file, false); + verify_raw + .seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET)) + .unwrap(); + let features = u64::read_be(&mut verify_raw).unwrap(); + assert_eq!( + features, 0xFFFF_FFFF_FFFF_FFFF, + "Autoclear features should NOT be cleared for read-only files" + ); + } + + #[test] + fn autoclear_features_v2_ignored() { + let header = valid_header_v2(); + with_basic_file(&header, |mut disk_file: RawFile| { + disk_file.rewind().unwrap(); + let qcow = QcowFile::from(disk_file).unwrap(); + assert_eq!(qcow.header.version, 2); + assert_eq!(qcow.header.autoclear_features, 0); + }); + } + #[test] fn corrupt_image_rejected_for_write() { // Test that a corrupt image cannot be opened for writing From 512c3ea58dc61f5ba01985e0ddfbb0c1e5493c71 Mon Sep 17 00:00:00 2001 From: stevenhorsman Date: Tue, 3 Feb 2026 09:46:31 +0000 Subject: [PATCH 0502/1893] 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 --- api_client/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/api_client/Cargo.toml b/api_client/Cargo.toml index b8791dfc3d..93a7836fcc 100644 --- a/api_client/Cargo.toml +++ b/api_client/Cargo.toml @@ -1,6 +1,7 @@ [package] authors = ["The Cloud Hypervisor Authors"] edition.workspace = true +license = "Apache-2.0" name = "api_client" version = "0.1.0" From 591aeb8ed83e7d1997c4c715c07b9b4f6ae608c7 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 3 Feb 2026 16:13:46 +0100 Subject: [PATCH 0503/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- .typos.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.typos.toml b/.typos.toml index b59130fe70..2874cc417f 100644 --- a/.typos.toml +++ b/.typos.toml @@ -21,5 +21,8 @@ liness = "liness" outout = "outout" [default.extend-identifiers] +consts = "consts" fo = "fo" fpr = "fpr" +# Public Linux API +msg_controllen = "msg_controllen" From e690d258cc8432fb6bd55a37f4cbc0dd2487f353 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 3 Feb 2026 16:58:20 +0100 Subject: [PATCH 0504/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- .github/workflows/build.yaml | 2 +- .github/workflows/docker-image.yaml | 2 +- .github/workflows/formatting.yaml | 2 +- .github/workflows/fuzz-build.yaml | 2 +- .github/workflows/integration-arm64.yaml | 2 +- .github/workflows/integration-rate-limiter.yaml | 2 +- .github/workflows/integration-vfio.yaml | 2 +- .github/workflows/integration-windows.yaml | 2 +- .github/workflows/integration-x86-64.yaml | 2 +- .github/workflows/mshv-infra.yaml | 4 ++-- .github/workflows/package-consistency.yaml | 2 +- .github/workflows/preview-riscv64-build.yaml | 2 +- .github/workflows/preview-riscv64-modules.yaml | 2 +- .github/workflows/quality.yaml | 2 +- .github/workflows/release.yaml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8528d54299..e3b1a9e7f7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Build on: [pull_request, merge_group] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index da2bbf5d3a..6a52f1edf0 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -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: diff --git a/.github/workflows/formatting.yaml b/.github/workflows/formatting.yaml index 00cd322797..8dee21e6b2 100644 --- a/.github/workflows/formatting.yaml +++ b/.github/workflows/formatting.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Code Formatting on: [pull_request, merge_group] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/fuzz-build.yaml b/.github/workflows/fuzz-build.yaml index b97796893d..d5f0332a9c 100644 --- a/.github/workflows/fuzz-build.yaml +++ b/.github/workflows/fuzz-build.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Cargo Fuzz Build on: [pull_request, merge_group] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/integration-arm64.yaml b/.github/workflows/integration-arm64.yaml index f48c0ec126..0678dbc839 100644 --- a/.github/workflows/integration-arm64.yaml +++ b/.github/workflows/integration-arm64.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Tests (ARM64) on: [pull_request, merge_group] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/integration-rate-limiter.yaml b/.github/workflows/integration-rate-limiter.yaml index 94497f47bd..b76c35c739 100644 --- a/.github/workflows/integration-rate-limiter.yaml +++ b/.github/workflows/integration-rate-limiter.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Tests (Rate-Limiter) on: [merge_group, pull_request] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/integration-vfio.yaml b/.github/workflows/integration-vfio.yaml index 416e99ee95..218e897270 100644 --- a/.github/workflows/integration-vfio.yaml +++ b/.github/workflows/integration-vfio.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Tests (VFIO) on: [merge_group, pull_request] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/integration-windows.yaml b/.github/workflows/integration-windows.yaml index 81ed017550..51877aa476 100644 --- a/.github/workflows/integration-windows.yaml +++ b/.github/workflows/integration-windows.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Tests (Windows Guest) on: [merge_group, pull_request] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/integration-x86-64.yaml b/.github/workflows/integration-x86-64.yaml index 453d7c2d32..9334b242ea 100644 --- a/.github/workflows/integration-x86-64.yaml +++ b/.github/workflows/integration-x86-64.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Tests (x86-64) on: [pull_request, merge_group] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index e8d25fc12e..ad71f69041 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -1,5 +1,5 @@ name: MSHV Infra Setup -on: +on: workflow_call: inputs: ARCH: @@ -44,7 +44,7 @@ on: description: 'Private IP of the VM' value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }} concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: infra-setup: diff --git a/.github/workflows/package-consistency.yaml b/.github/workflows/package-consistency.yaml index 9c5eb5c0e4..df7f01b8af 100644 --- a/.github/workflows/package-consistency.yaml +++ b/.github/workflows/package-consistency.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Consistency on: [pull_request, merge_group] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/preview-riscv64-build.yaml b/.github/workflows/preview-riscv64-build.yaml index e5fbf5ee27..ad87232d3d 100644 --- a/.github/workflows/preview-riscv64-build.yaml +++ b/.github/workflows/preview-riscv64-build.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor RISC-V 64-bit kvm build Preview on: [pull_request, merge_group] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/preview-riscv64-modules.yaml b/.github/workflows/preview-riscv64-modules.yaml index 1ae1c63758..1b7ac6ed16 100644 --- a/.github/workflows/preview-riscv64-modules.yaml +++ b/.github/workflows/preview-riscv64-modules.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor RISC-V 64-bit Preview on: [pull_request, merge_group] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index bb5fc10bfd..7a7e8bed0d 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -1,7 +1,7 @@ name: Cloud Hypervisor Quality Checks on: [pull_request, merge_group] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} cancel-in-progress: true jobs: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 18317aeae5..6a96de491b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -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 }} From 51662159c048ad50a81b9ed7ed21720d0db5a41e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 3 Feb 2026 16:10:34 +0100 Subject: [PATCH 0505/1893] 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 --- cloud-hypervisor/tests/integration.rs | 418 ++++++++++++++++++++++++++ 1 file changed, 418 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 27a7f2c764..6c580bac0b 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2543,6 +2543,7 @@ mod common_parallel { use std::cmp; use std::fs::{File, OpenOptions, copy}; use std::io::{self, SeekFrom}; + use std::process::Command; use crate::*; @@ -3799,6 +3800,423 @@ mod common_parallel { _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE, false, false, true); } + /// Configuration for QCOW2 multiqueue test image setup + enum QcowTestImageConfig { + /// Simple QCOW2 image with given size (e.g., "256M") + Simple(&'static str), + /// QCOW2 overlay with backing file + WithBacking, + } + + /// Helper to run QCOW2 multiqueue stress tests with shared setup/teardown. + /// + /// Creates a VM with multiple virtio queues on the test disk, then runs the + /// provided test closure. Handles VM lifecycle and consistency checks. + fn run_multiqueue_qcow2_test(image_config: QcowTestImageConfig, test_fn: F) + where + F: FnOnce(&Guest) + std::panic::UnwindSafe, + { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_image_path = guest.tmp_dir.as_path().join("test.qcow2"); + + // Create test image based on configuration and capture backing checksum if applicable + let initial_backing_checksum = match image_config { + QcowTestImageConfig::Simple(size) => { + Command::new("qemu-img") + .arg("create") + .args(["-f", "qcow2"]) + .arg(test_image_path.to_str().unwrap()) + .arg(size) + .output() + .expect("Failed to create QCOW2 test image"); + None + } + QcowTestImageConfig::WithBacking => { + let backing_path = guest.tmp_dir.as_path().join("backing.qcow2"); + Command::new("qemu-img") + .arg("create") + .args(["-f", "qcow2"]) + .arg(backing_path.to_str().unwrap()) + .arg("256M") + .output() + .expect("Failed to create backing QCOW2"); + + Command::new("qemu-img") + .arg("create") + .args(["-f", "qcow2"]) + .args(["-b", backing_path.to_str().unwrap()]) + .args(["-F", "qcow2"]) + .arg(test_image_path.to_str().unwrap()) + .output() + .expect("Failed to create overlay QCOW2"); + + compute_backing_checksum(&test_image_path) + } + }; + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=8"]) + .args(["--memory", "size=1024M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + &format!( + "path={},num_queues=8", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ), + &format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ), + &format!("path={},num_queues=8", test_image_path.to_str().unwrap()), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot(None).unwrap(); + test_fn(&guest); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + + disk_check_consistency( + guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), + None, + ); + disk_check_consistency(&test_image_path, initial_backing_checksum); + } + + #[test] + fn test_virtio_block_qcow2_multiqueue_writes() { + run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + assert_eq!( + guest + .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 8, + "Expected 8 queues on vdc" + ); + + guest + .ssh_command("sudo mkfs.ext4 -F /dev/vdc") + .expect("Failed to format disk"); + guest + .ssh_command("sudo mkdir -p /mnt/test && sudo mount /dev/vdc /mnt/test") + .expect("Failed to mount disk"); + + guest + .ssh_command( + "for i in $(seq 1 8); do \ + sudo dd if=/dev/urandom of=/mnt/test/file$i bs=1M count=32 conv=fsync & \ + done; wait", + ) + .expect("Failed to write files in parallel"); + + assert_eq!( + guest + .ssh_command("ls /mnt/test/file* | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 8, + "Expected 8 files to be created" + ); + + guest + .ssh_command("sudo rm -f /mnt/test/file*") + .expect("Failed to remove files"); + + // Do another round of heavy parallel I/O + guest + .ssh_command( + "for i in $(seq 1 16); do \ + sudo dd if=/dev/urandom of=/mnt/test/file$i bs=1M count=16 conv=fsync & \ + done; wait", + ) + .expect("Failed to write files in second round"); + + assert_eq!( + guest + .ssh_command("ls /mnt/test/file* | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 16, + "Expected 16 files after second round" + ); + + guest + .ssh_command("sudo umount /mnt/test") + .expect("Failed to unmount"); + }); + } + + #[test] + fn test_virtio_block_qcow2_multiqueue_mixed_rw() { + run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("512M"), |guest| { + guest + .ssh_command("sudo mkfs.ext4 -F /dev/vdc") + .expect("Failed to format disk"); + guest + .ssh_command("sudo mkdir -p /mnt/test && sudo mount /dev/vdc /mnt/test") + .expect("Failed to mount disk"); + + guest + .ssh_command( + "sudo dd if=/dev/urandom of=/mnt/test/readfile bs=1M count=64 conv=fsync", + ) + .expect("Failed to create initial file"); + + guest + .ssh_command( + "for i in $(seq 1 4); do \ + sudo dd if=/mnt/test/readfile of=/dev/null bs=64K & \ + sudo dd if=/dev/urandom of=/mnt/test/writefile$i bs=1M count=32 conv=fsync & \ + done; wait", + ) + .expect("Failed mixed read/write workload"); + + assert_eq!( + guest + .ssh_command("ls /mnt/test/writefile* | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4, + "Expected 4 write files" + ); + + guest + .ssh_command( + "for i in $(seq 1 4); do \ + sudo dd if=/mnt/test/writefile$i of=/dev/null bs=64K & \ + sudo dd if=/dev/urandom of=/mnt/test/newfile$i bs=1M count=16 conv=fsync & \ + done; wait", + ) + .expect("Failed second mixed workload"); + + guest + .ssh_command("sudo umount /mnt/test") + .expect("Failed to unmount"); + }); + } + + #[test] + fn test_virtio_block_qcow2_multiqueue_backing() { + run_multiqueue_qcow2_test(QcowTestImageConfig::WithBacking, |guest| { + guest + .ssh_command("sudo mkfs.ext4 -F /dev/vdc") + .expect("Failed to format disk"); + guest + .ssh_command("sudo mkdir -p /mnt/test && sudo mount /dev/vdc /mnt/test") + .expect("Failed to mount disk"); + + guest + .ssh_command( + "for i in $(seq 1 8); do \ + sudo dd if=/dev/urandom of=/mnt/test/file$i bs=1M count=16 conv=fsync & \ + done; wait", + ) + .expect("Failed to write files"); + + guest + .ssh_command( + "for i in $(seq 1 8); do \ + sudo dd if=/mnt/test/file$i of=/dev/null bs=64K & \ + sudo dd if=/dev/urandom of=/mnt/test/new$i bs=1M count=8 conv=fsync & \ + done; wait", + ) + .expect("Failed mixed backing/overlay workload"); + + assert_eq!( + guest + .ssh_command("ls /mnt/test/new* | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 8, + "Expected 8 new files" + ); + + guest + .ssh_command("sudo umount /mnt/test") + .expect("Failed to unmount"); + }); + } + + #[test] + fn test_virtio_block_qcow2_multiqueue_random_4k() { + run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + guest + .ssh_command( + "for i in $(seq 1 8); do \ + sudo dd if=/dev/urandom of=/dev/vdc bs=4K count=1000 seek=$((RANDOM % 60000)) conv=notrunc & \ + done; wait", + ) + .expect("Failed random 4K writes round 1"); + + guest + .ssh_command( + "for i in $(seq 1 8); do \ + sudo dd if=/dev/urandom of=/dev/vdc bs=4K count=1000 seek=$((RANDOM % 60000)) conv=notrunc & \ + done; wait", + ) + .expect("Failed random 4K writes round 2"); + + guest + .ssh_command( + "for i in $(seq 1 4); do \ + sudo dd if=/dev/vdc of=/dev/null bs=4K count=500 skip=$((RANDOM % 60000)) & \ + sudo dd if=/dev/urandom of=/dev/vdc bs=4K count=500 seek=$((RANDOM % 60000)) conv=notrunc & \ + done; wait", + ) + .expect("Failed mixed random I/O"); + }); + } + + #[test] + fn test_virtio_block_qcow2_multiqueue_fsync() { + run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + guest + .ssh_command("sudo mkfs.ext4 -F /dev/vdc") + .expect("Failed to format disk"); + guest + .ssh_command("sudo mkdir -p /mnt/test && sudo mount /dev/vdc /mnt/test") + .expect("Failed to mount disk"); + + guest + .ssh_command( + "for i in $(seq 1 8); do \ + (for j in $(seq 1 100); do \ + echo \"data$j\" | sudo tee /mnt/test/file${i}_$j > /dev/null && sudo sync; \ + done) & \ + done; wait", + ) + .expect("Failed fsync storm round 1"); + + assert_eq!( + guest + .ssh_command("ls /mnt/test/file* | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 800, + "Expected 800 files (8 processes x 100 files)" + ); + + guest + .ssh_command( + "for i in $(seq 1 8); do \ + (for j in $(seq 1 50); do \ + sudo dd if=/dev/urandom of=/mnt/test/dd${i}_$j bs=4K count=1 conv=fsync 2>/dev/null; \ + done) & \ + done; wait", + ) + .expect("Failed fsync storm round 2"); + + guest + .ssh_command("sudo umount /mnt/test") + .expect("Failed to unmount"); + }); + } + + #[test] + fn test_virtio_block_qcow2_multiqueue_metadata() { + run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + guest + .ssh_command("sudo mkfs.ext4 -F /dev/vdc") + .expect("Failed to format disk"); + guest + .ssh_command("sudo mkdir -p /mnt/test && sudo mount /dev/vdc /mnt/test") + .expect("Failed to mount disk"); + + guest + .ssh_command( + "for i in $(seq 1 8); do \ + (for j in $(seq 1 50); do \ + sudo mkdir -p /mnt/test/dir$i/subdir$j; \ + done) & \ + done; wait", + ) + .expect("Failed parallel mkdir"); + + let dir_count: u32 = guest + .ssh_command("find /mnt/test -type d | wc -l") + .expect("Failed to count directories") + .trim() + .parse() + .unwrap_or(0); + assert!( + dir_count >= 400, + "Expected at least 400 directories, got {dir_count}" + ); + + guest + .ssh_command( + "for i in $(seq 1 8); do \ + (for j in $(seq 1 100); do \ + sudo touch /mnt/test/dir$i/file$j; \ + done) & \ + done; wait", + ) + .expect("Failed parallel touch"); + + let file_count: u32 = guest + .ssh_command("find /mnt/test -type f | wc -l") + .expect("Failed to count files") + .trim() + .parse() + .unwrap_or(0); + assert!( + file_count >= 400, + "Expected at least 400 files, got {file_count}" + ); + + guest + .ssh_command( + "for i in $(seq 1 4); do \ + sudo rm -rf /mnt/test/dir$i & \ + (for j in $(seq 1 50); do \ + sudo touch /mnt/test/newfile${i}_$j; \ + done) & \ + done; wait", + ) + .expect("Failed parallel rm + touch"); + + guest + .ssh_command( + "for i in $(seq 5 8); do \ + (for j in $(seq 1 25); do \ + sudo mv /mnt/test/dir$i/file$j /mnt/test/dir$i/renamed$j 2>/dev/null || true; \ + done) & \ + done; wait", + ) + .expect("Failed parallel rename"); + + guest + .ssh_command("sync && sudo umount /mnt/test") + .expect("Failed to unmount"); + }); + } + #[test] fn test_virtio_block_qcow2_dirty_bit_unclean_shutdown() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); From 9bc367a27b2916b509b64cc53f55ab467b414a96 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 3 Feb 2026 18:19:43 +0100 Subject: [PATCH 0506/1893] block: qcow: Use Arc> for thread safe multiqueue access Wrap QcowFile in Arc> 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 --- block/src/qcow_sync.rs | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 0efc106fc7..e76c07c15f 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -6,6 +6,7 @@ use std::collections::VecDeque; use std::fs::File; use std::io::{Seek, SeekFrom}; use std::os::fd::AsRawFd; +use std::sync::{Arc, Mutex}; use vmm_sys_util::eventfd::EventFd; @@ -16,13 +17,21 @@ use crate::qcow::{QcowFile, RawFile, Result as QcowResult}; use crate::{AsyncAdaptor, BlockBackend}; pub struct QcowDiskSync { - qcow_file: QcowFile, + // FIXME: The Mutex serializes all QCOW2 I/O operations across queues, which + // is necessary for correctness but eliminates any parallelism benefit from + // multiqueue. QcowFile has internal mutable state (L2 cache, refcounts, file + // position) that is not safe to share across threads via Clone. + // + // A proper fix would require restructuring QcowFile to separate metadata + // operations (which need synchronization) from data I/O (which could be + // parallelized with per queue file descriptors). See #7560 for details. + qcow_file: Arc>, } impl QcowDiskSync { pub fn new(file: File, direct_io: bool) -> QcowResult { Ok(QcowDiskSync { - qcow_file: QcowFile::from(RawFile::new(file, direct_io))?, + qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)), }) } } @@ -30,12 +39,14 @@ impl QcowDiskSync { impl DiskFile for QcowDiskSync { fn logical_size(&mut self) -> DiskFileResult { self.qcow_file + .lock() + .unwrap() .seek(SeekFrom::End(0)) .map_err(DiskFileError::Size) } fn physical_size(&mut self) -> DiskFileResult { - self.qcow_file.physical_size().map_err(|e| { + self.qcow_file.lock().unwrap().physical_size().map_err(|e| { let io_inner = match e { crate::Error::GetFileMetadata(e) => e, _ => unreachable!(), @@ -45,22 +56,22 @@ impl DiskFile for QcowDiskSync { } fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { - Ok(Box::new(QcowSync::new(self.qcow_file.clone())) as Box) + Ok(Box::new(QcowSync::new(Arc::clone(&self.qcow_file))) as Box) } fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.qcow_file.as_raw_fd()) + BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd()) } } pub struct QcowSync { - qcow_file: QcowFile, + qcow_file: Arc>, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, } impl QcowSync { - pub fn new(qcow_file: QcowFile) -> Self { + pub fn new(qcow_file: Arc>) -> Self { QcowSync { qcow_file, eventfd: EventFd::new(libc::EFD_NONBLOCK) @@ -83,7 +94,7 @@ impl AsyncIo for QcowSync { iovecs: &[libc::iovec], user_data: u64, ) -> AsyncIoResult<()> { - self.qcow_file.read_vectored_sync( + self.qcow_file.lock().unwrap().read_vectored_sync( offset, iovecs, user_data, @@ -98,7 +109,7 @@ impl AsyncIo for QcowSync { iovecs: &[libc::iovec], user_data: u64, ) -> AsyncIoResult<()> { - self.qcow_file.write_vectored_sync( + self.qcow_file.lock().unwrap().write_vectored_sync( offset, iovecs, user_data, @@ -108,8 +119,11 @@ impl AsyncIo for QcowSync { } fn fsync(&mut self, user_data: Option) -> AsyncIoResult<()> { - self.qcow_file - .fsync_sync(user_data, &self.eventfd, &mut self.completion_list) + self.qcow_file.lock().unwrap().fsync_sync( + user_data, + &self.eventfd, + &mut self.completion_list, + ) } fn next_completed_request(&mut self) -> Option<(u64, i32)> { From 7667e8c0bcfe44289f6f57d6ae4b5d729a65de7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 23:55:48 +0000 Subject: [PATCH 0507/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 7a7e8bed0d..e8a473e7a8 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.43.0 + - uses: crate-ci/typos@v1.43.1 From 8c168d928f74678edf306bfd26ae642d6e946d01 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 3 Feb 2026 22:17:35 +0100 Subject: [PATCH 0508/1893] 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 --- block/src/qcow/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 5300b56e2b..300cd8bc3f 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -112,6 +112,8 @@ pub enum Error { SettingRefcountRefcount(#[source] io::Error), #[error("Size too small for number of clusters")] SizeTooSmallForNumberOfClusters, + #[error("Failed to sync header")] + SyncingHeader(#[source] io::Error), #[error("L1 entry table too large: {0}")] TooManyL1Entries(u64), #[error("Ref count table too large: {0}")] @@ -698,7 +700,7 @@ impl QcowHeader { self.incompatible_features &= !IncompatFeatures::DIRTY.bits(); } self.write_incompatible_features(file)?; - file.fsync().map_err(Error::WritingHeader)?; + file.fsync().map_err(Error::SyncingHeader)?; } Ok(()) } @@ -711,7 +713,7 @@ impl QcowHeader { if self.version == 3 { self.incompatible_features |= IncompatFeatures::CORRUPT.bits(); self.write_incompatible_features(file)?; - file.fsync().map_err(Error::WritingHeader)?; + file.fsync().map_err(Error::SyncingHeader)?; } Ok(()) } @@ -734,7 +736,7 @@ impl QcowHeader { file.seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET)) .map_err(Error::WritingHeader)?; u64::write_be(file, 0).map_err(Error::WritingHeader)?; - file.fsync().map_err(Error::WritingHeader)?; + file.fsync().map_err(Error::SyncingHeader)?; } Ok(()) } From 093a8497d0331a257b32b6c565d4129175d33fef Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 29 Jan 2026 19:28:43 -0500 Subject: [PATCH 0509/1893] 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 --- scripts/run_integration_tests_aarch64.sh | 2 +- scripts/run_integration_tests_x86_64.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index f3fee60077..68500028bc 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -13,7 +13,7 @@ build_virtiofsd() { VIRTIOFSD_DIR="$WORKLOADS_DIR/virtiofsd_build" VIRTIOFSD_REPO="https://gitlab.com/virtio-fs/virtiofsd.git" - checkout_repo "$VIRTIOFSD_DIR" "$VIRTIOFSD_REPO" v1.8.0 "97ea7908fe7f9bc59916671a771bdcfaf4044b45" + checkout_repo "$VIRTIOFSD_DIR" "$VIRTIOFSD_REPO" v1.13.3 "bbf82173682a3e48083771a0a23331e5c23b4924" if [ ! -f "$VIRTIOFSD_DIR/.built" ]; then pushd "$VIRTIOFSD_DIR" || exit diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 5eb8531d4f..31d3965c20 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -156,7 +156,7 @@ if [ ! -f "$VIRTIOFSD" ]; then pushd "$WORKLOADS_DIR" || exit git clone "https://gitlab.com/virtio-fs/virtiofsd.git" $VIRTIOFSD_DIR pushd $VIRTIOFSD_DIR || exit - git checkout v1.8.0 + git checkout v1.13.3 time cargo build --release cp target/release/virtiofsd "$VIRTIOFSD" || exit 1 popd || exit From 96f663b5f93a14efd9de2ca0de1a857b0bc22abe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:54:05 +0000 Subject: [PATCH 0510/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index e8a473e7a8..eeae1baeb3 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.43.1 + - uses: crate-ci/typos@v1.43.2 From 12f66b7ddc1671e62c8b2b78b975bebdfda58e1e Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 18 Sep 2025 16:45:41 -0700 Subject: [PATCH 0511/1893] 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 --- cloud-hypervisor/tests/integration.rs | 179 +++++++++++++------------- test_infra/src/lib.rs | 50 +++++-- 2 files changed, 128 insertions(+), 101 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 6c580bac0b..59db256f10 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2539,6 +2539,88 @@ EOF assert_eq!(test_message_write, file_message); } +fn _test_simple_launch(guest: &Guest) { + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .default_kernel_cmdline() + .default_disks() + .default_net() + .args(["--serial", "tty", "--console", "off"]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot(None).unwrap(); + + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); + assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + assert_eq!(guest.get_pci_bridge_class().unwrap_or_default(), "0x060000"); + + let expected_sequential_events = [ + &MetaEvent { + event: "starting".to_string(), + device_id: None, + }, + &MetaEvent { + event: "booting".to_string(), + device_id: None, + }, + &MetaEvent { + event: "booted".to_string(), + device_id: None, + }, + &MetaEvent { + event: "activated".to_string(), + device_id: Some("_disk0".to_string()), + }, + &MetaEvent { + event: "reset".to_string(), + device_id: Some("_disk0".to_string()), + }, + ]; + assert!(check_sequential_events( + &expected_sequential_events, + &event_path + )); + + // It's been observed on the Bionic image that udev and snapd + // services can cause some delay in the VM's shutdown. Disabling + // them improves the reliability of this test. + let _ = guest.ssh_command("sudo systemctl disable udev"); + let _ = guest.ssh_command("sudo systemctl stop udev"); + let _ = guest.ssh_command("sudo systemctl disable snapd"); + let _ = guest.ssh_command("sudo systemctl stop snapd"); + + guest.ssh_command("sudo poweroff").unwrap(); + thread::sleep(std::time::Duration::new(20, 0)); + let latest_events = [ + &MetaEvent { + event: "shutdown".to_string(), + device_id: None, + }, + &MetaEvent { + event: "deleted".to_string(), + device_id: None, + }, + &MetaEvent { + event: "shutdown".to_string(), + device_id: None, + }, + ]; + assert!(check_latest_events_exact(&latest_events, &event_path)); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + mod common_parallel { use std::cmp; use std::fs::{File, OpenOptions, copy}; @@ -2550,100 +2632,19 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_focal_hypervisor_fw() { - let path = fw_path(FwType::RustHypervisorFirmware); - test_simple_launch(&path, FOCAL_IMAGE_NAME); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let mut guest = Guest::new(Box::new(disk_config)); + guest.kernel_path = Some(fw_path(FwType::RustHypervisorFirmware)); + _test_simple_launch(&guest) } #[test] #[cfg(target_arch = "x86_64")] fn test_focal_ovmf() { - let path = fw_path(FwType::Ovmf); - test_simple_launch(&path, FOCAL_IMAGE_NAME); - } - - #[cfg(target_arch = "x86_64")] - fn test_simple_launch(fw_path: &str, disk_path: &str) { - let disk_config = Box::new(UbuntuDiskConfig::new(disk_path.to_string())); - let guest = Guest::new(disk_config); - let event_path = temp_event_monitor_path(&guest.tmp_dir); - - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) - .args(["--memory", "size=512M"]) - .args(["--kernel", fw_path]) - .default_disks() - .default_net() - .args(["--serial", "tty", "--console", "off"]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(Some(120)).unwrap(); - - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); - assert_eq!(guest.get_pci_bridge_class().unwrap_or_default(), "0x060000"); - - let expected_sequential_events = [ - &MetaEvent { - event: "starting".to_string(), - device_id: None, - }, - &MetaEvent { - event: "booting".to_string(), - device_id: None, - }, - &MetaEvent { - event: "booted".to_string(), - device_id: None, - }, - &MetaEvent { - event: "activated".to_string(), - device_id: Some("_disk0".to_string()), - }, - &MetaEvent { - event: "reset".to_string(), - device_id: Some("_disk0".to_string()), - }, - ]; - assert!(check_sequential_events( - &expected_sequential_events, - &event_path - )); - - // It's been observed on the Bionic image that udev and snapd - // services can cause some delay in the VM's shutdown. Disabling - // them improves the reliability of this test. - let _ = guest.ssh_command("sudo systemctl disable udev"); - let _ = guest.ssh_command("sudo systemctl stop udev"); - let _ = guest.ssh_command("sudo systemctl disable snapd"); - let _ = guest.ssh_command("sudo systemctl stop snapd"); - - guest.ssh_command("sudo poweroff").unwrap(); - thread::sleep(std::time::Duration::new(20, 0)); - let latest_events = [ - &MetaEvent { - event: "shutdown".to_string(), - device_id: None, - }, - &MetaEvent { - event: "deleted".to_string(), - device_id: None, - }, - &MetaEvent { - event: "shutdown".to_string(), - device_id: None, - }, - ]; - assert!(check_latest_events_exact(&latest_events, &event_path)); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let mut guest = Guest::new(Box::new(disk_config)); + guest.kernel_path = Some(fw_path(FwType::Ovmf)); + _test_simple_launch(&guest) } #[test] diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 2cd8527c7d..25548b557f 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -74,7 +74,7 @@ pub struct GuestNetworkConfig { pub const DEFAULT_TCP_LISTENER_MESSAGE: &str = "booted"; pub const DEFAULT_TCP_LISTENER_PORT: u16 = 8000; -pub const DEFAULT_TCP_LISTENER_TIMEOUT: i32 = 120; +pub const DEFAULT_TCP_LISTENER_TIMEOUT: u32 = 120; #[derive(Error, Debug)] pub enum WaitForBootError { @@ -91,7 +91,7 @@ pub enum WaitForBootError { } impl GuestNetworkConfig { - pub fn wait_vm_boot(&self, custom_timeout: Option) -> Result<(), WaitForBootError> { + pub fn wait_vm_boot(&self, custom_timeout: Option) -> Result<(), WaitForBootError> { let start = std::time::Instant::now(); // The 'port' is unique per 'GUEST' and listening to wild-card ip avoids retrying on 'TcpListener::bind()' let listen_addr = format!("0.0.0.0:{}", self.tcp_listener_port); @@ -122,14 +122,15 @@ impl GuestNetworkConfig { .expect("Cannot add 'tcp_listener' event to epoll"); let mut events = [epoll::Event::new(epoll::Events::empty(), 0); 1]; loop { - let num_events = match epoll::wait(epoll_fd, timeout * 1000_i32, &mut events[..]) { - Ok(num_events) => Ok(num_events), - Err(e) => match e.raw_os_error() { - Some(libc::EAGAIN) | Some(libc::EINTR) => continue, - _ => Err(e), - }, - } - .map_err(WaitForBootError::EpollWait)?; + let num_events = + match epoll::wait(epoll_fd, (timeout * 1000) as i32, &mut events[..]) { + Ok(num_events) => Ok(num_events), + Err(e) => match e.raw_os_error() { + Some(libc::EAGAIN) | Some(libc::EINTR) => continue, + _ => Err(e), + }, + } + .map_err(WaitForBootError::EpollWait)?; if num_events == 0 { return Err(WaitForBootError::EpollWaitTimeout); } @@ -887,6 +888,10 @@ pub struct Guest { pub tmp_dir: TempDir, pub disk_config: Box, pub network: GuestNetworkConfig, + pub vm_type: GuestVmType, + pub boot_timeout: u32, + pub kernel_path: Option, + pub kernel_cmdline: Option, } // Return the next id that can be used for this guest. This is stored in a @@ -951,6 +956,10 @@ impl Guest { tmp_dir, disk_config, network, + vm_type: GuestVmType::Regular, + boot_timeout: DEFAULT_TCP_LISTENER_TIMEOUT, + kernel_path: None, + kernel_cmdline: None, } } @@ -1076,7 +1085,7 @@ impl Guest { .map_err(Error::Parsing) } - pub fn wait_vm_boot(&self, custom_timeout: Option) -> Result<(), Error> { + pub fn wait_vm_boot(&self, custom_timeout: Option) -> Result<(), Error> { self.network .wait_vm_boot(custom_timeout) .map_err(Error::WaitForBoot) @@ -1214,7 +1223,7 @@ impl Guest { ); } - pub fn reboot_linux(&self, current_reboot_count: u32, custom_timeout: Option) { + pub fn reboot_linux(&self, current_reboot_count: u32, custom_timeout: Option) { let list_boots_cmd = "sudo last | grep -c reboot"; let boot_count = self .ssh_command(list_boots_cmd) @@ -1461,6 +1470,17 @@ impl<'a> GuestCommand<'a> { pub fn default_net(&mut self) -> &mut Self { self.args(["--net", self.guest.default_net_string().as_str()]) } + + pub fn default_kernel_cmdline(&mut self) -> &mut Self { + if let Some(kernel) = &self.guest.kernel_path { + self.command.args(["--kernel", kernel]); + if let Some(cmdline) = &self.guest.kernel_cmdline { + self.command.args(["--cmdline", cmdline]); + } + } + + self + } } /// Returns the absolute path into the workspaces target directory to locate the desired @@ -1858,3 +1878,9 @@ pub fn extract_bar_address(output: &str, device_desc: &str, bar_index: usize) -> } None } + +#[derive(PartialEq, Clone, Copy)] +pub enum GuestVmType { + Regular, + Confidential, +} From 6042eb969ef022cd7b472fb1faa0b875a1e8e060 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 17 Oct 2025 15:24:12 -0700 Subject: [PATCH 0512/1893] 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 --- cloud-hypervisor/tests/integration.rs | 246 +++++++++---------- performance-metrics/src/performance_tests.rs | 6 +- test_infra/src/lib.rs | 45 ++-- 3 files changed, 153 insertions(+), 144 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 59db256f10..5093d05379 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -234,7 +234,7 @@ fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { // Then boot it assert!(target_api.remote_command("boot", None)); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); @@ -254,7 +254,7 @@ fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { // Then boot it again assert!(target_api.remote_command("boot", None)); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); @@ -299,7 +299,7 @@ fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { // Then boot it assert!(target_api.remote_command("boot", None)); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); @@ -321,7 +321,7 @@ fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { // Then boot it again assert!(target_api.remote_command("boot", None)); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); @@ -793,7 +793,7 @@ fn setup_ovs_dpdk_guests( let guest_net_iface = "enp0s5"; let r = std::panic::catch_unwind(|| { - guest1.wait_vm_boot(None).unwrap(); + guest1.wait_vm_boot().unwrap(); guest1 .ssh_command(&format!( @@ -838,7 +838,7 @@ fn setup_ovs_dpdk_guests( .unwrap(); let r = std::panic::catch_unwind(|| { - guest2.wait_vm_boot(None).unwrap(); + guest2.wait_vm_boot().unwrap(); guest2 .ssh_command(&format!( @@ -1026,7 +1026,7 @@ fn test_cpu_topology(threads_per_core: u8, cores_per_package: u8, packages: u8, .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( guest.get_cpu_count().unwrap_or_default(), u32::from(total_vcpus) @@ -1141,7 +1141,7 @@ fn _test_guest_numa_nodes(acpi: bool) { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); guest.check_numa_common( Some(&[960_000, 1_920_000, 2_880_000]), @@ -1207,7 +1207,7 @@ fn _test_power_button(acpi: bool) { let child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!(remote_command(&api_socket, "power-button", None)); }); @@ -1314,7 +1314,7 @@ fn test_vhost_user_net( } let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); if let Some(tap_name) = tap { let tap_count = exec_host_command_output(&format!("ip link | grep -c {tap_name}")); @@ -1458,7 +1458,7 @@ fn test_vhost_user_blk( .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check both if /dev/vdc exists and if the block size is 16M. assert_eq!( @@ -1600,7 +1600,7 @@ fn test_boot_from_vhost_user_blk( .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Just check the VM booted correctly. assert_eq!(guest.get_cpu_count().unwrap_or_default(), num_queues as u32); @@ -1686,7 +1686,7 @@ fn _test_virtio_fs( let mut child = guest_command.capture_output().spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); if hotplug { // Add fs to the VM @@ -1860,7 +1860,7 @@ fn test_virtio_pmem(discard_writes: bool, specify_size: bool) { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check for the presence of /dev/pmem0 assert_eq!( @@ -1877,7 +1877,7 @@ fn test_virtio_pmem(discard_writes: bool, specify_size: bool) { assert_eq!(guest.ssh_command("sudo umount /mnt").unwrap(), ""); assert_eq!(guest.ssh_command("ls /mnt").unwrap(), ""); - guest.reboot_linux(0, None); + guest.reboot_linux(0); assert_eq!(guest.ssh_command("sudo mount /dev/pmem0 /mnt").unwrap(), ""); assert_eq!( guest @@ -1930,7 +1930,7 @@ fn _test_virtio_vsock(hotplug: bool) { let mut child = cmd.capture_output().spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); if hotplug { let (cmd_success, cmd_output) = remote_command_w_output( @@ -1954,7 +1954,7 @@ fn _test_virtio_vsock(hotplug: bool) { // Validate vsock works as expected. guest.check_vsock(socket.as_str()); - guest.reboot_linux(0, None); + guest.reboot_linux(0); // Validate vsock still works after a reboot. guest.check_vsock(socket.as_str()); @@ -2002,7 +2002,7 @@ fn test_memory_mergeable(mergeable: bool) { .unwrap(); let r = std::panic::catch_unwind(|| { - guest1.wait_vm_boot(None).unwrap(); + guest1.wait_vm_boot().unwrap(); }); if r.is_err() { kill_child(&mut child1); @@ -2028,7 +2028,7 @@ fn test_memory_mergeable(mergeable: bool) { .unwrap(); let r = std::panic::catch_unwind(|| { - guest2.wait_vm_boot(None).unwrap(); + guest2.wait_vm_boot().unwrap(); let ksm_ps_guest2 = get_ksm_pages_shared(); if mergeable { @@ -2302,7 +2302,7 @@ fn _test_virtio_iommu(acpi: bool) { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Verify the virtio-iommu device is present. assert!( @@ -2555,7 +2555,7 @@ fn _test_simple_launch(guest: &Guest) { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); @@ -2665,7 +2665,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(Some(120)).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); @@ -2720,7 +2720,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!( guest @@ -2766,7 +2766,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let pid = child.id(); let taskset_vcpu0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); assert_eq!(String::from_utf8_lossy(&taskset_vcpu0.stdout).trim(), "0,2"); @@ -2819,7 +2819,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let pid = child.id(); let taskset_q0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); assert_eq!(String::from_utf8_lossy(&taskset_q0.stdout).trim(), "0,2"); @@ -2854,7 +2854,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let r = std::panic::catch_unwind(|| { assert_eq!(guest.get_cpu_count().unwrap_or_default(), 48); @@ -2891,7 +2891,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); - guest.wait_vm_boot(Some(120)).unwrap(); + guest.wait_vm_boot().unwrap(); let r = std::panic::catch_unwind(|| { assert!(guest.get_total_memory().unwrap_or_default() > 128_000_000); @@ -2936,7 +2936,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!(guest.get_total_memory().unwrap_or_default() > 2_880_000); @@ -2955,7 +2955,7 @@ mod common_parallel { thread::sleep(std::time::Duration::new(5, 0)); assert!(guest.get_total_memory().unwrap_or_default() > 4_800_000); - guest.reboot_linux(0, None); + guest.reboot_linux(0); // Check the amount of RAM after reboot assert!(guest.get_total_memory().unwrap_or_default() > 4_800_000); @@ -3021,7 +3021,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let r = std::panic::catch_unwind(|| { let (cmd_success, cmd_output) = remote_command_w_output( @@ -3077,7 +3077,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); @@ -3114,7 +3114,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); #[cfg(target_arch = "aarch64")] let iface = "enp0s4"; @@ -3193,7 +3193,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let grep_cmd = "lspci | grep \"Host bridge\" | wc -l"; @@ -3310,7 +3310,7 @@ mod common_parallel { let cmd = "cat /sys/block/vdc/device/../numa_node"; let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( guest @@ -3348,7 +3348,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); @@ -3394,7 +3394,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); @@ -3471,7 +3471,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check both if /dev/vdc exists and if the block size is 16M. assert_eq!( @@ -3881,7 +3881,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); test_fn(&guest); }); @@ -4254,7 +4254,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( check_dirty_flag(&test_image_path).expect("Failed to check dirty flag"), @@ -4317,7 +4317,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( check_dirty_flag(&test_image_path).expect("Failed to check dirty flag"), @@ -4569,7 +4569,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check both if /dev/vdc exists and if the block size is 100 MiB. assert_eq!( @@ -4644,7 +4644,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(Some(120)).unwrap(); + guest.wait_vm_boot().unwrap(); }); kill_child(&mut child); @@ -4747,7 +4747,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( guest @@ -4794,7 +4794,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( guest @@ -4830,7 +4830,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( guest @@ -4871,7 +4871,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( guest @@ -4976,7 +4976,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Simple checks to validate the VM booted properly assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); @@ -5013,7 +5013,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let tap_count = exec_host_command_output("ip link | grep -c mytap1"); assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1"); @@ -5053,7 +5053,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Test that PMU exists. assert_eq!( @@ -5090,7 +5090,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Test that there is no ttyS0 assert_eq!( @@ -5138,7 +5138,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Test that there is a ttyS0 assert_eq!( @@ -5194,7 +5194,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Test that there is a ttyS0 assert_eq!( @@ -5254,7 +5254,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Test that there is a ttyS0 assert_eq!( @@ -5316,7 +5316,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Get pty fd for console let console_path = get_pty_path(&api_socket, "console"); _test_pty_interaction(console_path); @@ -5365,7 +5365,7 @@ mod common_parallel { .unwrap(); let _ = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); }); let mut socat_command = Command::new("socat"); @@ -5430,7 +5430,7 @@ mod common_parallel { let cmd = format!("echo {text} | sudo tee /dev/hvc0"); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!( guest @@ -5473,7 +5473,7 @@ mod common_parallel { .spawn() .unwrap(); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); guest.ssh_command("sudo shutdown -h now").unwrap(); @@ -5770,7 +5770,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); @@ -5868,7 +5868,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // 2 network interfaces + default localhost ==> 3 interfaces assert_eq!( @@ -5973,7 +5973,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); @@ -5993,7 +5993,7 @@ mod common_parallel { u32::from(desired_vcpus) ); - guest.reboot_linux(0, None); + guest.reboot_linux(0); assert_eq!( guest.get_cpu_count().unwrap_or_default(), @@ -6062,7 +6062,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); @@ -6083,7 +6083,7 @@ mod common_parallel { assert!(guest.get_total_memory().unwrap_or_default() > 480_000); assert!(guest.get_total_memory().unwrap_or_default() < 960_000); - guest.reboot_linux(0, None); + guest.reboot_linux(0); assert!(guest.get_total_memory().unwrap_or_default() < 960_000); @@ -6108,7 +6108,7 @@ mod common_parallel { let desired_ram = 1024 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - guest.reboot_linux(1, None); + guest.reboot_linux(1); assert!(guest.get_total_memory().unwrap_or_default() > 960_000); assert!(guest.get_total_memory().unwrap_or_default() < 1_920_000); @@ -6145,7 +6145,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); @@ -6173,7 +6173,7 @@ mod common_parallel { assert!(guest.get_total_memory().unwrap_or_default() > 960_000); assert!(guest.get_total_memory().unwrap_or_default() < 1_920_000); - guest.reboot_linux(0, None); + guest.reboot_linux(0); // Check the amount of memory after reboot is 1GiB assert!(guest.get_total_memory().unwrap_or_default() > 960_000); @@ -6216,7 +6216,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); @@ -6275,7 +6275,7 @@ mod common_parallel { .spawn() .unwrap(); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let r = std::panic::catch_unwind(|| { let overhead = get_vmm_overhead(child.id(), guest_memory_size_kb); @@ -6319,7 +6319,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check /dev/vdc is not there assert_eq!( @@ -6392,7 +6392,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check /dev/vdc is not there assert_eq!( @@ -6490,7 +6490,7 @@ mod common_parallel { .unwrap(); // Reboot the VM. - guest.reboot_linux(0, None); + guest.reboot_linux(0); // Check still there after reboot assert_eq!( @@ -6518,7 +6518,7 @@ mod common_parallel { 0 ); - guest.reboot_linux(1, None); + guest.reboot_linux(1); // Check device still absent assert_eq!( @@ -6584,7 +6584,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Add the disk to the VM let (cmd_success, cmd_output) = remote_command_w_output( @@ -6834,7 +6834,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // MIN-IO column assert_eq!( @@ -6903,7 +6903,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Wait for balloon memory's initialization and check its size. // The virtio-balloon driver might take a few seconds to report the @@ -6958,7 +6958,7 @@ mod common_parallel { let pid = child.id(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check the initial RSS is less than 1GiB let rss = process_rss_kib(pid); @@ -7047,7 +7047,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check /dev/pmem0 is not there assert_eq!( @@ -7098,7 +7098,7 @@ mod common_parallel { 1 ); - guest.reboot_linux(0, None); + guest.reboot_linux(0); // Check still there after reboot assert_eq!( @@ -7126,7 +7126,7 @@ mod common_parallel { 0 ); - guest.reboot_linux(1, None); + guest.reboot_linux(1); // Check still absent after reboot assert_eq!( @@ -7188,7 +7188,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let r = std::panic::catch_unwind(|| { // Add network @@ -7298,7 +7298,7 @@ mod common_parallel { 3 ); - guest.reboot_linux(0, None); + guest.reboot_linux(0); // 2 network interfaces + default localhost ==> 3 interfaces assert_eq!( @@ -7401,7 +7401,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let orig_counters = get_counters(&api_socket); guest @@ -7440,7 +7440,7 @@ mod common_parallel { let vmcore_file = temp_vmcore_file_path(&guest.tmp_dir); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!(remote_command(&api_socket, "pause", None)); @@ -7488,7 +7488,7 @@ mod common_parallel { let vmcore_file = temp_vmcore_file_path(&guest.tmp_dir); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!(remote_command( &api_socket, @@ -7529,7 +7529,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let mut expected_reboot_count = 1; @@ -7555,7 +7555,7 @@ mod common_parallel { // Trigger a panic (sync first). We need to do this inside a screen with a delay so the SSH command returns. guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); // Allow some time for the watchdog to trigger (max 30s) and reboot to happen - guest.wait_vm_boot(Some(50)).unwrap(); + guest.wait_vm_boot_custom_timeout(50).unwrap(); // Check a reboot is triggered by the watchdog expected_reboot_count += 1; assert_eq!(get_reboot_count(&guest), expected_reboot_count); @@ -7612,7 +7612,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Trigger guest a panic make_guest_panic(&guest); @@ -7680,7 +7680,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( guest @@ -7692,7 +7692,7 @@ mod common_parallel { 2 ); - guest.reboot_linux(0, None); + guest.reboot_linux(0); assert_eq!( guest @@ -7828,7 +7828,7 @@ mod common_parallel { // gets tested through wait_vm_boot() as it expects to receive a // HTTP request, and through the SSH command as well. let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( guest @@ -7840,7 +7840,7 @@ mod common_parallel { 2 ); - guest.reboot_linux(0, None); + guest.reboot_linux(0); assert_eq!( guest @@ -8082,7 +8082,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Hotplug the SPDK-NVMe device to the VM let (cmd_success, cmd_output) = remote_command_w_output( @@ -8128,7 +8128,7 @@ mod common_parallel { assert_eq!(guest.ssh_command("sudo umount /mnt").unwrap(), ""); assert_eq!(guest.ssh_command("ls /mnt").unwrap(), ""); - guest.reboot_linux(0, None); + guest.reboot_linux(0); assert_eq!( guest.ssh_command("sudo mount /dev/nvme0n1 /mnt").unwrap(), "" @@ -8175,7 +8175,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check both if /dev/vdc exists and if the block size is 128M. assert_eq!( @@ -8298,7 +8298,7 @@ mod common_parallel { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check we can find network interface related to vDPA device assert_eq!( @@ -8378,7 +8378,7 @@ mod common_parallel { thread::sleep(std::time::Duration::new(10, 0)); let mut child = guest_cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!( guest.ssh_command("ls /dev/tpm0").unwrap().trim(), "/dev/tpm0" @@ -8433,7 +8433,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let mut r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); }); kill_child(&mut child); @@ -8477,7 +8477,7 @@ mod common_parallel { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!(remote_command(&api_socket, "nmi", None)); @@ -8546,7 +8546,7 @@ mod dbus_api { // Then boot it assert!(http_api.remote_command("boot", None)); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); @@ -8565,7 +8565,7 @@ mod dbus_api { // Then boot it again assert!(http_api.remote_command("boot", None)); - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); @@ -8708,7 +8708,7 @@ mod ivshmem { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Make sure the source VM is functional // Check the number of vCPUs @@ -8855,7 +8855,7 @@ mod ivshmem { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); _test_ivshmem(&guest, &ivshmem_file_path, file_size); }); kill_child(&mut child); @@ -8920,7 +8920,7 @@ mod ivshmem { let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); @@ -9157,7 +9157,7 @@ mod common_sequential { let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); @@ -9426,7 +9426,7 @@ mod common_sequential { let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // close the fds after VM boots, as CH duplicates them before using for tap in taps.iter() { @@ -9624,7 +9624,7 @@ mod common_sequential { let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); @@ -10766,7 +10766,7 @@ mod vfio { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); @@ -10816,7 +10816,7 @@ mod vfio { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Hotplug the card to the VM let (cmd_success, cmd_output) = remote_command_w_output( @@ -10864,12 +10864,12 @@ mod vfio { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Check the VFIO device works after boot guest.check_nvidia_gpu(); - guest.reboot_linux(0, None); + guest.reboot_linux(0); // Check the VFIO device works after reboot guest.check_nvidia_gpu(); @@ -10904,7 +10904,7 @@ mod vfio { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); assert!( guest @@ -11130,7 +11130,7 @@ mod live_migration { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Make sure the source VM is functional // Check the number of vCPUs @@ -11294,7 +11294,7 @@ mod live_migration { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Make sure the source VM is functional // Check the number of vCPUs @@ -11495,7 +11495,7 @@ mod live_migration { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Make sure the source VM is functional // Check the number of vCPUs @@ -11721,7 +11721,7 @@ mod live_migration { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Make sure the source VM is functional // Check the number of vCPUs @@ -11827,7 +11827,7 @@ mod live_migration { // Trigger a panic (sync first). We need to do this inside a screen with a delay so the SSH command returns. guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); // Allow some time for the watchdog to trigger (max 30s) and reboot to happen - guest.wait_vm_boot(Some(50)).unwrap(); + guest.wait_vm_boot_custom_timeout(50).unwrap(); // Check a reboot is triggered by the watchdog expected_reboot_count += 1; assert_eq!(get_reboot_count(&guest), expected_reboot_count); @@ -12012,7 +12012,7 @@ mod live_migration { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Make sure the source VM is functaionl // Check the number of vCPUs @@ -12228,7 +12228,7 @@ mod live_migration { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Ensure the source VM is running normally assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); @@ -12464,7 +12464,7 @@ mod aarch64_acpi { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(Some(120)).unwrap(); + guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); assert!(guest.get_total_memory().unwrap_or_default() > 400_000); @@ -12564,7 +12564,7 @@ mod rate_limiter { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let measured_bps = measure_virtio_net_throughput(test_timeout, num_queues / 2, &guest, rx, true) .unwrap(); @@ -12650,7 +12650,7 @@ mod rate_limiter { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let fio_command = format!( "sudo fio --filename=/dev/vdc --name=test --output-format=json \ @@ -12744,7 +12744,7 @@ mod rate_limiter { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let mut fio_command = format!( "sudo fio --name=global --output-format=json \ @@ -12845,7 +12845,7 @@ mod fw_cfg { let mut child = cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // Wait a while for guest thread::sleep(std::time::Duration::new(3, 0)); let result = guest diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 573db69aef..7dcf443d3f 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -169,7 +169,7 @@ pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); measure_virtio_net_throughput(test_timeout, num_queues / 2, &guest, rx, bandwidth).unwrap() }); @@ -210,7 +210,7 @@ pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); // 'ethr' tool will measure the latency multiple times with provided test time let latency = measure_virtio_net_latency(&guest, control.test_timeout).unwrap(); @@ -441,7 +441,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot(None).unwrap(); + guest.wait_vm_boot().unwrap(); let fio_command = format!( "sudo fio --filename=/dev/vdc --name=test --output-format=json \ diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 25548b557f..d9a04bce9a 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -91,16 +91,12 @@ pub enum WaitForBootError { } impl GuestNetworkConfig { - pub fn wait_vm_boot(&self, custom_timeout: Option) -> Result<(), WaitForBootError> { + pub fn wait_vm_boot(&self, custom_timeout: u32) -> Result<(), WaitForBootError> { let start = std::time::Instant::now(); // The 'port' is unique per 'GUEST' and listening to wild-card ip avoids retrying on 'TcpListener::bind()' let listen_addr = format!("0.0.0.0:{}", self.tcp_listener_port); let expected_guest_addr = self.guest_ip0.as_str(); let mut s = String::new(); - let timeout = match custom_timeout { - Some(t) => t, - None => DEFAULT_TCP_LISTENER_TIMEOUT, - }; let mut closure = || -> Result<(), WaitForBootError> { let listener = @@ -122,15 +118,18 @@ impl GuestNetworkConfig { .expect("Cannot add 'tcp_listener' event to epoll"); let mut events = [epoll::Event::new(epoll::Events::empty(), 0); 1]; loop { - let num_events = - match epoll::wait(epoll_fd, (timeout * 1000) as i32, &mut events[..]) { - Ok(num_events) => Ok(num_events), - Err(e) => match e.raw_os_error() { - Some(libc::EAGAIN) | Some(libc::EINTR) => continue, - _ => Err(e), - }, - } - .map_err(WaitForBootError::EpollWait)?; + let num_events = match epoll::wait( + epoll_fd, + (custom_timeout * 1000).try_into().unwrap(), + &mut events[..], + ) { + Ok(num_events) => Ok(num_events), + Err(e) => match e.raw_os_error() { + Some(libc::EAGAIN) | Some(libc::EINTR) => continue, + _ => Err(e), + }, + } + .map_err(WaitForBootError::EpollWait)?; if num_events == 0 { return Err(WaitForBootError::EpollWaitTimeout); } @@ -163,7 +162,7 @@ impl GuestNetworkConfig { let duration = start.elapsed(); eprintln!( "\n\n==== Start 'wait_vm_boot' (FAILED) ==== \ - \n\nduration =\"{duration:?}, timeout = {timeout}s\" \ + \n\nduration =\"{duration:?}, timeout = {custom_timeout}s\" \ \nlisten_addr=\"{listen_addr}\" \ \nexpected_guest_addr=\"{expected_guest_addr}\" \ \nmessage=\"{s}\" \ @@ -1085,7 +1084,17 @@ impl Guest { .map_err(Error::Parsing) } - pub fn wait_vm_boot(&self, custom_timeout: Option) -> Result<(), Error> { + fn default_boot_timeout(&self) -> u32 { + self.boot_timeout + } + + pub fn wait_vm_boot(&self) -> Result<(), Error> { + self.network + .wait_vm_boot(self.default_boot_timeout()) + .map_err(Error::WaitForBoot) + } + + pub fn wait_vm_boot_custom_timeout(&self, custom_timeout: u32) -> Result<(), Error> { self.network .wait_vm_boot(custom_timeout) .map_err(Error::WaitForBoot) @@ -1223,7 +1232,7 @@ impl Guest { ); } - pub fn reboot_linux(&self, current_reboot_count: u32, custom_timeout: Option) { + pub fn reboot_linux(&self, current_reboot_count: u32) { let list_boots_cmd = "sudo last | grep -c reboot"; let boot_count = self .ssh_command(list_boots_cmd) @@ -1235,7 +1244,7 @@ impl Guest { assert_eq!(boot_count, current_reboot_count + 1); self.ssh_command("sudo reboot").unwrap(); - self.wait_vm_boot(custom_timeout).unwrap(); + self.wait_vm_boot().unwrap(); let boot_count = self .ssh_command(list_boots_cmd) .unwrap() From f391a37a35b8c32dcb32458637a85495fd807235 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 19 Oct 2025 13:38:00 -0700 Subject: [PATCH 0513/1893] 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 --- scripts/run_integration_tests_x86_64.sh | 34 +---------------------- scripts/test-util.sh | 37 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 31d3965c20..2259140507 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -28,39 +28,7 @@ if [ ! -f "$WORKLOADS_DIR/CLOUDHV.fd" ]; then download_ovmf fi -FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2" -FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME" -FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME" -if [ ! -f "$FOCAL_OS_IMAGE" ]; then - pushd "$WORKLOADS_DIR" || exit - time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1 - popd || exit -fi - -FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.raw" -FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME" -if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then - pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1 - popd || exit -fi - -JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2" -JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME" -JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME" -if [ ! -f "$JAMMY_OS_IMAGE" ]; then - pushd "$WORKLOADS_DIR" || exit - time wget --quiet $JAMMY_OS_IMAGE_URL || exit 1 - popd || exit -fi - -JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.raw" -JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME" -if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then - pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -p -f qcow2 -O raw $JAMMY_OS_IMAGE_NAME $JAMMY_OS_RAW_IMAGE_NAME || exit 1 - popd || exit -fi +download_x86_guest_images JAMMY_OS_QCOW_ZLIB_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-zlib.qcow2" JAMMY_OS_QCOW_ZLIB_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_ZLIB_FILE_IMAGE_NAME" diff --git a/scripts/test-util.sh b/scripts/test-util.sh index 5c49dcae62..035084eefa 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -301,3 +301,40 @@ copy_to_image() { mount_and_exec "$IMG" "$MOUNT_DIR" /bin/bash -c "$COPY_COMMAND" return $? } + +# Download x86 guest images (Focal and Jammy) +download_x86_guest_images() { + FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2" + FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME" + FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME" + if [ ! -f "$FOCAL_OS_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1 + popd || exit + fi + + FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.raw" + FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME" + if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1 + popd || exit + fi + + JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2" + JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME" + JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME" + if [ ! -f "$JAMMY_OS_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time wget --quiet $JAMMY_OS_IMAGE_URL || exit 1 + popd || exit + fi + + JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.raw" + JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME" + if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then + pushd "$WORKLOADS_DIR" || exit + time qemu-img convert -p -f qcow2 -O raw $JAMMY_OS_IMAGE_NAME $JAMMY_OS_RAW_IMAGE_NAME || exit 1 + popd || exit + fi +} From 258f82602769fee0f6284a0321ba8d57210d8b6a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 17 Oct 2025 20:03:41 -0700 Subject: [PATCH 0514/1893] 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 --- Cargo.lock | 1 + cloud-hypervisor/tests/integration.rs | 15 ++++++ scripts/dev_cli.sh | 50 +++++++++++++++++++ scripts/run_integration_tests_cvm.sh | 33 ++++++++++++ .../run_integration_tests_live_migration.sh | 4 +- scripts/run_integration_tests_rate_limiter.sh | 4 +- scripts/run_integration_tests_x86_64.sh | 4 +- scripts/run_metrics.sh | 1 + scripts/sha1sums-x86_64 | 4 -- scripts/sha1sums-x86_64-common | 4 ++ scripts/test-util.sh | 2 + test_infra/Cargo.toml | 1 + test_infra/src/lib.rs | 44 +++++++++++++++- 13 files changed, 155 insertions(+), 12 deletions(-) create mode 100755 scripts/run_integration_tests_cvm.sh create mode 100644 scripts/sha1sums-x86_64-common diff --git a/Cargo.lock b/Cargo.lock index cbca77a69c..70f2dc43f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2122,6 +2122,7 @@ dependencies = [ "dirs", "epoll", "libc", + "rand", "serde_json", "ssh2", "thiserror 2.0.18", diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5093d05379..594a1140bd 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -12862,3 +12862,18 @@ mod fw_cfg { handle_child_output(r, &output); } } + +#[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] +mod common_cvm { + use vm_memory::GuestAddress; + + use crate::*; + #[test] + fn test_focal_simple_launch() { + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let mut guest = Guest::new(Box::new(disk_config)); + guest.vm_type = GuestVmType::Confidential; + guest.boot_timeout = DEFAULT_CVM_TCP_LISTENER_TIMEOUT; + _test_simple_launch(&guest) + } +} diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index d055ed7438..e537c499c2 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -28,6 +28,9 @@ CTR_CLH_ROOT_DIR="/cloud-hypervisor" CTR_CLH_CARGO_BUILT_DIR="${CTR_CLH_ROOT_DIR}/build" CTR_CLH_CARGO_TARGET="${CTR_CLH_CARGO_BUILT_DIR}/cargo_target" CTR_CLH_INTEGRATION_WORKLOADS="/root/workloads" +SRC_IGVM_FILES_PATH="/usr/share/cloud-hypervisor/cvm" +DEST_IGVM_FILES_PATH="$CLH_INTEGRATION_WORKLOADS/igvm_files" +CTR_IGVM_FILES_PATH="/igvm_files" # Container networking option CTR_CLH_NET="bridge" @@ -173,6 +176,23 @@ process_volumes_args() { done } +# Copy IGVM files to the workloads directory +# This is needed for the IGVM integration tests to run +# $1 - source path +# $2 - destination path +copy_igvm_files() { + src=$1 + dest=$2 + + if [ -d "$src" ]; then + say "Copying IGVM files from $src to $dest" + cp "$src"/* "$dest" + else + say_err "IGVM File path '$src' not found on host" + exit 1 + fi +} + cmd_help() { echo "" echo "Cloud Hypervisor $(basename "$0")" @@ -200,6 +220,7 @@ cmd_help() { echo " --integration-windows Run the Windows guest integration tests." echo " --integration-live-migration Run the live-migration integration tests." echo " --integration-rate-limiter Run the rate-limiter integration tests." + echo " --integration-cvm Run the Confidential VM integration tests." echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu" echo " --metrics Generate performance metrics" echo " --coverage Generate code coverage information" @@ -333,6 +354,7 @@ cmd_tests() { integration_windows=false integration_live_migration=false integration_rate_limiter=false + integration_cvm=false metrics=false coverage=false libc="gnu" @@ -351,6 +373,7 @@ cmd_tests() { "--integration-windows") { integration_windows=true; } ;; "--integration-live-migration") { integration_live_migration=true; } ;; "--integration-rate-limiter") { integration_rate_limiter=true; } ;; + "--integration-cvm") { integration_cvm=true; } ;; "--metrics") { metrics=true; } ;; "--coverage") { coverage=true; } ;; "--libc") @@ -449,6 +472,33 @@ cmd_tests() { dbus-run-session ./scripts/run_integration_tests_"$(uname -m)".sh "$@" || fix_dir_perms $? || exit $? fi + if [ "$integration_cvm" = true ]; then + mkdir -p "$DEST_IGVM_FILES_PATH" + copy_igvm_files "$SRC_IGVM_FILES_PATH" "$DEST_IGVM_FILES_PATH" + say "Running CVM integration tests for $target..." + $DOCKER_RUNTIME run \ + --workdir "$CTR_CLH_ROOT_DIR" \ + --rm \ + --privileged \ + --security-opt seccomp=unconfined \ + --ipc=host \ + --net="$CTR_CLH_NET" \ + --mount type=tmpfs,destination=/tmp \ + --volume /dev:/dev \ + --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ + --volume "$DEST_IGVM_FILES_PATH:$CTR_IGVM_FILES_PATH" \ + ${exported_volumes:+"$exported_volumes"} \ + --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ + --env USER="root" \ + --env BUILD_TARGET="$target" \ + --env RUSTFLAGS="$rustflags" \ + --env TARGET_CC="$target_cc" \ + --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ + --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ + "$CTR_IMAGE" \ + ./scripts/run_integration_tests_cvm.sh "$@" || fix_dir_perms $? || exit $? + fi + if [ "$integration_vfio" = true ]; then say "Running VFIO integration tests for $target..." $DOCKER_RUNTIME run \ diff --git a/scripts/run_integration_tests_cvm.sh b/scripts/run_integration_tests_cvm.sh new file mode 100755 index 0000000000..edf543fc88 --- /dev/null +++ b/scripts/run_integration_tests_cvm.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2048,SC2086,SC2154,SC1094 +set -x + +# shellcheck source=/dev/null +source "$HOME"/.cargo/env +source "$(dirname "${BASH_SOURCE[0]}")/test-util.sh" + +WORKLOADS_DIR="$HOME/workloads" +mkdir -p "$WORKLOADS_DIR" + +process_common_args "$@" + +test_features="--features mshv,igvm,sev_snp" +build_features="mshv,igvm,sev_snp" + +download_x86_guest_images +cp scripts/sha1sums-x86_64-common "$WORKLOADS_DIR" + +pushd "$WORKLOADS_DIR" || exit +if ! sha1sum sha1sums-x86_64-common --check; then + echo "sha1sum validation of images failed, remove invalid images to fix the issue." + exit 1 +fi +popd || exit + +cargo build --features $build_features --all --release --target "$BUILD_TARGET" + +export RUST_BACKTRACE=1 +cargo nextest run $test_features "common_cvm::$test_filter" -- ${test_binary_args[*]} +RES=$? + +exit $RES diff --git a/scripts/run_integration_tests_live_migration.sh b/scripts/run_integration_tests_live_migration.sh index f88beed65a..11cf59b41b 100755 --- a/scripts/run_integration_tests_live_migration.sh +++ b/scripts/run_integration_tests_live_migration.sh @@ -28,7 +28,7 @@ if [ -n "${MIGRATABLE_VERSION}" ]; then fi migratable_version=${MIGRATABLE_VERSION} fi -cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR" +cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR" FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2" FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME" @@ -48,7 +48,7 @@ if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then fi pushd "$WORKLOADS_DIR" || exit -if ! grep focal sha1sums-x86_64 | sha1sum --check; then +if ! grep focal sha1sums-x86_64-common | sha1sum --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi diff --git a/scripts/run_integration_tests_rate_limiter.sh b/scripts/run_integration_tests_rate_limiter.sh index e4e627426b..abdf161c9a 100755 --- a/scripts/run_integration_tests_rate_limiter.sh +++ b/scripts/run_integration_tests_rate_limiter.sh @@ -18,7 +18,7 @@ if [ "$hypervisor" = "mshv" ]; then test_features="--features mshv" fi -cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR" +cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR" JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2" JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME" @@ -38,7 +38,7 @@ if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then fi pushd "$WORKLOADS_DIR" || exit -if ! grep jammy sha1sums-x86_64 | sha1sum --check; then +if ! grep jammy sha1sums-x86_64-common | sha1sum --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 2259140507..80ac279349 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -18,7 +18,7 @@ if [ "$hypervisor" = "mshv" ]; then test_features="--features mshv" fi -cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR" +cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR" if [ ! -f "$WORKLOADS_DIR/hypervisor-fw" ]; then download_hypervisor_fw @@ -105,7 +105,7 @@ if [ ! -f "$ALPINE_INITRAMFS_IMAGE" ]; then fi pushd "$WORKLOADS_DIR" || exit -if ! sha1sum sha1sums-x86_64 --check; then +if ! sha1sum sha1sums-x86_64 sha1sums-x86_64-common --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi diff --git a/scripts/run_metrics.sh b/scripts/run_metrics.sh index 988d90aafe..8feeeecb7e 100755 --- a/scripts/run_metrics.sh +++ b/scripts/run_metrics.sh @@ -62,6 +62,7 @@ if ! grep focal sha1sums-"${TEST_ARCH}" | sha1sum --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi + popd || exit if [ "${TEST_ARCH}" == "aarch64" ]; then diff --git a/scripts/sha1sums-x86_64 b/scripts/sha1sums-x86_64 index e198816035..c49f00b266 100644 --- a/scripts/sha1sums-x86_64 +++ b/scripts/sha1sums-x86_64 @@ -1,7 +1,3 @@ d4a44acc6014d5f83dea1c625c43d677a95fa75f alpine-minirootfs-x86_64.tar.gz -f1eccdc5e1b515dbad294426ab081b47ebfb97c0 focal-server-cloudimg-amd64-custom-20210609-0.qcow2 -7f5a8358243a96adf61f5c20139b29f308f2c0e3 focal-server-cloudimg-amd64-custom-20210609-0.raw -5f10738920efb74f0bf854cadcd1b1fd544e49c8 jammy-server-cloudimg-amd64-custom-20241017-0.qcow2 -c1dfbe7abde400e675844568dbe9d3914222f6de jammy-server-cloudimg-amd64-custom-20241017-0.raw 540ac358429305d7aa94e15363665d1c9d845982 hypervisor-fw 4e96fd0914a44005d40707b2b0c7e829e4086bd5 CLOUDHV.fd diff --git a/scripts/sha1sums-x86_64-common b/scripts/sha1sums-x86_64-common new file mode 100644 index 0000000000..b7fb9923ca --- /dev/null +++ b/scripts/sha1sums-x86_64-common @@ -0,0 +1,4 @@ +f1eccdc5e1b515dbad294426ab081b47ebfb97c0 focal-server-cloudimg-amd64-custom-20210609-0.qcow2 +7f5a8358243a96adf61f5c20139b29f308f2c0e3 focal-server-cloudimg-amd64-custom-20210609-0.raw +5f10738920efb74f0bf854cadcd1b1fd544e49c8 jammy-server-cloudimg-amd64-custom-20241017-0.qcow2 +c1dfbe7abde400e675844568dbe9d3914222f6de jammy-server-cloudimg-amd64-custom-20241017-0.raw diff --git a/scripts/test-util.sh b/scripts/test-util.sh index 035084eefa..8958439330 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# shellcheck disable=SC1009,SC2048,SC2086,SC1073,SC1040,SC1072 +# shellcheck source=/dev/null set -x hypervisor="kvm" diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index e6ea592c39..ec5700a6c3 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -8,6 +8,7 @@ version = "0.1.0" dirs = { workspace = true } epoll = { workspace = true } libc = { workspace = true } +rand = "0.9.2" serde_json = { workspace = true } ssh2 = { version = "0.9.5", features = ["vendored-openssl"] } thiserror = { workspace = true } diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index d9a04bce9a..c62d478324 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -18,6 +18,7 @@ use std::str::FromStr; use std::time::Duration; use std::{env, fmt, fs, io, thread}; +use rand::RngCore; use serde_json::Value; use ssh2::Session; use thiserror::Error; @@ -75,6 +76,7 @@ pub struct GuestNetworkConfig { pub const DEFAULT_TCP_LISTENER_MESSAGE: &str = "booted"; pub const DEFAULT_TCP_LISTENER_PORT: u16 = 8000; pub const DEFAULT_TCP_LISTENER_TIMEOUT: u32 = 120; +pub const DEFAULT_CVM_TCP_LISTENER_TIMEOUT: u32 = 120; #[derive(Error, Debug)] pub enum WaitForBootError { @@ -891,6 +893,7 @@ pub struct Guest { pub boot_timeout: u32, pub kernel_path: Option, pub kernel_cmdline: Option, + pub console_type: Option, } // Return the next id that can be used for this guest. This is stored in a @@ -959,6 +962,7 @@ impl Guest { boot_timeout: DEFAULT_TCP_LISTENER_TIMEOUT, kernel_path: None, kernel_cmdline: None, + console_type: None, } } @@ -1481,8 +1485,20 @@ impl<'a> GuestCommand<'a> { } pub fn default_kernel_cmdline(&mut self) -> &mut Self { - if let Some(kernel) = &self.guest.kernel_path { - self.command.args(["--kernel", kernel]); + if self.guest.vm_type == GuestVmType::Confidential { + let console_str = if let Some(c) = &self.guest.console_type { + c.as_str() + } else { + "hvc0" + }; + let igvm = direct_igvm_boot_path(Some(console_str)) + .expect("IGVM boot file not found for console type: {console_str}"); + self.command.args(["--igvm", igvm.to_str().unwrap()]); + self.command + .args(["--host-data", generate_host_data().as_str()]); + self.command.args(["--platform", "sev_snp=on"]); + } else if let Some(kernel) = &self.guest.kernel_path { + self.command.args(["--kernel", kernel.as_str()]); if let Some(cmdline) = &self.guest.kernel_cmdline { self.command.args(["--cmdline", cmdline]); } @@ -1893,3 +1909,27 @@ pub enum GuestVmType { Regular, Confidential, } + +// Get the direct igvm boot file path based on the console type +fn direct_igvm_boot_path(console: Option<&str>) -> Option { + // get the default hvc0 igvm file if console string is not passed + let console_str = console.unwrap_or("hvc0"); + + if console_str != "hvc0" && console_str != "ttyS0" { + panic!("IGVM console should be hvc0 or ttyS0, got: {console_str}"); + } + + let igvm_filepath = format!("/igvm_files/linux-{console_str}.bin"); + if Path::new(&igvm_filepath).exists() { + Some(PathBuf::from(igvm_filepath)) + } else { + None + } +} + +// Generate a random 64-character hex string for host data +fn generate_host_data() -> String { + let mut bytes = [0u8; 32]; + rand::rng().fill_bytes(&mut bytes); + bytes.iter().map(|b| format!("{b:02x}")).collect() +} From 2c2f5d243143baf1c444a01418424e8827ba7193 Mon Sep 17 00:00:00 2001 From: Rowen-Ye Date: Fri, 6 Feb 2026 19:07:43 +0800 Subject: [PATCH 0515/1893] 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 --- vmm/src/memory_manager.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 8b0cdff6c1..2011e47684 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -1356,13 +1356,17 @@ impl MemoryManager { Ok(FileOffset::new(f, 0)) } - fn open_backing_file(backing_file: &PathBuf, file_offset: u64) -> Result { + fn open_backing_file( + backing_file: &PathBuf, + file_offset: u64, + shared: bool, + ) -> Result { if backing_file.is_dir() { Err(Error::DirectoryAsBackingFileForMemory) } else { let f = OpenOptions::new() .read(true) - .write(true) + .write(shared) .open(backing_file) .map_err(Error::SharedFileCreate)?; @@ -1397,7 +1401,7 @@ impl MemoryManager { } else { mmap_flags |= libc::MAP_PRIVATE; } - Some(Self::open_backing_file(backing_file, file_offset)?) + Some(Self::open_backing_file(backing_file, file_offset, shared)?) } else if shared || hugepages { // For hugepages we must also MAP_SHARED otherwise we will trigger #4805 // because the MAP_PRIVATE will trigger CoW against the backing file with From 1e0eba60af85cde427eec65be699769fb2222d55 Mon Sep 17 00:00:00 2001 From: Champ-Goblem Date: Fri, 6 Feb 2026 17:13:07 +0000 Subject: [PATCH 0516/1893] 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 --- vmm/src/memory_manager.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 2011e47684..03b7b8a837 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -25,9 +25,7 @@ use devices::ioapic; #[cfg(target_arch = "aarch64")] use hypervisor::HypervisorVmError; use libc::_SC_NPROCESSORS_ONLN; -#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] -use log::debug; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracer::trace_scoped; @@ -1490,17 +1488,20 @@ impl MemoryManager { }); } - if region.file_offset().is_none() && thp { - info!( - "Anonymous mapping at 0x{:x} (size = 0x{:x})", - region.as_ptr() as u64, - size - ); + info!( + "RAM region mapping at 0x{:x} (size = 0x{:x})", + region.as_ptr() as u64, + size + ); + + if thp && !hugepages { // SAFETY: FFI call with correct arguments let ret = unsafe { libc::madvise(region.as_ptr() as _, size, libc::MADV_HUGEPAGE) }; if ret != 0 { let e = io::Error::last_os_error(); warn!("Failed to mark pages as THP eligible: {e}"); + } else { + debug!("Successfully marked pages as THP eligible"); } } From 7314a77d43f485ea49dd5ea8881c23e54d1fb8b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:54:11 +0000 Subject: [PATCH 0517/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index eeae1baeb3..a42371a9fb 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.43.2 + - uses: crate-ci/typos@v1.43.3 From e3a2bf0870ab70095563285b15ee6f0bb39eafe4 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Feb 2026 14:25:12 +0100 Subject: [PATCH 0518/1893] build: Update mshv crates to 0.6.7 Release notes: https://github.com/rust-vmm/mshv/pull/307 Signed-off-by: Anatol Belski --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70f2dc43f1..d7aa14e243 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1321,9 +1321,9 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mshv-bindings" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7752a74e9b4f95f20c5eec69ee7cf25aee5da6d87d18574254b44f22940151fb" +checksum = "3cbfd4f32d185152003679339751839da77c17e18fa8882a11051a236f841426" dependencies = [ "libc", "num_enum", @@ -1335,9 +1335,9 @@ dependencies = [ [[package]] name = "mshv-ioctls" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e058608d09f2f8b106b06e6c58a09aa44915dd6a36cd4142d3a7d32e59c1fb" +checksum = "f035616abe1e4cbc026a1a8094ff8d3900f5063fe6608309098bc745926fdfd8" dependencies = [ "libc", "mshv-bindings", diff --git a/Cargo.toml b/Cargo.toml index 750506096d..c07dac9a5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,8 +49,8 @@ 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.6" -mshv-ioctls = "0.6.6" +mshv-bindings = "0.6.7" +mshv-ioctls = "0.6.7" seccompiler = "0.5.0" vfio-bindings = { version = "0.6.0", default-features = false } vfio-ioctls = { version = "0.5.1", default-features = false } From a00189cf7278582e559b189967d0f262077a824b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 19 Jan 2026 19:15:12 +0100 Subject: [PATCH 0519/1893] 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 --- hypervisor/src/lib.rs | 1 + hypervisor/src/mshv/mod.rs | 4 ++++ vmm/src/lib.rs | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 77e1f9a438..7f8084d062 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -171,6 +171,7 @@ pub struct HypervisorVmConfig { #[cfg(feature = "sev_snp")] pub mem_size: u64, pub nested: bool, + pub smt_enabled: bool, } #[derive(Copy, Clone)] diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 0c8d2632c2..b36b306a93 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -307,6 +307,10 @@ impl hypervisor::Hypervisor for MshvHypervisor { .__bindgen_anon_1 .set_nested_virt_support(1u64); } + + if _config.smt_enabled { + create_args.pt_flags |= 1 << MSHV_PT_BIT_SMT_ENABLED_GUEST; + } } // Modified feature bit fields are written back to create_args for i in 0..create_args.pt_num_cpu_fbanks { diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 6917e005e7..41a2c3383a 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -221,6 +221,11 @@ impl From<&VmConfig> for hypervisor::HypervisorVmConfig { #[cfg(feature = "sev_snp")] mem_size: _value.memory.total_size(), nested: _value.cpus.nested, + smt_enabled: _value + .cpus + .topology + .as_ref() + .is_some_and(|t| t.threads_per_core > 1), } } } From 7157e97083874b8736b469f8122ad49cb8e9e522 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 01:59:20 +0000 Subject: [PATCH 0520/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index a42371a9fb..4ab462f329 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.43.3 + - uses: crate-ci/typos@v1.43.4 From 2c49f4f4f328e74108f90b7e95d9d5983a873a77 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 10 Feb 2026 03:38:05 +0000 Subject: [PATCH 0521/1893] 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 --- hypervisor/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 7f8084d062..3d919e45ce 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -225,6 +225,8 @@ macro_rules! set_x86_64_reg { StandardRegisters::Kvm(s) => s.$reg_name = val, #[cfg(any(feature = "mshv", feature = "mshv_emulator"))] StandardRegisters::Mshv(s) => s.$reg_name = val, + #[allow(unreachable_patterns)] + _ => { let _ = val; unreachable!("no x86_64 register backend available") }, } } } @@ -243,6 +245,8 @@ macro_rules! get_x86_64_reg { StandardRegisters::Kvm(s) => s.$reg_name, #[cfg(any(feature = "mshv", feature = "mshv_emulator"))] StandardRegisters::Mshv(s) => s.$reg_name, + #[allow(unreachable_patterns)] + _ => unreachable!("no x86_64 register backend available"), } } } From c569a4cbd4f0ad00a48e0e8a2801abd9333d4e50 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 7 Feb 2026 00:06:39 +0100 Subject: [PATCH 0522/1893] 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 --- block/src/qcow/mod.rs | 46 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 300cd8bc3f..e9f45cbf1f 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -774,6 +774,7 @@ impl BackingFileOps for RawFile { /// Backing file wrapper struct BackingFile { inner: Box, + virtual_size: u64, } impl BackingFile { @@ -804,22 +805,48 @@ impl BackingFile { None => detect_image_type(&mut raw_file)?, }; - let inner: Box = match backing_format { - ImageType::Raw => Box::new(raw_file), + let (inner, virtual_size): (Box, u64) = match backing_format { + ImageType::Raw => { + let size = raw_file + .seek(SeekFrom::End(0)) + .map_err(Error::BackingFileIo)?; + raw_file.rewind().map_err(Error::BackingFileIo)?; + (Box::new(raw_file), size) + } ImageType::Qcow2 => { let backing_qcow = QcowFile::from_with_nesting_depth(raw_file, max_nesting_depth - 1) .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; - Box::new(backing_qcow) + let size = backing_qcow.virtual_size(); + (Box::new(backing_qcow), size) } }; - Ok(Some(Self { inner })) + Ok(Some(Self { + inner, + virtual_size, + })) } + /// Read from backing file, returning zeros for any portion beyond backing file size. #[inline] fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { - self.inner.read_at(address, buf) + if address >= self.virtual_size { + // Entire read is beyond backing file + buf.fill(0); + return Ok(()); + } + + let available = (self.virtual_size - address) as usize; + if available >= buf.len() { + // Entire read is within backing file + self.inner.read_at(address, buf) + } else { + // Partial read, fill the rest with zeroes + self.inner.read_at(address, &mut buf[..available])?; + buf[available..].fill(0); + Ok(()) + } } } @@ -827,6 +854,7 @@ impl Clone for BackingFile { fn clone(&self) -> Self { Self { inner: self.inner.clone_box(), + virtual_size: self.virtual_size, } } } @@ -1108,8 +1136,12 @@ impl QcowFile { } pub fn set_backing_file(&mut self, backing: Option>) { - self.backing_file = backing.map(|b| BackingFile { - inner: Box::new(*b), + self.backing_file = backing.map(|b| { + let virtual_size = b.virtual_size(); + BackingFile { + inner: Box::new(*b), + virtual_size, + } }); } From 279344800ee366c8d3d738b60072373d8c0eb49b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 7 Feb 2026 00:06:57 +0100 Subject: [PATCH 0523/1893] 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 --- block/src/qcow/mod.rs | 60 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index e9f45cbf1f..75d18f8520 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -3288,6 +3288,66 @@ mod unit_tests { }); } + #[test] + fn read_beyond_backing_file_returns_zeros() { + let backing_temp = TempFile::new().unwrap(); + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + let backing_size = 1024 * 1024; + + { + let backing_raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); + let mut backing_qcow = QcowFile::new(backing_raw, 3, backing_size).unwrap(); + let data = b"BACKING_DATA"; + backing_qcow.rewind().unwrap(); + backing_qcow.write_all(data).unwrap(); + let boundary_data = [0xAAu8; 512]; + backing_qcow + .seek(SeekFrom::Start(backing_size - 512)) + .unwrap(); + backing_qcow.write_all(&boundary_data).unwrap(); + backing_qcow.flush().unwrap(); + } + + let overlay_file = TempFile::new().unwrap(); + let overlay_raw = RawFile::new(overlay_file.into_file(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Qcow2), + }; + let overlay_size = backing_size * 2; // 2x the backing size + let mut overlay = + QcowFile::new_from_backing(overlay_raw, 3, overlay_size, &backing_config).unwrap(); + + assert_eq!(overlay.virtual_size(), overlay_size); + + let mut buf = vec![0u8; 12]; + overlay.rewind().unwrap(); + overlay.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"BACKING_DATA"); + + let offset_beyond = backing_size + 4096; + let mut beyond_buf = vec![0xFFu8; 4096]; + overlay.seek(SeekFrom::Start(offset_beyond)).unwrap(); + overlay.read_exact(&mut beyond_buf).unwrap(); + assert!( + beyond_buf.iter().all(|&b| b == 0), + "Read beyond backing file should return zeros" + ); + + let offset_at_boundary = backing_size - 512; + let mut boundary_buf = vec![0xFFu8; 1024]; // 512 in backing, 512 beyond + overlay.seek(SeekFrom::Start(offset_at_boundary)).unwrap(); + overlay.read_exact(&mut boundary_buf).unwrap(); + assert!( + boundary_buf[..512].iter().all(|&b| b == 0xAA), + "Portion within backing file should contain backing data" + ); + assert!( + boundary_buf[512..].iter().all(|&b| b == 0), + "Portion beyond backing file should be zeros" + ); + } + #[test] fn write_zeroes_read() { with_basic_file(&valid_header_v3(), |disk_file: RawFile| { From a702bf1d104e503ff023d911f120e5afd220fb24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 02:14:40 +0000 Subject: [PATCH 0524/1893] 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] Signed-off-by: Rob Bradford --- Cargo.lock | 130 ++++++++++++++-------- Cargo.toml | 8 +- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 243 +++++++++++++++++++++++++++++++++++++----- fuzz/Cargo.toml | 2 +- net_util/Cargo.toml | 2 +- test_infra/Cargo.toml | 2 +- test_infra/src/lib.rs | 2 +- 8 files changed, 312 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7aa14e243..753c53cc0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -81,14 +81,14 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "api_client" @@ -385,20 +385,31 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.0", +] + [[package]] name = "clap" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -470,6 +481,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc-any" version = "2.5.0" @@ -597,7 +617,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -679,7 +699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -737,9 +757,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -922,13 +942,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd66c162c2a0609c0507f49814877c621d00cb85978e6fbbf04e88b8048c07c8" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" dependencies = [ "cfg-if", "libc", "r-efi", + "rand_core 0.10.0", "wasip2", "wasip3", ] @@ -1113,9 +1134,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" dependencies = [ "jiff-static", "log", @@ -1126,9 +1147,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" dependencies = [ "proc-macro2", "quote", @@ -1203,9 +1224,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" [[package]] name = "libredox" @@ -1281,9 +1302,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" @@ -1357,7 +1378,7 @@ name = "net_util" version = "0.1.0" dependencies = [ "epoll", - "getrandom 0.4.0", + "getrandom 0.4.1", "libc", "log", "net_gen", @@ -1773,7 +1794,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.1", + "rand_core 0.10.0", ] [[package]] @@ -1783,7 +1815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", ] [[package]] @@ -1795,6 +1827,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "range_map_vec" version = "0.2.0" @@ -1834,9 +1872,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1846,9 +1884,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1857,9 +1895,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "remain" @@ -1894,7 +1932,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -2094,15 +2132,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.24.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -2122,7 +2160,7 @@ dependencies = [ "dirs", "epoll", "libc", - "rand", + "rand 0.10.0", "serde_json", "ssh2", "thiserror 2.0.18", @@ -2272,9 +2310,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-xid" @@ -2296,7 +2334,7 @@ checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", - "rand", + "rand 0.9.2", "serde_core", "wasm-bindgen", ] @@ -2993,18 +3031,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -3013,9 +3051,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index c07dac9a5c..3005f232b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,24 +74,24 @@ serde_json = "1.0.149" serde_with = { version = "3.16.1", default-features = false } # other crates -anyhow = "1.0.99" +anyhow = "1.0.101" bitflags = "2.10.0" byteorder = "1.5.0" cfg-if = "1.0.4" -clap = "4.5.56" +clap = "4.5.57" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" epoll = "4.4.0" flume = "0.12.0" itertools = "0.14.0" -libc = "0.2.180" +libc = "0.2.181" log = "0.4.29" signal-hook = "0.4.3" thiserror = "2.0.18" uuid = { version = "1.20.0" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.37", default-features = false } +zerocopy = { version = "0.8.39", default-features = false } [workspace.lints.clippy] # Any clippy lint (group) in alphabetical order: diff --git a/devices/Cargo.toml b/devices/Cargo.toml index ab1a70cd86..0414f13d2a 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.37", features = [ +zerocopy = { version = "0.8.39", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index b757f7dfe3..55be75cd53 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "arbitrary" @@ -190,18 +190,18 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -427,9 +427,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -453,6 +453,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "futures-core" version = "0.3.31" @@ -514,12 +520,40 @@ dependencies = [ "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hypervisor" version = "0.1.0" @@ -556,6 +590,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -569,7 +609,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -653,11 +695,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.180" +version = "0.2.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" [[package]] name = "libfuzzer-sys" @@ -701,9 +749,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "micro_http" @@ -726,9 +774,9 @@ dependencies = [ [[package]] name = "mshv-bindings" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7752a74e9b4f95f20c5eec69ee7cf25aee5da6d87d18574254b44f22940151fb" +checksum = "3cbfd4f32d185152003679339751839da77c17e18fa8882a11051a236f841426" dependencies = [ "libc", "num_enum", @@ -750,7 +798,7 @@ name = "net_util" version = "0.1.0" dependencies = [ "epoll", - "getrandom 0.3.4", + "getrandom 0.4.1", "libc", "log", "net_gen", @@ -876,6 +924,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -981,6 +1039,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -1208,9 +1272,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" @@ -1470,6 +1540,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1528,6 +1607,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1579,21 +1692,103 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "zerocopy" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -1602,9 +1797,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" [[package]] name = "zstd" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 6a0892244f..931b7862c8 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.180" +libc = "0.2.181" libfuzzer-sys = "0.4.10" linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml index 780389e4d2..e9a8f5badf 100644 --- a/net_util/Cargo.toml +++ b/net_util/Cargo.toml @@ -6,7 +6,7 @@ version = "0.1.0" [dependencies] epoll = { workspace = true } -getrandom = "0.4.0" +getrandom = "0.4.1" libc = { workspace = true } log = { workspace = true } net_gen = { path = "../net_gen" } diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index ec5700a6c3..6c53e9ca0f 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -8,7 +8,7 @@ version = "0.1.0" dirs = { workspace = true } epoll = { workspace = true } libc = { workspace = true } -rand = "0.9.2" +rand = "0.10.0" serde_json = { workspace = true } ssh2 = { version = "0.9.5", features = ["vendored-openssl"] } thiserror = { workspace = true } diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index c62d478324..3ab550870e 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -18,7 +18,7 @@ use std::str::FromStr; use std::time::Duration; use std::{env, fmt, fs, io, thread}; -use rand::RngCore; +use rand::Rng; use serde_json::Value; use ssh2::Session; use thiserror::Error; From 509832298b6865365b00bda88722e76e41ce7f41 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 8 Feb 2026 21:14:28 +0000 Subject: [PATCH 0525/1893] 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 --- block/src/lib.rs | 1 + block/src/qcow_sync.rs | 17 +++++++--- cloud-hypervisor/tests/integration.rs | 49 ++++++++++++++++++++------- vmm/src/config.rs | 13 +++++-- vmm/src/device_manager.rs | 6 +++- vmm/src/vm_config.rs | 2 ++ 6 files changed, 68 insertions(+), 20 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 34c96eea7f..72210302a1 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -788,6 +788,7 @@ pub trait AsyncAdaptor { } } +#[derive(PartialEq, Eq, Debug)] pub enum ImageType { FixedVhd, Qcow2, diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index e76c07c15f..d802291c1d 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -29,10 +29,19 @@ pub struct QcowDiskSync { } impl QcowDiskSync { - pub fn new(file: File, direct_io: bool) -> QcowResult { - Ok(QcowDiskSync { - qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)), - }) + pub fn new(file: File, direct_io: bool, backing_files: bool) -> QcowResult { + if backing_files { + Ok(QcowDiskSync { + qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)), + }) + } else { + Ok(QcowDiskSync { + qcow_file: Arc::new(Mutex::new(QcowFile::from_with_nesting_depth( + RawFile::new(file, direct_io), + 0, + )?)), + }) + } } } diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 594a1140bd..5566caf986 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -3422,6 +3422,7 @@ mod common_parallel { disable_io_uring: bool, disable_aio: bool, verify_os_disk: bool, + backing_files: bool, ) { let disk_config = UbuntuDiskConfig::new(image_name.to_string()); let guest = Guest::new(Box::new(disk_config)); @@ -3448,8 +3449,9 @@ mod common_parallel { .args([ "--disk", format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + "path={},backing_files={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), + if backing_files { "on"} else {"off"} ) .as_str(), format!( @@ -3528,17 +3530,17 @@ mod common_parallel { #[test] fn test_virtio_block_io_uring() { - _test_virtio_block(FOCAL_IMAGE_NAME, false, true, false); + _test_virtio_block(FOCAL_IMAGE_NAME, false, true, false, false); } #[test] fn test_virtio_block_aio() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, false, false); + _test_virtio_block(FOCAL_IMAGE_NAME, true, false, false, false); } #[test] fn test_virtio_block_sync() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, true, false); + _test_virtio_block(FOCAL_IMAGE_NAME, true, true, false, false); } fn run_qemu_img(path: &std::path::Path, args: &[&str]) -> std::process::Output { @@ -3768,22 +3770,28 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2, false, false, true); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2, false, false, true, false); } #[test] fn test_virtio_block_qcow2_zlib() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZLIB, false, false, true); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZLIB, false, false, true, false); } #[test] fn test_virtio_block_qcow2_zstd() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZSTD, false, false, true); + _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZSTD, false, false, true, false); } #[test] fn test_virtio_block_qcow2_backing_zstd_file() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE, false, false, true); + _test_virtio_block( + JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE, + false, + false, + true, + true, + ); } #[test] @@ -3793,12 +3801,19 @@ mod common_parallel { false, false, true, + true, ); } #[test] fn test_virtio_block_qcow2_backing_raw_file() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE, false, false, true); + _test_virtio_block( + JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE, + false, + false, + true, + true, + ); } /// Configuration for QCOW2 multiqueue test image setup @@ -3873,7 +3888,15 @@ mod common_parallel { "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ), - &format!("path={},num_queues=8", test_image_path.to_str().unwrap()), + &format!( + "path={},num_queues=8,backing_files={}", + test_image_path.to_str().unwrap(), + if initial_backing_checksum.is_some() { + "on" + } else { + "off" + }, + ), ]) .default_net() .capture_output() @@ -4491,7 +4514,7 @@ mod common_parallel { .output() .expect("Expect generating VHD image from RAW image"); - _test_virtio_block(FOCAL_IMAGE_NAME_VHD, false, false, false); + _test_virtio_block(FOCAL_IMAGE_NAME_VHD, false, false, false, false); } #[test] @@ -4515,7 +4538,7 @@ mod common_parallel { .output() .expect("Expect generating dynamic VHDx image from RAW image"); - _test_virtio_block(FOCAL_IMAGE_NAME_VHDX, false, false, true); + _test_virtio_block(FOCAL_IMAGE_NAME_VHDX, false, false, true, false); } #[test] diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 78d6f9f1e1..e1f9213d3c 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1093,7 +1093,7 @@ impl DiskConfig { ops_size=,ops_one_time_burst=,ops_refill_time=,\ id=,pci_segment=,rate_limit_group=,\ queue_affinity=,\ - serial="; + serial=,backing_files=on|off"; pub fn parse(disk: &str) -> Result { let mut parser = OptionParser::new(); @@ -1118,7 +1118,8 @@ impl DiskConfig { .add("pci_segment") .add("serial") .add("rate_limit_group") - .add("queue_affinity"); + .add("queue_affinity") + .add("backing_files"); parser.parse(disk).map_err(Error::ParseDisk)?; let path = parser.get("path").map(PathBuf::from); @@ -1203,6 +1204,12 @@ impl DiskConfig { }) .collect() }); + let backing_files = parser + .convert::("backing_files") + .map_err(Error::ParseDisk)? + .unwrap_or(Toggle(false)) + .0; + let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { Some(TokenBucketConfig { size: bw_size, @@ -1247,6 +1254,7 @@ impl DiskConfig { pci_segment, serial, queue_affinity, + backing_files, }) } @@ -3414,6 +3422,7 @@ mod unit_tests { pci_segment: 0, serial: None, queue_affinity: None, + backing_files: false, } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 6d465047b1..9b2a32455f 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2657,6 +2657,10 @@ impl DeviceManager { let image_type = detect_image_type(&mut file).map_err(DeviceManagerError::DetectImageType)?; + if image_type != ImageType::Qcow2 && disk_cfg.backing_files { + warn!("Enabling backing_files option only applies for QCOW2 files"); + } + let image = match image_type { ImageType::FixedVhd => { // Use asynchronous backend relying on io_uring if the @@ -2710,7 +2714,7 @@ impl DeviceManager { ImageType::Qcow2 => { info!("Using synchronous QCOW2 disk file"); Box::new( - QcowDiskSync::new(file, disk_cfg.direct) + QcowDiskSync::new(file, disk_cfg.direct, disk_cfg.backing_files) .map_err(DeviceManagerError::CreateQcowDiskSync)?, ) as Box } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 9c28e536da..a71cca7be0 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -284,6 +284,8 @@ pub struct DiskConfig { pub serial: Option, #[serde(default)] pub queue_affinity: Option>, + #[serde(default)] + pub backing_files: bool, } impl ApplyLandlock for DiskConfig { From 97c5d837abde6eaeaaeb58e7bd6ab728aeeac135 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Mon, 9 Feb 2026 18:39:06 -0800 Subject: [PATCH 0526/1893] 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 --- scripts/create-cloud-init.sh | 39 +++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/scripts/create-cloud-init.sh b/scripts/create-cloud-init.sh index 6ff418fe96..5724c34c5a 100755 --- a/scripts/create-cloud-init.sh +++ b/scripts/create-cloud-init.sh @@ -1,8 +1,37 @@ #!/usr/bin/env bash set -ex -rm -f /tmp/ubuntu-cloudinit.img -mkdosfs -n CIDATA -C /tmp/ubuntu-cloudinit.img 8192 -mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/local/user-data :: -mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/local/meta-data :: -mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/local/network-config :: +usage() { + echo "Usage: $0 [-o|--output ]" + echo "" + echo "Options:" + echo " -o, --output Specify output file path (default: /tmp/ubuntu-cloudinit.img)" + echo " -h, --help Show this help message" +} + +OUTPUT_FILE=/tmp/ubuntu-cloudinit.img + +while [ "$1" != "" ]; do + echo "Processing argument: $1" + case $1 in + -o | --output) + OUTPUT_FILE=$2 + shift # Remove argument (-o) name from `$@` + shift # Remove argument value (file path) from `$@` + ;; + -h | --help) + usage # run usage function on help + exit 0 + ;; + *) + usage # run usage function if wrong argument provided + exit 1 + ;; + esac +done + +rm -f "$OUTPUT_FILE" +mkdosfs -n CIDATA -C "$OUTPUT_FILE" 8192 +mcopy -oi "$OUTPUT_FILE" -s test_data/cloud-init/ubuntu/local/user-data :: +mcopy -oi "$OUTPUT_FILE" -s test_data/cloud-init/ubuntu/local/meta-data :: +mcopy -oi "$OUTPUT_FILE" -s test_data/cloud-init/ubuntu/local/network-config :: From bf6f0f8352f9e414d008eef66ce86338ba310f83 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Mon, 9 Feb 2026 03:29:10 +0000 Subject: [PATCH 0527/1893] 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 --- virtio-devices/src/vsock/packet.rs | 212 +++++++++++++++++++++-------- 1 file changed, 156 insertions(+), 56 deletions(-) diff --git a/virtio-devices/src/vsock/packet.rs b/virtio-devices/src/vsock/packet.rs index 65d41b06bc..57218a5b87 100644 --- a/virtio-devices/src/vsock/packet.rs +++ b/virtio-devices/src/vsock/packet.rs @@ -3,14 +3,13 @@ // //! `VsockPacket` provides a thin wrapper over the buffers exchanged via virtio queues. -//! There are two components to a vsock packet, each using its own descriptor in a -//! virtio queue: +//! There are two components to a vsock packet, each described by a virtio descriptor chain: //! - the packet header; and //! - the packet data/buffer. //! //! There is a 1:1 relation between descriptor chains and packets: the first (chain head) holds -//! the header, and an optional second descriptor holds the data. The second descriptor is only -//! present for data packets (VSOCK_OP_RW). +//! the header, and the remaining descriptors (if any) hold the data. The data descriptors are +//! only present for data packets (VSOCK_OP_RW). //! //! `VsockPacket` wraps these two buffers and provides direct access to the data stored //! in guest memory. This is done to avoid unnecessarily copying data from guest memory @@ -91,17 +90,26 @@ const HDROFF_BUF_ALLOC: usize = 36; // we have successfully written to a backing Unix socket. const HDROFF_FWD_CNT: usize = 40; +/// The packet data buffer, which may be either: +/// - a borrowed slice of guest memory, if the packet data is stored in one contiguous buffer +/// described by a single virtq descriptor; +/// - an owned, linear buffer, if the packet data is stored in multiple buffers described by +/// multiple virtq descriptors. +enum PacketBuffer { + Borrowed { ptr: *mut u8, len: usize }, + Owned(Box<[u8]>), +} + /// The vsock packet, implemented as a wrapper over a virtq descriptor chain: /// - the chain head, holding the packet header; and -/// - (an optional) data/buffer descriptor, only present for data packets (VSOCK_OP_RW). +/// - (optional) buffer, only present for data packets (VSOCK_OP_RW). /// pub struct VsockPacket { // We still hold the header address in guest memory. We need to write back the modified // header in RX buffers. guest_hdr_addr: GuestAddress, hdr: [u8; VSOCK_PKT_HDR_SIZE], - buf: Option<*mut u8>, - buf_size: usize, + buf: Option, } impl VsockPacket { @@ -150,7 +158,6 @@ impl VsockPacket { guest_hdr_addr, hdr, buf: None, - buf_size: 0, }; // No point looking for a data/buffer descriptor, if the packet is zero-length. @@ -164,44 +171,85 @@ impl VsockPacket { return Err(VsockError::InvalidPktLen(pkt.len())); } - // Prior to Linux v6.3 there are two descriptors - if head.has_next() { - let buf_desc = desc_chain.next().ok_or(VsockError::BufDescMissing)?; + // For small packets, the data may be stored in the same descriptor as the header. + if !head.has_next() { + let buf_size: usize = head.len() as usize - VSOCK_PKT_HDR_SIZE; + let buf_ptr = get_host_address_range( + desc_chain.memory(), + head.addr() + .checked_add(VSOCK_PKT_HDR_SIZE as u64) + .unwrap() + .translate_gva(access_platform, buf_size), + buf_size, + ) + .ok_or(VsockError::GuestMemory)?; + pkt.buf = Some(PacketBuffer::Borrowed { + ptr: buf_ptr, + len: buf_size, + }); + + return Ok(pkt); + } - // TX data should be read-only. - if buf_desc.is_write_only() { - return Err(VsockError::UnreadableDescriptor); + // We have separate header and data descriptors. + let buf_desc = desc_chain.next().ok_or(VsockError::BufDescMissing)?; + + // TX data should be read-only. + if buf_desc.is_write_only() { + return Err(VsockError::UnreadableDescriptor); + } + + if buf_desc.has_next() { + // Multiple data descriptors -- copy into a linear buffer. + let total_len = pkt.len() as usize; + let mut owned = vec![0u8; total_len]; + let mut offset = 0usize; + let mut cur_desc = Some(buf_desc); + + while let Some(desc) = cur_desc { + if desc.is_write_only() { + return Err(VsockError::UnreadableDescriptor); + } + + let desc_len = desc.len() as usize; + if desc_len > 0 && offset < total_len { + let to_copy = std::cmp::min(desc_len, total_len - offset); + let desc_addr = desc.addr().translate_gva(access_platform, desc_len); + desc_chain + .memory() + .read_slice(&mut owned[offset..offset + to_copy], desc_addr) + .map_err(|_| VsockError::GuestMemory)?; + offset += to_copy; + } + + cur_desc = if desc.has_next() { + Some(desc_chain.next().ok_or(VsockError::BufDescMissing)?) + } else { + None + }; } + if offset < total_len { + return Err(VsockError::BufDescTooSmall); + } + pkt.buf = Some(PacketBuffer::Owned(owned.into_boxed_slice())); + } else { // The data buffer should be large enough to fit the size of the data, as described by // the header descriptor. if buf_desc.len() < pkt.len() { return Err(VsockError::BufDescTooSmall); } let buf_size = buf_desc.len() as usize; - pkt.buf_size = buf_size; - pkt.buf = Some( - get_host_address_range( - desc_chain.memory(), - buf_desc.addr().translate_gva(access_platform, buf_size), - pkt.buf_size, - ) - .ok_or(VsockError::GuestMemory)?, - ); - } else { - let buf_size: usize = head.len() as usize - VSOCK_PKT_HDR_SIZE; - pkt.buf_size = buf_size; - pkt.buf = Some( - get_host_address_range( - desc_chain.memory(), - head.addr() - .checked_add(VSOCK_PKT_HDR_SIZE as u64) - .unwrap() - .translate_gva(access_platform, buf_size), - buf_size, - ) - .ok_or(VsockError::GuestMemory)?, - ); + let buf_ptr = get_host_address_range( + desc_chain.memory(), + buf_desc.addr().translate_gva(access_platform, buf_size), + buf_size, + ) + .ok_or(VsockError::GuestMemory)?; + pkt.buf = Some(PacketBuffer::Borrowed { + ptr: buf_ptr, + len: buf_size, + }); } Ok(pkt) @@ -252,26 +300,33 @@ impl VsockPacket { let buf_desc = desc_chain.next().ok_or(VsockError::BufDescMissing)?; let buf_size = buf_desc.len() as usize; + // TODO: We still assume that there are at most two descriptors. We should probably + // support multi-descriptor RX packets as well, like we do for TX. This means we should + // add a function to commit the owned buffer back to guest memory. + if buf_desc.has_next() { + return Err(VsockError::BufDescTooSmall); + } + Ok(Self { guest_hdr_addr, hdr, - buf: Some( - get_host_address_range( + buf: Some(PacketBuffer::Borrowed { + ptr: get_host_address_range( desc_chain.memory(), buf_desc.addr().translate_gva(access_platform, buf_size), buf_size, ) .ok_or(VsockError::GuestMemory)?, - ), - buf_size, + len: buf_size, + }), }) } else { let buf_size: usize = head.len() as usize - VSOCK_PKT_HDR_SIZE; Ok(Self { guest_hdr_addr, hdr, - buf: Some( - get_host_address_range( + buf: Some(PacketBuffer::Borrowed { + ptr: get_host_address_range( desc_chain.memory(), head.addr() .checked_add(VSOCK_PKT_HDR_SIZE as u64) @@ -280,8 +335,8 @@ impl VsockPacket { buf_size, ) .ok_or(VsockError::GuestMemory)?, - ), - buf_size, + len: buf_size, + }), }) } } @@ -320,11 +375,14 @@ impl VsockPacket { /// (and often is) larger than the length of the packet data. The packet data length /// is stored in the packet header, and accessible via `VsockPacket::len()`. pub fn buf(&self) -> Option<&[u8]> { - self.buf.map(|ptr| { - // SAFETY: bound checks have already been performed when creating the packet - // from the virtq descriptor. - unsafe { std::slice::from_raw_parts(ptr as *const u8, self.buf_size) } - }) + match self.buf.as_ref()? { + PacketBuffer::Owned(owned) => Some(owned), + PacketBuffer::Borrowed { ptr, len } => { + // SAFETY: bound checks have already been performed when creating the packet + // from the virtq descriptor. + Some(unsafe { std::slice::from_raw_parts(*ptr as *const u8, *len) }) + } + } } /// Provides in-place, byte-slice, mutable access to the vsock packet data buffer. @@ -335,11 +393,14 @@ impl VsockPacket { /// (and often is) larger than the length of the packet data. The packet data length /// is stored in the packet header, and accessible via `VsockPacket::len()`. pub fn buf_mut(&mut self) -> Option<&mut [u8]> { - self.buf.map(|ptr| { - // SAFETY: bound checks have already been performed when creating the packet - // from the virtq descriptor. - unsafe { std::slice::from_raw_parts_mut(ptr, self.buf_size) } - }) + match self.buf.as_mut()? { + PacketBuffer::Owned(owned) => Some(owned), + PacketBuffer::Borrowed { ptr, len } => { + // SAFETY: bound checks have already been performed when creating the packet + // from the virtq descriptor. + Some(unsafe { std::slice::from_raw_parts_mut(*ptr, *len) }) + } + } } pub fn src_cid(&self) -> u64 { @@ -447,7 +508,7 @@ mod unit_tests { use virtio_bindings::virtio_ring::VRING_DESC_F_WRITE; use virtio_queue::QueueOwnedT; use vm_memory::GuestAddress; - use vm_virtio::queue::testing::VirtqDesc as GuestQDesc; + use vm_virtio::queue::testing::{VirtQueue as GuestQ, VirtqDesc as GuestQDesc}; use super::super::unit_tests::TestContext; use super::*; @@ -583,6 +644,45 @@ mod unit_tests { } } + #[test] + fn test_tx_packet_assembly_multi_desc() { + const QSIZE: u16 = 4; + let test_ctx = TestContext::new(); + let guest_txvq = GuestQ::new(GuestAddress(0x0060_0000), &test_ctx.mem, QSIZE); + let mut queue = guest_txvq.create_queue(); + + guest_txvq.dtable[0].set( + 0x0061_0000, + VSOCK_PKT_HDR_SIZE as u32, + virtio_bindings::virtio_ring::VRING_DESC_F_NEXT + .try_into() + .unwrap(), + 1, + ); + guest_txvq.dtable[1].set( + 0x0061_1000, + 4 * 1024, + virtio_bindings::virtio_ring::VRING_DESC_F_NEXT + .try_into() + .unwrap(), + 2, + ); + guest_txvq.dtable[2].set(0x0061_2000, 4 * 1024, 0, 0); + guest_txvq.avail.ring[0].set(0); + guest_txvq.avail.idx.set(1); + + set_pkt_len(8 * 1024, &guest_txvq.dtable[0], &test_ctx.mem); + + let pkt = VsockPacket::from_tx_virtq_head( + &mut queue.iter(&test_ctx.mem).unwrap().next().unwrap(), + None, + ) + .unwrap(); + + assert_eq!(pkt.len(), 8 * 1024); + assert_eq!(pkt.buf().unwrap().len(), 8 * 1024); + } + #[test] fn test_rx_packet_assembly() { // Test case: successful RX packet assembly. From c2add07476e978a2f39a84f4ffd04be8a40ee9a5 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Tue, 10 Feb 2026 12:49:35 -0800 Subject: [PATCH 0528/1893] 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 --- vmm/src/device_manager.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 9b2a32455f..0cd91c5c9c 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2830,7 +2830,7 @@ impl DeviceManager { } fn make_virtio_block_devices(&mut self) -> DeviceManagerResult<()> { - let mut block_devices = self.config.lock().unwrap().disks.clone(); + let mut block_devices = self.config.lock().unwrap().disks.take(); if let Some(disk_list_cfg) = &mut block_devices { for disk_cfg in disk_list_cfg.iter_mut() { let device = self.make_virtio_block_device(disk_cfg, false)?; @@ -3001,7 +3001,7 @@ impl DeviceManager { /// Add virto-net and vhost-user-net devices fn make_virtio_net_devices(&mut self) -> DeviceManagerResult<()> { - let mut net_devices = self.config.lock().unwrap().net.clone(); + let mut net_devices = self.config.lock().unwrap().net.take(); if let Some(net_list_cfg) = &mut net_devices { for net_cfg in net_list_cfg.iter_mut() { let device = self.make_virtio_net_device(net_cfg)?; @@ -3109,7 +3109,7 @@ impl DeviceManager { } fn make_virtio_fs_devices(&mut self) -> DeviceManagerResult<()> { - let mut fs_devices = self.config.lock().unwrap().fs.clone(); + let mut fs_devices = self.config.lock().unwrap().fs.take(); if let Some(fs_list_cfg) = &mut fs_devices { for fs_cfg in fs_list_cfg.iter_mut() { let device = self.make_virtio_fs_device(fs_cfg)?; @@ -3296,7 +3296,7 @@ impl DeviceManager { fn make_virtio_pmem_devices(&mut self) -> DeviceManagerResult<()> { // Add virtio-pmem if required - let mut pmem_devices = self.config.lock().unwrap().pmem.clone(); + let mut pmem_devices = self.config.lock().unwrap().pmem.take(); if let Some(pmem_list_cfg) = &mut pmem_devices { for pmem_cfg in pmem_list_cfg.iter_mut() { let device = self.make_virtio_pmem_device(pmem_cfg)?; @@ -3366,7 +3366,7 @@ impl DeviceManager { } fn make_virtio_vsock_devices(&mut self) -> DeviceManagerResult<()> { - let mut vsock = self.config.lock().unwrap().vsock.clone(); + let mut vsock = self.config.lock().unwrap().vsock.take(); if let Some(vsock_cfg) = &mut vsock { let device = self.make_virtio_vsock_device(vsock_cfg)?; self.virtio_devices.push(device); @@ -3607,7 +3607,7 @@ impl DeviceManager { fn make_vdpa_devices(&mut self) -> DeviceManagerResult<()> { // Add vdpa if required - let mut vdpa_devices = self.config.lock().unwrap().vdpa.clone(); + let mut vdpa_devices = self.config.lock().unwrap().vdpa.take(); if let Some(vdpa_list_cfg) = &mut vdpa_devices { for vdpa_cfg in vdpa_list_cfg.iter_mut() { let device = self.make_vdpa_device(vdpa_cfg)?; @@ -3901,7 +3901,7 @@ impl DeviceManager { fn add_vfio_devices(&mut self) -> DeviceManagerResult> { let mut iommu_attached_device_ids = Vec::new(); - let mut devices = self.config.lock().unwrap().devices.clone(); + let mut devices = self.config.lock().unwrap().devices.take(); if let Some(device_list_cfg) = &mut devices { for device_cfg in device_list_cfg.iter_mut() { @@ -4022,7 +4022,7 @@ impl DeviceManager { } fn add_user_devices(&mut self) -> DeviceManagerResult> { - let mut user_devices = self.config.lock().unwrap().user_devices.clone(); + let mut user_devices = self.config.lock().unwrap().user_devices.take(); if let Some(device_list_cfg) = &mut user_devices { for device_cfg in device_list_cfg.iter_mut() { From 638848fcff9d05b633cc0f52e8f40f336da5518b Mon Sep 17 00:00:00 2001 From: Damian Barabonkov Date: Tue, 10 Feb 2026 12:23:28 -0800 Subject: [PATCH 0529/1893] 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 --- pci/src/vfio.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 914feaffde..9e8e7e3163 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -276,6 +276,17 @@ pub struct MmioRegion { pub(crate) user_memory_regions: Vec, } +impl MmioRegion { + /// Returns true if this region has the exact same memory slots as the other region. + pub fn has_matching_slots(&self, other: &MmioRegion) -> bool { + self.user_memory_regions.len() == other.user_memory_regions.len() + && self + .user_memory_regions + .iter() + .all(|u| other.user_memory_regions.iter().any(|o| o.slot == u.slot)) + } +} + /// # Safety /// /// [`Self::find_user_address`] must always either return `Err` From dde5f6ef38ae1b0a2c8685cc27732ae732e11de3 Mon Sep 17 00:00:00 2001 From: Damian Barabonkov Date: Tue, 10 Feb 2026 12:23:36 -0800 Subject: [PATCH 0530/1893] 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 --- vmm/src/device_manager.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 0cd91c5c9c..e7f8d40202 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4607,13 +4607,18 @@ impl DeviceManager { } let (pci_device, bus_device, virtio_device, remove_dma_handler) = match pci_device_handle { - // No need to remove any virtio-mem mapping here as the container outlives all devices + // VirtioMemMappingSource::Container cleanup is handled by + // cleanup_vfio_container when the last VFIO device is removed. PciDeviceHandle::Vfio(vfio_pci_device) => { - for mmio_region in vfio_pci_device.lock().unwrap().mmio_regions() { - self.mmio_regions - .lock() - .unwrap() - .retain(|x| x.start != mmio_region.start); + // Remove this device's MMIO regions from the DeviceManager's + // mmio_regions list. We match on UserMemoryRegion slot numbers + // rather than MmioRegion start addresses because move_bar() + // updates the device's region addresses but not the + // DeviceManager's cloned copies. + let device_regions = vfio_pci_device.lock().unwrap().mmio_regions().clone(); + let mut mmio_regions = self.mmio_regions.lock().unwrap(); + for device_region in &device_regions { + mmio_regions.retain(|x| !x.has_matching_slots(device_region)); } ( From 92b58ba94a397f01c2a589495d2aea58487874b0 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Mon, 9 Feb 2026 14:02:54 -0500 Subject: [PATCH 0531/1893] 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 --- cloud-hypervisor/tests/integration.rs | 1 + virtio-devices/src/vhost_user/mod.rs | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5566caf986..d2f2556451 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -12440,6 +12440,7 @@ mod live_migration { } #[test] + #[ignore = "See #5532 and #7689"] #[cfg(target_arch = "x86_64")] #[cfg(not(feature = "mshv"))] fn test_live_migration_ovs_dpdk_local() { diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 05233a0be6..7e2c162cb9 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -375,15 +375,13 @@ impl VhostUserCommon { } pub fn shutdown(&mut self) { - if let Some(vu) = &self.vu { - // SAFETY: trivially safe - let _ = unsafe { libc::close(vu.lock().unwrap().socket_handle().as_raw_fd()) }; - } - // Remove socket path if needed if self.server { let _ = std::fs::remove_file(&self.socket_path); } + + // Drop the vhost-user handle + self.vu = None; } pub fn add_memory_region( From 2882ccd00a5ed1fc48ae02add24f6e7a863f0547 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 11 Feb 2026 07:37:49 +0100 Subject: [PATCH 0532/1893] vmm: config validation: add more context to errors Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/config.rs | 64 +++++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index e1f9213d3c..ad8bf37a5b 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -186,8 +186,8 @@ pub enum ValidationError { #[error("Path missing when using socket console mode")] ConsoleSocketPathMissing, /// Max is less than boot - #[error("Max CPUs lower than boot CPUs")] - CpusMaxLowerThanBoot, + #[error("Max CPUs ({0}) lower than boot CPUs ({1})")] + CpusMaxLowerThanBoot(u32 /* max vCPUs */, u32 /* boot vCPUs */), /// Too many CPUs. #[error("Too many CPUs: specified {0} but {MAX_SUPPORTED_CPUS} is the limit")] TooManyCpus(u32 /* specified CPUs */), @@ -221,14 +221,14 @@ pub enum ValidationError { #[error("Dies per package must be 1")] CpuTopologyDiesPerPackage, /// Virtio needs a min of 2 queues - #[error("Number of queues to virtio_net less than 2")] - VnetQueueLowerThan2, + #[error("Number of queues ({0}) to virtio_net should be higher than 2")] + VnetQueueLowerThan2(usize), /// The input queue number for virtio_net must match the number of input fds - #[error("Number of queues to virtio_net does not match the number of input FDs")] - VnetQueueFdMismatch, + #[error("Number of queues ({0}) to virtio_net does not match the number of FDs ({1})")] + VnetQueueFdMismatch(usize /* num of queues */, usize /* FD num */), /// Using reserved fd - #[error("Reserved fd number (<= 2)")] - VnetReservedFd, + #[error("Reserved fd number (<= 2): {0}")] + VnetReservedFd(i32), /// Hardware checksum offload is disabled. #[error("\"offload_tso\" and \"offload_ufo\" depend on \"offload_csum\"")] NoHardwareChecksumOffload, @@ -247,8 +247,8 @@ pub enum ValidationError { #[error("No TDX firmware specified")] TdxFirmwareMissing, /// Insufficient vCPUs for queues - #[error("Number of vCPUs is insufficient for number of queues")] - TooManyQueues, + #[error("Queue count ({0}) must not exceed boot vCPUs ({1})")] + TooManyQueues(usize /* queues */, usize /* vCPUs */), /// Invalid queue size #[error("Queue size is smaller than {MINIMUM_BLOCK_QUEUE_SIZE}: {0}")] InvalidQueueSize(u16), @@ -1260,7 +1260,10 @@ impl DiskConfig { pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { if self.num_queues > vm_config.cpus.boot_vcpus as usize { - return Err(ValidationError::TooManyQueues); + return Err(ValidationError::TooManyQueues( + self.num_queues, + vm_config.cpus.boot_vcpus as usize, + )); } if self.queue_size <= MINIMUM_BLOCK_QUEUE_SIZE { @@ -1492,23 +1495,30 @@ impl NetConfig { pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { if self.num_queues < 2 { - return Err(ValidationError::VnetQueueLowerThan2); + return Err(ValidationError::VnetQueueLowerThan2(self.num_queues)); } - if self.fds.is_some() && self.fds.as_ref().unwrap().len() * 2 != self.num_queues { - return Err(ValidationError::VnetQueueFdMismatch); - } + if let Some(fds) = &self.fds { + let actual_queues = fds.len() * 2; + if actual_queues != self.num_queues { + return Err(ValidationError::VnetQueueFdMismatch( + self.num_queues, + actual_queues, + )); + } - if let Some(fds) = self.fds.as_ref() { - for fd in fds { - if *fd <= 2 { - return Err(ValidationError::VnetReservedFd); + for &fd in fds { + if fd <= 2 { + return Err(ValidationError::VnetReservedFd(fd)); } } } if (self.num_queues / 2) > vm_config.cpus.boot_vcpus as usize { - return Err(ValidationError::TooManyQueues); + return Err(ValidationError::TooManyQueues( + self.num_queues, + vm_config.cpus.boot_vcpus as usize, + )); } if self.vhost_user && self.iommu { @@ -1657,7 +1667,10 @@ impl FsConfig { pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { if self.num_queues > vm_config.cpus.boot_vcpus as usize { - return Err(ValidationError::TooManyQueues); + return Err(ValidationError::TooManyQueues( + self.num_queues, + vm_config.cpus.boot_vcpus as usize, + )); } if let Some(platform_config) = vm_config.platform.as_ref() { @@ -2558,7 +2571,10 @@ impl VmConfig { } if self.cpus.max_vcpus < self.cpus.boot_vcpus { - return Err(ValidationError::CpusMaxLowerThanBoot); + return Err(ValidationError::CpusMaxLowerThanBoot( + self.cpus.max_vcpus, + self.cpus.boot_vcpus, + )); } if self.cpus.max_vcpus > MAX_SUPPORTED_CPUS { @@ -4213,7 +4229,7 @@ mod unit_tests { invalid_config.cpus.boot_vcpus = 32; assert_eq!( invalid_config.validate(), - Err(ValidationError::CpusMaxLowerThanBoot) + Err(ValidationError::CpusMaxLowerThanBoot(16, 32)) ); let mut invalid_config = valid_config.clone(); @@ -4301,7 +4317,7 @@ mod unit_tests { }]); assert_eq!( invalid_config.validate(), - Err(ValidationError::VnetReservedFd) + Err(ValidationError::VnetReservedFd(0)) ); let mut invalid_config = valid_config.clone(); From caa362c31f1efd7b5cf9f6ab889daa3be77dfc44 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Sat, 15 Nov 2025 16:44:47 +0000 Subject: [PATCH 0533/1893] 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 --- hypervisor/src/mshv/aarch64/gic/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypervisor/src/mshv/aarch64/gic/mod.rs b/hypervisor/src/mshv/aarch64/gic/mod.rs index 47dab6ff34..5a448d61cc 100644 --- a/hypervisor/src/mshv/aarch64/gic/mod.rs +++ b/hypervisor/src/mshv/aarch64/gic/mod.rs @@ -121,6 +121,6 @@ impl Vgic for MshvGicV2M { } fn save_data_tables(&self) -> Result<()> { - unimplemented!() + Ok(()) } } From 629c117ff353b92cd3426d7e603ed5b9652571d1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 6 Feb 2026 21:13:52 +0100 Subject: [PATCH 0534/1893] 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 --- block/src/qcow/mod.rs | 144 ++++++++++++++++++++++++++++++++++++ block/src/qcow/vec_cache.rs | 15 ++++ block/src/qcow_sync.rs | 10 ++- 3 files changed, 168 insertions(+), 1 deletion(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 75d18f8520..3a868c4932 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -104,12 +104,18 @@ pub enum Error { RefcountTableOffEnd, #[error("Too many clusters specified for refcount")] RefcountTableTooLarge, + #[error("Failed to resize")] + ResizeIo(#[source] io::Error), + #[error("Resize not supported with backing file")] + ResizeWithBackingFile, #[error("Failed to seek file")] SeekingFile(#[source] io::Error), #[error("Failed to set file size")] SettingFileSize(#[source] io::Error), #[error("Failed to set refcount refcount")] SettingRefcountRefcount(#[source] io::Error), + #[error("Shrinking QCOW images is not supported")] + ShrinkNotSupported, #[error("Size too small for number of clusters")] SizeTooSmallForNumberOfClusters, #[error("Failed to sync header")] @@ -1219,6 +1225,144 @@ impl QcowFile { Ok(None) } + /// Resize the virtual size of the QCOW2 image. + /// + /// This supports growing the image, including growing the L1 table + /// if needed. Shrinking is not supported, as it could lead to data + /// loss. Not supported when a backing file is present in that case + /// an error is returned. + pub fn resize(&mut self, new_size: u64) -> Result<()> { + let current_size = self.virtual_size(); + + if new_size == current_size { + return Ok(()); + } + + if new_size < current_size { + return Err(Error::ShrinkNotSupported); + } + + if self.backing_file.is_some() { + return Err(Error::ResizeWithBackingFile); + } + + // Grow the L1 table if needed + let cluster_size = self.raw_file.cluster_size(); + let entries_per_cluster = cluster_size / size_of::() as u64; + let new_clusters = div_round_up_u64(new_size, cluster_size); + let needed_l1_entries = div_round_up_u64(new_clusters, entries_per_cluster) as u32; + + if needed_l1_entries > self.header.l1_size { + self.grow_l1_table(needed_l1_entries)?; + } + + self.header.size = new_size; + + self.raw_file + .file_mut() + .rewind() + .map_err(Error::SeekingFile)?; + self.header + .write_to(self.raw_file.file_mut()) + .map_err(|e| match e { + Error::WritingHeader(io_err) => Error::ResizeIo(io_err), + other => other, + })?; + + self.raw_file + .file_mut() + .sync_all() + .map_err(Error::SyncingHeader)?; + + Ok(()) + } + + /// Grow the L1 table to accommodate at least `new_l1_size` entries. + /// + /// This allocates a new L1 table at file end (guaranteeing contiguity), + /// copies existing entries, updates refcounts, and atomically switches + /// to the new table. + fn grow_l1_table(&mut self, new_l1_size: u32) -> Result<()> { + let old_l1_size = self.header.l1_size; + let old_l1_offset = self.header.l1_table_offset; + let cluster_size = self.raw_file.cluster_size(); + + let new_l1_bytes = new_l1_size as u64 * size_of::() as u64; + let new_l1_clusters = div_round_up_u64(new_l1_bytes, cluster_size); + + // Allocate contiguous clusters at file end for new L1 table + let file_size = self + .raw_file + .file_mut() + .seek(SeekFrom::End(0)) + .map_err(Error::ResizeIo)?; + let new_l1_offset = self.raw_file.cluster_address(file_size + cluster_size - 1); + + // Extend file to fit all L1 clusters + let new_file_end = new_l1_offset + new_l1_clusters * cluster_size; + self.raw_file + .file_mut() + .set_len(new_file_end) + .map_err(Error::SettingFileSize)?; + + // Set refcounts for the contiguous range + for i in 0..new_l1_clusters { + self.set_cluster_refcount(new_l1_offset + i * cluster_size, 1) + .map_err(Error::ResizeIo)?; + } + + let mut new_l1_data = vec![0u64; new_l1_size as usize]; + let old_entries = self.l1_table.get_values(); + new_l1_data[..old_entries.len()].copy_from_slice(old_entries); + + for (i, l2_addr) in new_l1_data.iter_mut().enumerate() { + if *l2_addr != 0 && i < old_entries.len() { + let refcount = self + .refcounts + .get_cluster_refcount(&mut self.raw_file, *l2_addr) + .map_err(Error::GettingRefcount)?; + *l2_addr = l1_entry_make(*l2_addr, refcount == 1); + } + } + + // Write the new L1 table to the file. + self.raw_file + .write_pointer_table_direct(new_l1_offset, new_l1_data.iter()) + .map_err(Error::ResizeIo)?; + + self.raw_file + .file_mut() + .sync_all() + .map_err(Error::SyncingHeader)?; + + self.header.l1_size = new_l1_size; + self.header.l1_table_offset = new_l1_offset; + + self.raw_file + .file_mut() + .rewind() + .map_err(Error::SeekingFile)?; + self.header.write_to(self.raw_file.file_mut())?; + + self.raw_file + .file_mut() + .sync_all() + .map_err(Error::SyncingHeader)?; + + // Free old L1 table clusters + let old_l1_bytes = old_l1_size as u64 * size_of::() as u64; + let old_l1_clusters = div_round_up_u64(old_l1_bytes, cluster_size); + for i in 0..old_l1_clusters { + let cluster_addr = old_l1_offset + i * cluster_size; + let _ = self.set_cluster_refcount(cluster_addr, 0); + } + + // Update L1 table cache + self.l1_table.extend(new_l1_size as usize); + + Ok(()) + } + fn find_avail_clusters(&mut self) -> Result<()> { let cluster_size = self.raw_file.cluster_size(); diff --git a/block/src/qcow/vec_cache.rs b/block/src/qcow/vec_cache.rs index 0423bb6a53..0646421873 100644 --- a/block/src/qcow/vec_cache.rs +++ b/block/src/qcow/vec_cache.rs @@ -62,6 +62,21 @@ impl VecCache { pub 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 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 Cacheable for VecCache { diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index d802291c1d..200fb36bd1 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; use std::fs::File; -use std::io::{Seek, SeekFrom}; +use std::io::{self, Seek, SeekFrom}; use std::os::fd::AsRawFd; use std::sync::{Arc, Mutex}; @@ -68,6 +68,14 @@ impl DiskFile for QcowDiskSync { Ok(Box::new(QcowSync::new(Arc::clone(&self.qcow_file))) as Box) } + fn resize(&mut self, size: u64) -> DiskFileResult<()> { + self.qcow_file + .lock() + .unwrap() + .resize(size) + .map_err(|e| DiskFileError::ResizeError(io::Error::other(e))) + } + fn fd(&mut self) -> BorrowedDiskFd<'_> { BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd()) } From 99493c728ec6fd8b9193565c50d023e61bfe2e30 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 6 Feb 2026 21:14:54 +0100 Subject: [PATCH 0535/1893] 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 --- block/src/qcow/mod.rs | 92 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 3a868c4932..12a6c93d87 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -3432,6 +3432,98 @@ mod unit_tests { }); } + #[test] + fn resize_grow_within_l1() { + with_default_file(0x10_0000, false, |mut q| { + let original_size = q.virtual_size(); + assert_eq!(original_size, 0x10_0000); + + q.resize(original_size) + .expect("Resize to same size should succeed"); + assert_eq!(q.virtual_size(), original_size); + }); + } + + #[test] + fn resize_grow_with_l1_growth() { + let initial_size = 1024 * 1024; // 1 MB + let new_size = 600 * 1024 * 1024; // 600 MB + + let tmp: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false); + let mut q = QcowFile::new(tmp, 3, initial_size).unwrap(); + + let original_l1_size = q.header().l1_size; + assert_eq!(q.virtual_size(), initial_size); + + let test_data = b"Hello, QCOW resize test!"; + q.rewind().unwrap(); + q.write_all(test_data).unwrap(); + + q.resize(new_size).expect("Resize should succeed"); + assert_eq!(q.virtual_size(), new_size); + + assert!(q.header().l1_size > original_l1_size); + + // Verify original data is still intact + let mut buf = vec![0u8; test_data.len()]; + q.rewind().unwrap(); + q.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, test_data); + + let new_offset = new_size - 0x10000; // 64KB before end + q.seek(SeekFrom::Start(new_offset)).unwrap(); + let new_data = b"Data at new end!"; + q.write_all(new_data).unwrap(); + + let mut buf2 = vec![0u8; new_data.len()]; + q.seek(SeekFrom::Start(new_offset)).unwrap(); + q.read_exact(&mut buf2).unwrap(); + assert_eq!(&buf2, new_data); + } + + #[test] + fn resize_shrink_fails() { + with_default_file(0x10_0000, false, |mut q| { + let original_size = q.virtual_size(); + let smaller_size = original_size / 2; + + let result = q.resize(smaller_size); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), Error::ShrinkNotSupported)); + + assert_eq!(q.virtual_size(), original_size); + }); + } + + #[test] + fn resize_with_backing_file_fails() { + let backing_temp = TempFile::new().unwrap(); + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + let backing_size = 1024 * 1024; // 1 MB + + { + let backing_raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); + let _backing_qcow = QcowFile::new(backing_raw, 3, backing_size).unwrap(); + } + + let overlay_file = TempFile::new().unwrap(); + let overlay_raw = RawFile::new(overlay_file.into_file(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Qcow2), + }; + let mut overlay = + QcowFile::new_from_backing(overlay_raw, 3, backing_size, &backing_config).unwrap(); + + assert_eq!(overlay.virtual_size(), backing_size); + + let result = overlay.resize(backing_size * 2); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), Error::ResizeWithBackingFile)); + + assert_eq!(overlay.virtual_size(), backing_size); + } + #[test] fn read_beyond_backing_file_returns_zeros() { let backing_temp = TempFile::new().unwrap(); From 9ba9c0819a12987a45045ffe0be6884131e627df Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 6 Feb 2026 21:37:40 +0100 Subject: [PATCH 0536/1893] 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 --- cloud-hypervisor/tests/integration.rs | 121 ++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index d2f2556451..5de48b7a71 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -6684,6 +6684,127 @@ mod common_parallel { handle_child_output(r, &output); } + #[test] + fn test_disk_resize_qcow2() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = edk2_path(); + + let api_socket = temp_api_path(&guest.tmp_dir); + + let test_disk_path = guest.tmp_dir.as_path().join("resize-test.qcow2"); + + // Create a 16MB QCOW2 disk image + assert!( + exec_host_command_output(&format!( + "qemu-img create -f qcow2 {} 16M", + test_disk_path.to_str().unwrap() + )) + .status + .success() + ); + + let mut cmd = GuestCommand::new(&guest); + + cmd.args(["--api-socket", &api_socket]) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Add the QCOW2 disk to the VM + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-disk", + Some(&format!( + "path={},id=test0", + test_disk_path.to_str().unwrap() + )), + ); + + assert!(cmd_success); + assert!(String::from_utf8_lossy(&cmd_output).contains("\"id\":\"test0\"")); + + // Check that /dev/vdc exists and the block size is 16M + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 16M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Write some data to verify it persists after resize + guest + .ssh_command("sudo dd if=/dev/urandom of=/dev/vdc bs=1M count=8") + .unwrap(); + + // Resize disk up to 32M + let resize_up_success = + resize_disk_command(&api_socket, "test0", "33554432" /* 32M */); + assert!(resize_up_success); + + // Check new size is visible + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 32M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Write to the expanded area to verify it works + guest + .ssh_command("sudo dd if=/dev/zero of=/dev/vdc bs=1M count=32") + .unwrap(); + + // Resize to 64M to exercise L1 table growth + let resize_up_again_success = + resize_disk_command(&api_socket, "test0", "67108864" /* 64M */); + assert!(resize_up_again_success); + + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 64M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Write to the full disk + guest + .ssh_command("sudo dd if=/dev/zero of=/dev/vdc bs=1M count=64") + .unwrap(); + + // QCOW2 does not support shrinking, no resize down test here. + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + disk_check_consistency(&test_disk_path, None); + + handle_child_output(r, &output); + } + fn create_loop_device(backing_file_path: &str, block_size: u32, num_retries: usize) -> String { const LOOP_CONFIGURE: u64 = 0x4c0a; const LOOP_CTL_GET_FREE: u64 = 0x4c82; From 231bbe2d5d736f0db0686a3262955154648f93ea Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Wed, 11 Feb 2026 06:40:23 +0000 Subject: [PATCH 0537/1893] 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 --- vmm/src/config.rs | 6 +++++- vmm/src/vm_config.rs | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index ad8bf37a5b..af9c4ce9a5 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2202,7 +2202,11 @@ impl NumaConfig { let guest_numa_id = parser .convert::("guest_numa_id") .map_err(Error::ParseNuma)? - .unwrap_or(0); + .ok_or_else(|| { + Error::ParseNuma(OptionParserError::InvalidValue( + "guest_numa_id is required for all NUMA nodes".to_string(), + )) + })?; let cpus = parser .convert::("cpus") .map_err(Error::ParseNuma)? diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index a71cca7be0..954ab16fcd 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -676,7 +676,6 @@ pub struct NumaDistance { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct NumaConfig { - #[serde(default)] pub guest_numa_id: u32, #[serde(default)] pub cpus: Option>, From 6d4827b5ff53be563c3c43d7e56bd31e46f6d814 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Tue, 13 Jan 2026 20:21:35 +0000 Subject: [PATCH 0538/1893] 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=,distances=[...],..." The optional field is accepted but not used. Signed-off-by: Saravanan D --- arch/src/lib.rs | 1 + vmm/src/api/openapi/cloud-hypervisor.yaml | 2 ++ vmm/src/config.rs | 5 +++++ vmm/src/vm_config.rs | 2 ++ 4 files changed, 10 insertions(+) diff --git a/arch/src/lib.rs b/arch/src/lib.rs index a7c0ffdca9..c1c1973667 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -120,6 +120,7 @@ pub struct NumaNode { pub pci_segments: Vec, pub distances: BTreeMap, pub memory_zones: Vec, + pub device_id: Option, } pub type NumaNodes = BTreeMap; diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 1fa3d9b517..d1c67c97f9 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -1203,6 +1203,8 @@ components: items: type: integer format: int32 + device_id: + type: string VmResize: type: object diff --git a/vmm/src/config.rs b/vmm/src/config.rs index af9c4ce9a5..3244d1bc0e 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2185,6 +2185,7 @@ impl VsockConfig { impl NumaConfig { pub const SYNTAX: &'static str = "Settings related to a given NUMA node \ \"guest_numa_id=,cpus=,distances=,\ + device_id=,\ memory_zones=,\ pci_segments=\""; @@ -2194,6 +2195,7 @@ impl NumaConfig { .add("guest_numa_id") .add("cpus") .add("distances") + .add("device_id") .add("memory_zones") .add("pci_segments"); @@ -2222,6 +2224,7 @@ impl NumaConfig { }) .collect() }); + let device_id = parser.get("device_id"); let memory_zones = parser .convert::("memory_zones") .map_err(Error::ParseNuma)? @@ -2234,6 +2237,7 @@ impl NumaConfig { guest_numa_id, cpus, distances, + device_id, memory_zones, pci_segments, }) @@ -4118,6 +4122,7 @@ mod unit_tests { guest_numa_id: 0, cpus: None, distances: None, + device_id: None, memory_zones: None, pci_segments: None, } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 954ab16fcd..e0e74cb4ab 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -685,6 +685,8 @@ pub struct NumaConfig { pub memory_zones: Option>, #[serde(default)] pub pci_segments: Option>, + #[serde(default)] + pub device_id: Option, } /// Errors describing a misconfigured payload, i.e., a configuration that From fa435489755bcfe1d2fddcdc97a2c9b4c2e23984 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Wed, 11 Feb 2026 06:41:05 +0000 Subject: [PATCH 0539/1893] 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 --- vmm/src/config.rs | 182 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 3244d1bc0e..abadbb9367 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -362,6 +362,9 @@ pub enum ValidationError { MaskProvidedWithoutIp, #[error("IP provided without a mask")] IpProvidedWithoutMask, + /// Invalid NUMA Configuration + #[error("NUMA Configuration is invalid")] + InvalidNumaConfig(String), } type ValidationResult = std::result::Result; @@ -2233,6 +2236,11 @@ impl NumaConfig { .convert::("pci_segments") .map_err(Error::ParseNuma)? .map(|v| v.0.iter().map(|e| *e as u16).collect()); + if device_id.is_some() && (cpus.is_some() || memory_zones.is_some()) { + return Err(Error::ParseNuma(OptionParserError::InvalidValue( + "device_id in numa config cannot be used with cpus or memory zones".to_string(), + ))); + } Ok(NumaConfig { guest_numa_id, cpus, @@ -2242,6 +2250,58 @@ impl NumaConfig { pci_segments, }) } + + pub fn is_generic_initiator(&self) -> bool { + self.device_id.is_some() + } + + /// Validates NumaConfig + pub fn validate(&self) -> result::Result<(), ValidationError> { + match (&self.device_id, &self.cpus, &self.memory_zones) { + (Some(device_id), None, None) => { + // Valid generic initiator case + if device_id.is_empty() { + return Err(ValidationError::InvalidNumaConfig( + "device_id in numa config cannot be empty".to_string(), + )); + } + Ok(()) + } + (None, Some(cpus), _) => { + // Standard NUMA with cpus + if cpus.is_empty() { + return Err(ValidationError::InvalidNumaConfig( + "cpus list in numa config cannot be empty".to_string(), + )); + } + Ok(()) + } + (None, _, Some(memory_zones)) => { + // Standard NUMA with memory_zones (cpus is None here) + if memory_zones.is_empty() { + return Err(ValidationError::InvalidNumaConfig( + "memory_zones in numa config cannot be empty".to_string(), + )); + } + Ok(()) + } + _ => { + // Default handles all error cases + if self.device_id.is_some() && (self.cpus.is_some() || self.memory_zones.is_some()) + { + Err(ValidationError::InvalidNumaConfig( + "device_id in numa config is mutually exclusive with cpus and memory_zones" + .to_string(), + )) + } else { + Err(ValidationError::InvalidNumaConfig( + "numa config must specify either device_id or cpus/memory_zones" + .to_string(), + )) + } + } + } + } } #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] @@ -2778,6 +2838,7 @@ impl VmConfig { let mut used_numa_node_memory_zones = HashMap::new(); let mut used_pci_segments = HashMap::new(); for numa_node in numa.iter() { + numa_node.validate()?; if let Some(memory_zones) = numa_node.memory_zones.clone() { for memory_zone in memory_zones.iter() { if used_numa_node_memory_zones.contains_key(memory_zone) { @@ -3921,6 +3982,121 @@ mod unit_tests { Ok(()) } + #[test] + fn test_numa_config_parsing() -> Result<()> { + // Error when device_id and cpu/memory are present + let invalid_input = "guest_numa_id=0,cpus=[0,1],distances=[0@25,1@20],\ + device_id=vfio0,memory_zones=[mem1],pci_segments=[0]"; + NumaConfig::parse(invalid_input).unwrap_err(); + // Successful numa config parsing + let standard_input = "guest_numa_id=1,cpus=[2,3],distances=[0@20],\ + memory_zones=[mem0],pci_segments=[0]"; + let expected_standard = NumaConfig { + guest_numa_id: 1, + cpus: Some(vec![2, 3]), + distances: Some(vec![NumaDistance { + destination: 0, + distance: 20, + }]), + device_id: None, + memory_zones: Some(vec!["mem0".to_string()]), + pci_segments: Some(vec![0]), + }; + assert_eq!(NumaConfig::parse(standard_input)?, expected_standard); + // Successful generic initiator config parse + let gi_input = "guest_numa_id=2,device_id=vfio1,distances=[0@30],pci_segments=[1]"; + let expected_gi = NumaConfig { + guest_numa_id: 2, + cpus: None, + distances: Some(vec![NumaDistance { + destination: 0, + distance: 30, + }]), + device_id: Some("vfio1".to_string()), + memory_zones: None, + pci_segments: Some(vec![1]), + }; + assert_eq!(NumaConfig::parse(gi_input)?, expected_gi); + Ok(()) + } + + #[test] + fn test_numa_config_generic_initiator_valid() { + // device_id specified, no cpus/memory_zones + let config = NumaConfig { + guest_numa_id: 0, + cpus: None, + distances: Some(vec![NumaDistance { + destination: 1, + distance: 20, + }]), + memory_zones: None, + device_id: Some("vfio0".to_string()), + pci_segments: None, + }; + config.validate().unwrap(); + assert!(config.is_generic_initiator()); + } + + #[test] + fn test_numa_config_invalid_device_id() { + // empty device_id + let config = NumaConfig { + guest_numa_id: 0, + cpus: None, + distances: None, + memory_zones: None, + device_id: Some(String::new()), + pci_segments: None, + }; + assert!(config.validate().is_err()); + } + + #[test] + fn test_numa_config_invalid_both_device_cpus() { + // device_id and cpus specified + let config = NumaConfig { + guest_numa_id: 0, + cpus: Some(vec![0, 1]), + distances: None, + device_id: Some("vfio0".to_string()), + memory_zones: None, + pci_segments: None, + }; + assert!(config.validate().is_err()); + } + + #[test] + fn test_numa_config_invalid_both_device_memory() { + // device_id and memory zones specified + let config = NumaConfig { + guest_numa_id: 0, + cpus: None, + distances: None, + device_id: Some("vfio0".to_string()), + memory_zones: Some(vec!["mem0".to_string()]), + pci_segments: None, + }; + assert!(config.validate().is_err()); + } + + #[test] + fn test_numa_config_standard_valid() { + // No device_id + let config = NumaConfig { + guest_numa_id: 0, + cpus: Some(vec![0, 1]), + distances: Some(vec![NumaDistance { + destination: 1, + distance: 20, + }]), + device_id: None, + memory_zones: Some(vec!["mem0".to_string()]), + pci_segments: None, + }; + config.validate().unwrap(); + } + #[test] fn test_restore_parsing() -> Result<()> { assert_eq!( @@ -4636,11 +4812,13 @@ mod unit_tests { invalid_config.numa = Some(vec![ NumaConfig { guest_numa_id: 0, + cpus: Some(vec![0]), pci_segments: Some(vec![1]), ..numa_fixture() }, NumaConfig { guest_numa_id: 1, + cpus: Some(vec![1]), pci_segments: Some(vec![1]), ..numa_fixture() }, @@ -4676,10 +4854,12 @@ mod unit_tests { invalid_config.numa = Some(vec![ NumaConfig { guest_numa_id: 0, + cpus: Some(vec![0]), ..numa_fixture() }, NumaConfig { guest_numa_id: 1, + cpus: Some(vec![1]), pci_segments: Some(vec![0]), ..numa_fixture() }, @@ -4693,11 +4873,13 @@ mod unit_tests { invalid_config.numa = Some(vec![ NumaConfig { guest_numa_id: 0, + cpus: Some(vec![0]), pci_segments: Some(vec![0]), ..numa_fixture() }, NumaConfig { guest_numa_id: 1, + cpus: Some(vec![1]), pci_segments: Some(vec![1]), ..numa_fixture() }, From dc0c306dd92bbd80ad79359d428ee76bd2d76607 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Wed, 14 Jan 2026 08:08:33 +0000 Subject: [PATCH 0540/1893] 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 --- vmm/src/acpi.rs | 273 +++++++++++++++++++++++++++++++++++++- vmm/src/device_manager.rs | 23 ++++ vmm/src/vm.rs | 8 +- 3 files changed, 301 insertions(+), 3 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 3554e869ee..8f46b20ddb 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -16,7 +16,7 @@ use arch::NumaNodes; #[cfg(target_arch = "aarch64")] use arch::aarch64::DeviceInfoForFdt; use bitflags::bitflags; -use log::info; +use log::{info, warn}; use pci::PciBdf; use tracer::trace_scoped; use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryRegion}; @@ -104,6 +104,82 @@ struct ProcessorGiccAffinity { pub clock_domain: u32, } +// ACPI 6.6 Section 5.2.16.6 - Generic Initiator Affinity Structure +// Associates devices (e.g., GPUs, NVMe, accelerators) with NUMA proximity domains +// +// Device Handle Type values per ACPI 6.6 spec: +// 0 = ACPI device handle (uses HID and UID) +// 1 = PCI device handle (uses Segment and BDF) +// +// Note: Some older Linux kernel versions may incorrectly expect +// device_handle_type=0 for PCI devices. +#[allow(dead_code)] +#[repr(C, packed)] +#[derive(Default, IntoBytes, Immutable, FromBytes)] +struct GenericInitiatorAffinity { + pub type_: u8, + pub length: u8, + _reserved1: u8, + pub device_handle_type: u8, + pub proximity_domain: u32, + pub device_handle: [u8; 16], + pub flags: u32, + _reserved2: u32, +} + +impl GenericInitiatorAffinity { + #[allow(dead_code)] + fn from_acpi_device(hid: u64, uid: u32, proximity_domain: u32) -> Self { + let mut device_handle = [0u8; 16]; + // ACPI 6.6 Table 5-66: ACPI device handle + // Bytes 0-7: Hardware ID (HID) as 64-bit value + // Bytes 8-11: Unique ID (UID) as 32-bit value + device_handle[0..8].copy_from_slice(&hid.to_le_bytes()); + device_handle[8..12].copy_from_slice(&uid.to_le_bytes()); + // Bytes 12-15: Reserved + GenericInitiatorAffinity { + type_: 5, + length: 32, + _reserved1: 0, + device_handle_type: 0, // 0 = ACPI + proximity_domain, + device_handle, + flags: 1, + _reserved2: 0, + } + } + + fn from_pci_bdf(bdf: PciBdf, proximity_domain: u32) -> Self { + let mut device_handle = [0u8; 16]; + let segment = bdf.segment(); + let bus = bdf.bus(); + let device = bdf.device(); + let function = bdf.function(); + + // ACPI 6.6 Table 5-66: PCI Device Handle + device_handle[0] = (segment & 0xff) as u8; + device_handle[1] = ((segment >> 8) & 0xff) as u8; + device_handle[2] = bus; + device_handle[3] = bus; + device_handle[4] = device; + device_handle[5] = device; + device_handle[6] = function; + device_handle[7] = function; + // Bytes 8-15 remain 0 (Reserved) + + GenericInitiatorAffinity { + type_: 5, + length: 32, + _reserved1: 0, + device_handle_type: 1, // 1 = PCI + proximity_domain, + device_handle, + flags: 1, + _reserved2: 0, + } + } +} + bitflags! { #[derive(Copy, Clone)] pub struct MemAffinityFlags: u32 { @@ -293,6 +369,7 @@ fn create_tpm2_table() -> Sdt { fn create_srat_table( numa_nodes: &NumaNodes, + device_manager: &Arc>, #[cfg(target_arch = "x86_64")] topology: Option<(u16, u16, u16, u16)>, ) -> Sdt { let mut srat = Sdt::new(*b"SRAT", 36, 3, *b"CLOUDH", *b"CHSRAT ", 1); @@ -302,7 +379,9 @@ fn create_srat_table( // Check the MemoryAffinity structure is the right size as expected by // the ACPI specification. assert_eq!(std::mem::size_of::(), 40); - + // Confirm struct size matches ACPI 6.6 spec + assert_eq!(std::mem::size_of::(), 32); + let dm = device_manager.lock().unwrap(); for (node_id, node) in numa_nodes.iter() { let proximity_domain = *node_id; @@ -353,6 +432,19 @@ fn create_srat_table( clock_domain: 0, }); } + + // Add Generic Initiator Affinity structures for device-only NUMA nodes + if let Some(device_id) = &node.device_id { + // Resolve device_id to guest BDF + if let Some(bdf) = dm.get_device_bdf(device_id) { + srat.append(GenericInitiatorAffinity::from_pci_bdf( + bdf, + proximity_domain, + )); + } else { + warn!("Generic Initiator: device_id '{device_id}' not found in device manager"); + } + } } srat } @@ -370,6 +462,10 @@ fn create_slit_table(numa_nodes: &NumaNodes) -> Sdt { 10 } else if let Some(distance) = distances.get(i) { *distance + // When forward distance config is missing + // we can derive it using distance symmetry + } else if let Some(destination) = numa_nodes.get(i) { + destination.distances.get(node_id).copied().unwrap_or(20) } else { 20 }; @@ -887,6 +983,7 @@ fn create_acpi_tables_internal( // SRAT let srat = create_srat_table( numa_nodes, + device_manager, #[cfg(target_arch = "x86_64")] topology, ); @@ -1074,6 +1171,7 @@ pub fn create_acpi_tables_tdx( // SRAT tables.push(create_srat_table( numa_nodes, + device_manager, #[cfg(target_arch = "x86_64")] topology, )); @@ -1090,3 +1188,174 @@ pub fn create_acpi_tables_tdx( tables } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generic_initiator_affinity_size() { + // ACPI spec requires Generic Initiator Affinity Structure to be exactly 32 bytes + assert_eq!( + std::mem::size_of::(), + 32, + "GenericInitiatorAffinity must be exactly 32 bytes per ACPI 6.6 spec" + ); + } + + #[test] + fn test_generic_initiator_from_pci_bdf() { + // Test creating Generic Initiator from PCI BDF + // segment:bus:device:function = 0000:00:05.0 + let bdf = PciBdf::new(0, 0, 5, 0); + let proximity_domain = 1; + + let gi = GenericInitiatorAffinity::from_pci_bdf(bdf, proximity_domain); + + // Verify structure fields + assert_eq!(gi.type_, 5, "Type must be 5 for Generic Initiator"); + assert_eq!(gi.length, 32, "Length must be 32 bytes"); + assert_eq!(gi._reserved1, 0, "Reserved field must be 0"); + assert_eq!( + gi.device_handle_type, 1, + "Device handle type must be 1 for PCI per ACPI 6.6 spec" + ); + // Copy packed fields to local variables to avoid unaligned references + let gi_proximity_domain = gi.proximity_domain; + let gi_flags = gi.flags; + let gi_reserved2 = gi._reserved2; + assert_eq!( + gi_proximity_domain, proximity_domain, + "Proximity domain must match input" + ); + assert_eq!(gi_flags, 1, "Flags must be 1 (enabled)"); + assert_eq!(gi_reserved2, 0, "Reserved field must be 0"); + + // Verify PCI BDF encoding in device_handle + // ACPI 6.6 Table 5-66 format: + // Bytes 0-1: PCI Segment (little-endian) + // Byte 2: Start Bus Number + // Byte 3: End Bus Number + // Byte 4: Start Device Number + // Byte 5: End Device Number + // Byte 6: Start Function + // Byte 7: End Function + // Bytes 8-15: Reserved + let expected_handle: [u8; 16] = [ + 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Reserved + ]; + assert_eq!( + gi.device_handle, expected_handle, + "Device handle must encode PCI BDF correctly per ACPI 6.6 Table 5-66" + ); + } + + #[test] + fn test_generic_initiator_multiple_numa_nodes() { + // Test Generic Initiators assigned to different NUMA nodes + let bdf0 = PciBdf::new(0, 0, 4, 0); + let bdf1 = PciBdf::new(0, 0, 5, 0); + + let gi0 = GenericInitiatorAffinity::from_pci_bdf(bdf0, 0); + let gi1 = GenericInitiatorAffinity::from_pci_bdf(bdf1, 1); + + // Copy packed fields to local variables to avoid unaligned references + let gi0_proximity_domain = gi0.proximity_domain; + let gi1_proximity_domain = gi1.proximity_domain; + assert_eq!(gi0_proximity_domain, 0); + assert_eq!(gi1_proximity_domain, 1); + + // Verify both have correct type and length + assert_eq!(gi0.type_, 5); + assert_eq!(gi0.length, 32); + assert_eq!(gi1.type_, 5); + assert_eq!(gi1.length, 32); + } + + #[test] + fn test_generic_initiator_repr_c_layout() { + // Verify the struct has correct C representation for ACPI table + // This ensures field offsets match ACPI spec + let gi = GenericInitiatorAffinity { + type_: 5, + length: 32, + _reserved1: 0, + device_handle_type: 1, + proximity_domain: 1, + device_handle: [0u8; 16], + flags: 1, + _reserved2: 0, + }; + + // Convert to bytes and verify layout + // SAFETY: `gi` is a local, initialized struct. Because it is `repr(packed)`, + // there is no internal padding, making every byte within it + // safe to read. Casting to `u8` satisfies alignment requirements. + let bytes = unsafe { + std::slice::from_raw_parts( + &gi as *const GenericInitiatorAffinity as *const u8, + std::mem::size_of::(), + ) + }; + + // Verify field positions per ACPI 6.6 spec + assert_eq!(bytes[0], 5, "Offset 0: Type"); + assert_eq!(bytes[1], 32, "Offset 1: Length"); + assert_eq!(bytes[2], 0, "Offset 2: Reserved"); + assert_eq!(bytes[3], 1, "Offset 3: Device Handle Type (1=PCI per spec)"); + // Proximity domain at offset 4-7 (u32 little-endian) + assert_eq!(bytes[4], 1); + assert_eq!(bytes[5], 0); + assert_eq!(bytes[6], 0); + assert_eq!(bytes[7], 0); + // Device handle at offset 8-23 (16 bytes) + // Flags at offset 24-27 (u32 little-endian) + assert_eq!(bytes[24], 1); + // Reserved at offset 28-31 + } + + #[test] + fn test_generic_initiator_acpi_device_handle() { + // Test ACPI device handle (device_handle_type=0) for completeness + // This validates HID and UID encoding per ACPI 6.6 spec (Table 5.65) + let hid: u64 = 0x0123456789ABCDEF; + let uid: u32 = 0x12345678; + let proximity_domain = 2; + + let gi = GenericInitiatorAffinity::from_acpi_device(hid, uid, proximity_domain); + + // Verify structure fields + assert_eq!(gi.type_, 5, "Type must be 5 for Generic Initiator"); + assert_eq!(gi.length, 32, "Length must be 32 bytes"); + assert_eq!(gi._reserved1, 0, "Reserved field must be 0"); + assert_eq!( + gi.device_handle_type, 0, + "Device handle type must be 0 for ACPI per ACPI 6.6 spec" + ); + // Copy packed fields to local variables to avoid unaligned references + let gi_proximity_domain = gi.proximity_domain; + let gi_flags = gi.flags; + let gi_reserved2 = gi._reserved2; + assert_eq!( + gi_proximity_domain, proximity_domain, + "Proximity domain must match input" + ); + assert_eq!(gi_flags, 1, "Flags must be 1 (enabled)"); + assert_eq!(gi_reserved2, 0, "Reserved field must be 0"); + + // Verify ACPI device handle encoding + // Expected format per ACPI 6.6 Table 5.65: + // Bytes 0-7: HID (64-bit, little-endian) + // Bytes 8-11: UID (32-bit, little-endian) + // Bytes 12-15: Reserved + let expected_handle: [u8; 16] = [ + 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01, // HID + 0x78, 0x56, 0x34, 0x12, // UID + 0, 0, 0, 0, // Reserved + ]; + assert_eq!( + gi.device_handle, expected_handle, + "Device handle must encode HID and UID correctly" + ); + } +} diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index e7f8d40202..e721cd2520 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1051,6 +1051,10 @@ pub struct DeviceManager { // List of guest NUMA nodes. numa_nodes: NumaNodes, + // Mapping from device ID (e.g., "vfio0") to guest PCI BDF. + // Used for Generic Initiator NUMA nodes to resolve device_id to BDF. + device_id_to_bdf: HashMap, + // Possible handle to the virtio-balloon device balloon: Option>>, @@ -1348,6 +1352,7 @@ impl DeviceManager { id_to_dev_info: HashMap::new(), seccomp_action, numa_nodes, + device_id_to_bdf: HashMap::new(), balloon: None, activate_evt: activate_evt .try_clone() @@ -1645,6 +1650,9 @@ impl DeviceManager { handle.dma_handler, )?; + // Track device BDF for Generic Initiator support + self.device_id_to_bdf.insert(handle.id.clone(), dev_id); + if handle.iommu { iommu_attached_devices.push(dev_id); } @@ -3837,6 +3845,10 @@ impl DeviceManager { .unwrap() .insert(vfio_name.clone(), node); + // Track device ID → guest BDF mapping for Generic Initiator resolution + self.device_id_to_bdf + .insert(vfio_name.clone(), pci_device_bdf); + Ok((pci_device_bdf, vfio_name)) } @@ -4018,6 +4030,10 @@ impl DeviceManager { .unwrap() .insert(vfio_user_name.clone(), node); + // Track device ID → guest BDF mapping for Generic Initiator resolution + self.device_id_to_bdf + .insert(vfio_user_name.clone(), pci_device_bdf); + Ok((pci_device_bdf, vfio_user_name)) } @@ -4331,6 +4347,13 @@ impl DeviceManager { &self.pci_segments } + // Get the guest PCI BDF for a device ID. + // Returns None if the device ID is not found. + // Used for resolving Generic Initiator device_id to BDF in ACPI generation. + pub fn get_device_bdf(&self, device_id: &str) -> Option { + self.device_id_to_bdf.get(device_id).copied() + } + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub fn cmdline_additions(&self) -> &[String] { self.cmdline_additions.as_slice() diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 4ed3a1f3df..e359eb4ebf 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -58,7 +58,7 @@ use linux_loader::loader::bzimage::BzImage; use linux_loader::loader::elf::PvhBootCapability::PvhEntryPresent; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use linux_loader::loader::pe::Error::InvalidImageMagicNumber; -use log::{error, info}; +use log::{error, info, warn}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -1032,6 +1032,12 @@ impl Vm { let dest = distance.destination; let dist = distance.distance; + if dest == config.guest_numa_id && dist != 10 { + warn!( + "Ignoring self-distance {dest}@{dist} (must be 10 per ACPI spec)" + ); + } + if !configs.iter().any(|cfg| cfg.guest_numa_id == dest) { error!("Unknown destination NUMA node {dest}"); return Err(Error::InvalidNumaConfig); From df67c3690e7815c54b9a730e2967cafd10fb51ad Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Wed, 14 Jan 2026 11:24:53 +0000 Subject: [PATCH 0541/1893] 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 --- arch/src/aarch64/fdt.rs | 211 +++++++++++++++++++++++++++++++++++----- 1 file changed, 186 insertions(+), 25 deletions(-) diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index 5ce1bea206..0310cf5b08 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -19,7 +19,7 @@ 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, warn}; use thiserror::Error; use vm_fdt::{FdtWriter, FdtWriterResult}; use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion}; @@ -345,6 +345,17 @@ fn create_cpu_nodes( 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) { let cpu_name = format!("cpu@{cpu_id:x}"); let cpu_node = fdt.begin_node(&cpu_name)?; @@ -359,8 +370,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 +514,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 = Vec::new(); @@ -518,12 +538,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. @@ -1044,6 +1067,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 = 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 +1095,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())?; @@ -1160,3 +1206,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, device_id: Option) -> 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 = 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"); + } +} From 18768e9bf8d68a95bcde47159a33fcaf0a7dc7d8 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Wed, 14 Jan 2026 12:11:06 +0000 Subject: [PATCH 0542/1893] 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 --- cloud-hypervisor/tests/integration.rs | 114 ++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5de48b7a71..1bc941fad9 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -11063,6 +11063,120 @@ mod vfio { handle_child_output(r, &output); } + + fn test_guest_numa_generic_initiator() { + // Skip test if VFIO device is not available or not ready + if !std::path::Path::new(NVIDIA_VFIO_DEVICE).exists() { + println!("SKIPPED: VFIO device {} not found", NVIDIA_VFIO_DEVICE); + return; + } + + // Check if device is bound to vfio-pci driver + let driver_path = format!("{}/driver", NVIDIA_VFIO_DEVICE); + if let Ok(driver) = std::fs::read_link(&driver_path) { + let driver_name = driver.file_name().unwrap_or_default().to_string_lossy(); + if driver_name != "vfio-pci" { + println!( + "SKIPPED: VFIO device {} bound to {}, not vfio-pci", + NVIDIA_VFIO_DEVICE, driver_name + ); + return; + } + } else { + println!( + "SKIPPED: VFIO device {} not bound to any driver", + NVIDIA_VFIO_DEVICE + ); + return; + } + + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + + // x86_64: Direct kernel boot + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) + .args(["--memory", "size=0"]) + .args(["--memory-zone", "id=mem0,size=1G", "id=mem1,size=1G"]) + .args([ + "--numa", + "guest_numa_id=0,cpus=[0-1],distances=[1@20,2@25],memory_zones=mem0", + "guest_numa_id=1,cpus=[2-3],distances=[0@20,2@30],memory_zones=mem1", + "guest_numa_id=2,device_id=vfio0,distances=[0@25,1@30]", + ]) + .args([ + "--device", + &format!("id=vfio0,path={},iommu=on", NVIDIA_VFIO_DEVICE), + ]) + .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args(["--api-socket", &api_socket]) + .capture_output() + .default_disks() + .default_net() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Verify NUMA topology is correct + guest.check_numa_common( + Some(&[960_000, 960_000]), + Some(&[&[0, 1], &[2, 3]]), + Some(&["10 20 25", "20 10 30", "25 30 10"]), + ); + + // Verify Generic Initiator support is present + // Linux kernel sets has_generic_initiator when it parses Type 5 SRAT entries + let has_gi = guest + .ssh_command( + "cat /sys/devices/system/node/has_generic_initiator 2>/dev/null || echo 0", + ) + .unwrap() + .trim() + .to_string(); + + assert_eq!( + has_gi, "2", + "Generic Initiator support should be detected by kernel" + ); + + // Verify SRAT table contains Generic Initiator entry (Type 5) + // We'll check that /sys/firmware/acpi/tables/SRAT exists and contains our entry + let srat_check = guest + .ssh_command( + "[ -f /sys/firmware/acpi/tables/SRAT ] && echo 'exists' || echo 'missing'", + ) + .unwrap() + .trim() + .to_string(); + + assert_eq!( + srat_check, "exists", + "SRAT table should exist in guest firmware" + ); + + // Use hexdump to verify Type 5 entry is present + // Type 5 (0x05) should appear in the SRAT table + let srat_has_type5 = guest + .ssh_command("sudo hexdump -C /sys/firmware/acpi/tables/SRAT | grep -q '05 20' && echo 'found' || echo 'not_found'") + .unwrap() + .trim() + .to_string(); + + assert_eq!( + srat_has_type5, "found", + "SRAT table should contain Generic Initiator Affinity Structure (Type 5, Length 0x20/32)" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } } mod live_migration { From 3af9ac78a9bfcd0f79f577979280d23238cc4011 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Thu, 15 Jan 2026 09:02:23 +0000 Subject: [PATCH 0543/1893] 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 --- docs/memory.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/docs/memory.md b/docs/memory.md index d8a7619e22..75179e0e07 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -429,17 +429,20 @@ introduced to define a guest NUMA topology. It allows for a fine description about the CPUs and memory ranges associated with each NUMA node. Additionally it allows for specifying the distance between each NUMA node. +Furthermore, it supports ACPI Generic Initiator Affinity (SRAT Type 5), which allows VFIO-PCI devices (such as GPUs) to be associated with NUMA nodes that are {memory,cpu}-less. Detailed configuration for this feature can be found under the device_id parameter. + ```rust struct NumaConfig { guest_numa_id: u32, cpus: Option>, distances: Option>, memory_zones: Option>, + device_id: Option, } ``` ``` ---numa Settings related to a given NUMA node "guest_numa_id=,cpus=,distances=,memory_zones= +--numa Settings related to a given NUMA node "guest_numa_id=,cpus=,distances=,memory_zones=,device_id=" ``` ### `guest_numa_id` @@ -454,7 +457,7 @@ Value is an unsigned integer of 32 bits. _Example_ ``` ---numa guest_numa_id=0 +--numa guest_numa_id=0,cpus=[0-1],memory_zones=mem0 ``` ### `cpus` @@ -481,6 +484,9 @@ simply be described with `cpus=[0-99,255]`. As soon as one tries to describe a list of values, `[` and `]` must be used to demarcate the list. +**Note:** When creating a Generic Initiator node via the `device_id` parameter, +the `cpus` option must not be specified. + _Example_ ``` @@ -507,13 +513,34 @@ from the others with `,` separator. As soon as one tries to describe a list of values, `[` and `]` must be used to demarcate the list. +**Default distances:** +- If distances are not specified for a NUMA node, default values are applied: + - Distance to self: 10 + - Distance to all other nodes: 20 +- Partial distance specifications are allowed; unspecified distances use the defaults above + +**Distance symmetry:** +- Cloud Hypervisor automatically ensures distance symmetry in ACPI SLIT (System Locality Information Table) and FDT +- If node A specifies distance to node B, the reverse distance (B to A) is automatically set to the same value + For instance, if one wants to define 3 NUMA nodes, with each node located at different distances, it can be described with the following example. _Example_ ``` +# Explicit bidirectional distances --numa guest_numa_id=0,distances=[1@15,2@25] guest_numa_id=1,distances=[0@15,2@20] guest_numa_id=2,distances=[0@25,1@20] + +# Simplified with symmetry - only specify in one direction +--numa guest_numa_id=0,distances=[1@15,2@25] guest_numa_id=1,distances=[2@20] +# Results in the same topology: 0↔1=15, 0↔2=25, 1↔2=20 + +# Using defaults - only specify non-default distances +--numa guest_numa_id=0,cpus=[0-1],memory_zones=mem0,distances=[1@15] +--numa guest_numa_id=1,cpus=[2-3],memory_zones=mem1 +# Node 0: self=10, to node 1=15 +# Node 1: self=10, to node 0=15 (symmetric) ``` ### `memory_zones` @@ -539,6 +566,9 @@ Note that a memory zone must belong to a single NUMA node. The following configuration is incorrect, therefore not allowed: `--numa guest_numa_id=0,memory_zones=mem0 guest_numa_id=1,memory_zones=mem0` +**Note:** When creating a Generic Initiator node via the `device_id` parameter, +the `memory_zones` option must not be specified. + _Example_ ``` @@ -547,6 +577,44 @@ _Example_ --numa guest_numa_id=0,memory_zones=[mem0,mem2] guest_numa_id=1,memory_zones=mem1 ``` +### `device_id` (Generic Initiator) + +Device identifier for creating a Generic Initiator NUMA node that is +{CPU,memory}-less and associated with a specific VFIO-PCI device. + +Generic Initiator nodes are defined by ACPI SRAT (System Resource Affinity +Table) Type 5 entries and allow the guest OS to understand device-to-memory +proximity relationships. Without Generic Initiator support, the guest OS has +no way to know which NUMA node a passthrough device is closest to. + +By exposing these proximity relationships, the guest OS can perform +NUMA-aware scheduling and optimize memory placement for workloads +utilizing those specific devices. + +When `device_id` is specified, `cpus` and `memory_zones` must NOT be provided. + +Value is a string referring to an existing device identifier defined via +`--device id=`. + +_Example_ + +```bash +# Create two standard NUMA nodes with CPUs and memory, plus one Generic +# Initiator node for a VFIO GPU +--cpus boot=4 +--memory size=0 +--memory-zone id=mem0,size=2G id=mem1,size=2G +--numa guest_numa_id=0,cpus=[0-1],memory_zones=mem0,distances=[1@20,2@25] +--numa guest_numa_id=1,cpus=[2-3],memory_zones=mem1,distances=[0@20,2@30] +--numa guest_numa_id=2,device_id=gpu0,distances=[0@25,1@30] +--device id=gpu0,path=/sys/bus/pci/devices/0000:01:00.0,iommu=on +``` + +In this configuration: +- Node 0: CPUs 0-1, 2GB memory +- Node 1: CPUs 2-3, 2GB memory +- Node 2 (auto-assigned): GPU device, closer to node 0 (distance=25) than node 1 (distance=30) + ### PCI bus Cloud Hypervisor supports guests with one or more PCI segments. The default PCI segment always From 9babad0114600608b46a959c0068b4ddcd812244 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Sat, 31 Jan 2026 11:49:26 +0000 Subject: [PATCH 0544/1893] 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 --- .lychee.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.lychee.toml b/.lychee.toml index 44517a7819..fb267bbfff 100644 --- a/.lychee.toml +++ b/.lychee.toml @@ -1,5 +1,7 @@ verbose = "info" +exclude_path = [".lychee.toml"] + exclude = [ # Availability of links below should be manually verified. # Page for intel TDX support, returns 403 while querying. @@ -16,6 +18,13 @@ exclude = [ "\\$.*", # 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', ] # Exclude loopback addresses From 03ef7d1991ba4119a8cf5805dec6b43d8e86fe82 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 12 Feb 2026 19:03:07 +0100 Subject: [PATCH 0545/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 4 ++-- vmm/src/vm.rs | 62 ++++++++++++++++---------------------------------- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 41a2c3383a..dcf6614b24 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1875,7 +1875,7 @@ impl RequestHandler for Vmm { match &self.vm_config { Some(vm_config) => { let state = match &self.vm { - Some(vm) => vm.get_state()?, + Some(vm) => vm.get_state(), None => VmState::Created, }; let config = vm_config.lock().unwrap().clone(); @@ -2347,7 +2347,7 @@ impl RequestHandler for Vmm { return e; } - if vm.get_state().unwrap() == VmState::Paused + if vm.get_state() == VmState::Paused && let Err(e) = vm.resume() { return e; diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index e359eb4ebf..882993f0a7 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -19,7 +19,7 @@ use std::mem::size_of; use std::num::Wrapping; use std::ops::Deref; use std::os::unix::net::UnixStream; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, Mutex}; #[cfg(not(target_arch = "riscv64"))] use std::time::Instant; use std::{cmp, result, str, thread}; @@ -150,9 +150,6 @@ pub enum Error { #[error("Cannot enable interrupt controller")] EnableInterruptController(#[source] interrupt_controller::Error), - #[error("VM state is poisoned")] - PoisonedState, - #[error("Error from device manager")] DeviceManager(#[source] DeviceManagerError), @@ -511,7 +508,7 @@ pub struct Vm { threads: Vec>, device_manager: Arc>, config: Arc>, - state: RwLock, + state: VmState, cpu_manager: Arc>, memory_manager: Arc>, #[cfg_attr(any(not(feature = "kvm"), target_arch = "aarch64"), allow(dead_code))] @@ -875,7 +872,7 @@ impl Vm { None }; - let vm_state = if snapshot.is_some() { + let state = if snapshot.is_some() { VmState::Paused } else { VmState::Created @@ -888,7 +885,7 @@ impl Vm { device_manager, config, threads: Vec::with_capacity(1), - state: RwLock::new(vm_state), + state, cpu_manager, memory_manager, vm, @@ -1669,10 +1666,9 @@ impl Vm { } pub fn shutdown(&mut self) -> Result<()> { - let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?; let new_state = VmState::Shutdown; - state.valid_transition(new_state)?; + self.state.valid_transition(new_state)?; // Wake up the DeviceManager threads so they will get terminated cleanly self.device_manager @@ -1691,7 +1687,7 @@ impl Vm { for thread in self.threads.drain(..) { thread.join().map_err(Error::ThreadCleanup)?; } - *state = new_state; + self.state = new_state; Ok(()) } @@ -2378,7 +2374,7 @@ impl Vm { pub fn boot(&mut self) -> Result<()> { trace_scoped!("Vm::boot"); - let current_state = self.get_state()?; + let current_state = self.state; if current_state == VmState::Paused { return self.resume().map_err(Error::Resume); } @@ -2562,8 +2558,7 @@ impl Vm { .start_boot_vcpus(new_state == VmState::BreakPoint) .map_err(Error::CpuManager)?; - let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?; - *state = new_state; + self.state = new_state; Ok(()) } @@ -2593,12 +2588,9 @@ impl Vm { Arc::clone(&self.config) } - /// Get the VM state. Returns an error if the state is poisoned. - pub fn get_state(&self) -> Result { + /// Get the VM state. + pub fn get_state(&self) -> VmState { self.state - .try_read() - .map_err(|_| Error::PoisonedState) - .map(|state| *state) } /// Gets the actual size of the balloon. @@ -2850,13 +2842,8 @@ impl Vm { impl Pausable for Vm { fn pause(&mut self) -> std::result::Result<(), MigratableError> { event!("vm", "pausing"); - let mut state = self - .state - .try_write() - .map_err(|e| MigratableError::Pause(anyhow!("Could not get VM state: {e}")))?; let new_state = VmState::Paused; - - state + self.state .valid_transition(new_state) .map_err(|e| MigratableError::Pause(anyhow!("Invalid transition: {e:?}")))?; @@ -2883,7 +2870,7 @@ impl Pausable for Vm { .pause() .map_err(|e| MigratableError::Pause(anyhow!("Could not pause the VM: {e}")))?; - *state = new_state; + self.state = new_state; event!("vm", "paused"); Ok(()) @@ -2891,14 +2878,10 @@ impl Pausable for Vm { fn resume(&mut self) -> std::result::Result<(), MigratableError> { event!("vm", "resuming"); - let current_state = self.get_state().unwrap(); - let mut state = self - .state - .try_write() - .map_err(|e| MigratableError::Resume(anyhow!("Could not get VM state: {e}")))?; + let current_state = self.get_state(); let new_state = VmState::Running; - state + self.state .valid_transition(new_state) .map_err(|e| MigratableError::Resume(anyhow!("Invalid transition: {e:?}")))?; @@ -2921,7 +2904,7 @@ impl Pausable for Vm { self.device_manager.lock().unwrap().resume()?; // And we're back to the Running state. - *state = new_state; + self.state = new_state; event!("vm", "resumed"); Ok(()) } @@ -2953,8 +2936,7 @@ impl Snapshottable for Vm { } } - let current_state = self.get_state().unwrap(); - if current_state != VmState::Paused { + if self.get_state() != VmState::Paused { return Err(MigratableError::Snapshot(anyhow!( "Trying to snapshot while VM is running" ))); @@ -3116,20 +3098,16 @@ impl Debuggable for Vm { } fn debug_pause(&mut self) -> std::result::Result<(), DebuggableError> { - if *self.state.read().unwrap() == VmState::Running { + if self.state == VmState::Running { self.pause().map_err(DebuggableError::Pause)?; } - let mut state = self - .state - .try_write() - .map_err(|_| DebuggableError::PoisonedState)?; - *state = VmState::BreakPoint; + self.state = VmState::BreakPoint; Ok(()) } fn debug_resume(&mut self) -> std::result::Result<(), DebuggableError> { - if *self.state.read().unwrap() == VmState::BreakPoint { + if self.state == VmState::BreakPoint { self.resume().map_err(DebuggableError::Pause)?; } @@ -3209,7 +3187,7 @@ impl GuestDebuggable for Vm { } } - match self.get_state().unwrap() { + match self.get_state() { VmState::Running => { self.pause().map_err(GuestDebuggableError::Pause)?; resume = true; From 41b23229a595d68ebac95257ead523f7d7f4d307 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 12 Feb 2026 18:33:04 -0800 Subject: [PATCH 0546/1893] 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 --- vmm/src/device_manager.rs | 4 +- vmm/src/vm.rs | 682 ++++++++++++++++++++++++-------------- 2 files changed, 441 insertions(+), 245 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index e721cd2520..0a88b44008 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -15,7 +15,9 @@ use std::io::{self, IsTerminal, Seek, SeekFrom, stdout}; use std::num::Wrapping; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, FromRawFd}; -use std::path::{Path, PathBuf}; +#[cfg(not(target_arch = "riscv64"))] +use std::path::Path; +use std::path::PathBuf; use std::result; use std::sync::{Arc, Mutex}; #[cfg(not(target_arch = "riscv64"))] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 882993f0a7..dc5a96a4b1 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -11,7 +11,7 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fs::{File, OpenOptions}; use std::io::{self, Seek, SeekFrom, Write}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] @@ -560,21 +560,10 @@ impl Vm { let numa_nodes = Self::create_numa_nodes(config.lock().unwrap().numa.as_deref(), &memory_manager)?; - #[cfg(feature = "tdx")] - let tdx_enabled = config.lock().unwrap().is_tdx_enabled(); - #[cfg(feature = "sev_snp")] - let sev_snp_enabled = config.lock().unwrap().is_sev_snp_enabled(); - #[cfg(feature = "tdx")] - let force_iommu = tdx_enabled; - #[cfg(feature = "sev_snp")] - let force_iommu = sev_snp_enabled; - #[cfg(not(any(feature = "tdx", feature = "sev_snp")))] - let force_iommu = false; + // Determine if IOMMU should be forced based on confidential computing features + let force_iommu = Self::should_force_iommu(&config); - #[cfg(feature = "guest_debug")] - let stop_on_boot = config.lock().unwrap().gdb; - #[cfg(not(feature = "guest_debug"))] - let stop_on_boot = false; + let stop_on_boot = Self::should_stop_on_boot(&config); let memory = memory_manager.lock().unwrap().guest_memory(); let io_bus = Arc::new(Bus::new()); @@ -587,63 +576,26 @@ impl Vm { mmio_bus: mmio_bus.clone(), }); - let cpus_config = { &config.lock().unwrap().cpus.clone() }; - let cpu_manager = cpu::CpuManager::new( - cpus_config, + // Create CPU manager + let cpu_manager = Self::create_cpu_manager( + &config, vm.clone(), exit_evt.try_clone().map_err(Error::EventFdClone)?, reset_evt.try_clone().map_err(Error::EventFdClone)?, #[cfg(feature = "guest_debug")] vm_debug_evt, - hypervisor.clone(), + &hypervisor, seccomp_action.clone(), vm_ops, - #[cfg(feature = "tdx")] - tdx_enabled, &numa_nodes, - #[cfg(feature = "sev_snp")] - sev_snp_enabled, - ) - .map_err(Error::CpuManager)?; - - #[cfg(target_arch = "x86_64")] - cpu_manager - .lock() - .unwrap() - .populate_cpuid( - hypervisor.as_ref(), - #[cfg(feature = "tdx")] - tdx_enabled, - ) - .map_err(Error::CpuManager)?; - - // The initial TDX configuration must be done before the vCPUs are - // created - #[cfg(feature = "tdx")] - if tdx_enabled { - let cpuid = cpu_manager.lock().unwrap().common_cpuid(); - let max_vcpus = cpu_manager.lock().unwrap().max_vcpus(); - vm.tdx_init(&cpuid, max_vcpus) - .map_err(Error::InitializeTdxVm)?; - } + )?; + // Perform hypervisor-specific TDX initialization if enabled #[cfg(feature = "tdx")] - let dynamic = !tdx_enabled; - #[cfg(not(feature = "tdx"))] - let dynamic = true; + Self::init_tdx_if_enabled(&config, &vm, &cpu_manager)?; - #[cfg(feature = "kvm")] - let is_kvm = matches!( - hypervisor.hypervisor_type(), - hypervisor::HypervisorType::Kvm - ); - #[cfg(feature = "mshv")] - let is_mshv = matches!( - hypervisor.hypervisor_type(), - hypervisor::HypervisorType::Mshv - ); - - let device_manager = DeviceManager::new( + // Create device manager + let device_manager = Self::create_device_manager( io_bus, mmio_bus, vm.clone(), @@ -659,190 +611,25 @@ impl Vm { boot_id_list, #[cfg(not(target_arch = "riscv64"))] timestamp, - snapshot_from_id(snapshot, DEVICE_MANAGER_SNAPSHOT_ID), - dynamic, - ) - .map_err(Error::DeviceManager)?; - - // Initialize the VM now that we have created the device manager. - // For MSHV and non aarch64, we need to initialize the VM before creating vCPUs. - // For aarch64, we need to initialize the VM after creating interrupt controller. - // Push down write after the IC(Interrupt Controller) creation for MSHV aarch64. - #[cfg(all(feature = "mshv", not(target_arch = "aarch64")))] - { - if is_mshv { - vm.init().map_err(Error::InitializeVm)?; - } - } - #[cfg(feature = "sev_snp")] - if sev_snp_enabled { - cpu_manager - .lock() - .unwrap() - .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) - .map_err(Error::CpuManager)?; - - // This initial SEV-SNP configuration must be done immediately after - // vCPUs are created. As part of this initialization we are - // transitioning the guest into secure state. - vm.sev_snp_init().map_err(Error::InitializeSevSnpVm)?; - } - - #[cfg(feature = "sev_snp")] - // Loading the igvm file is pushed down here because - // igvm parser needs cpu_manager to retrieve cpuid leaf. - // Currently, Microsoft Hypervisor does not provide any - // Hypervisor specific common cpuid, we need to call get_cpuid_values - // per cpuid through cpu_manager. - let _load_payload_handle = if snapshot.is_none() && sev_snp_enabled { - Self::load_payload_async( - &memory_manager, - &config, - #[cfg(feature = "igvm")] - &cpu_manager, - #[cfg(feature = "sev_snp")] - sev_snp_enabled, - )? - } else { - None - }; - - #[cfg(feature = "mshv")] - { - if is_mshv { - let ic = device_manager - .lock() - .unwrap() - .create_interrupt_controller() - .map_err(Error::DeviceManager)?; - #[cfg(target_arch = "aarch64")] - vm.init().map_err(Error::InitializeVm)?; - device_manager - .lock() - .unwrap() - .create_devices( - console_info.clone(), - console_resize_pipe.clone(), - original_termios.clone(), - ic, - ) - .map_err(Error::DeviceManager)?; - } - } - cfg_if::cfg_if! { - if #[cfg(feature = "sev_snp")] { - if !sev_snp_enabled { - memory_manager - .lock() - .unwrap() - .allocate_address_space() - .map_err(Error::MemoryManager)?; - } - } else { - memory_manager - .lock() - .unwrap() - .allocate_address_space() - .map_err(Error::MemoryManager)?; - } - } - - #[cfg(target_arch = "aarch64")] - memory_manager - .lock() - .unwrap() - .add_uefi_flash() - .map_err(Error::MemoryManager)?; - - // First case is when sev_snp is enabled(compiled), but run time non-cvn - // guest boot. 2nd case is when sev_snp is not compiled in, KVM and MSHV regular guest boot. - cfg_if::cfg_if! { - if #[cfg(feature = "sev_snp")] { - let _load_payload_handle = if snapshot.is_none() && !sev_snp_enabled { - Self::load_payload_async( - &memory_manager, - &config, - #[cfg(feature = "igvm")] - &cpu_manager, - #[cfg(feature = "sev_snp")] - sev_snp_enabled, - )? - } else { - None - }; - } else { - let _load_payload_handle = if snapshot.is_none() { - Self::load_payload_async( - &memory_manager, - &config, - #[cfg(feature = "igvm")] - &cpu_manager, - )? - } else { - None - }; - } - } - - // First case is when sev_snp is enabled(compiled), but run time non-cvn - // guest boot. 2nd case is when sev_snp is not compiled in, KVM and MSHV regular guest boot. - cfg_if::cfg_if! { - if #[cfg(feature = "sev_snp")] { - if !sev_snp_enabled { - cpu_manager - .lock() - .unwrap() - .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) - .map_err(Error::CpuManager)?; - } - } else { - cpu_manager - .lock() - .unwrap() - .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) - .map_err(Error::CpuManager)?; - } - } - - // For KVM, we need to create interrupt controller after we create boot vcpus. - // Because we restore GIC state from the snapshot as part of boot vcpu creation. - // This means that we need to create interrupt controller after we restore in case of KVM guests. - #[cfg(feature = "kvm")] - { - if is_kvm { - let ic = device_manager - .lock() - .unwrap() - .create_interrupt_controller() - .map_err(Error::DeviceManager)?; - - vm.init().map_err(Error::InitializeVm)?; - - device_manager - .lock() - .unwrap() - .create_devices(console_info, console_resize_pipe, original_termios, ic) - .map_err(Error::DeviceManager)?; - } - } + snapshot, + &config, + )?; - #[cfg(feature = "fw_cfg")] - { - let fw_cfg_config = config - .lock() - .unwrap() - .payload - .as_ref() - .is_some_and(|p| p.fw_cfg_config.is_some()); - if fw_cfg_config { - device_manager - .lock() - .unwrap() - .create_fw_cfg_device() - .map_err(Error::DeviceManager)?; - } - } + // Perform hypervisor-specific initialization + let load_payload_handle = Self::hypervisor_specific_init( + &vm, + &memory_manager, + &cpu_manager, + &device_manager, + &config, + &hypervisor, + console_info.as_ref(), + console_resize_pipe.as_ref(), + &original_termios, + snapshot, + )?; + // Load kernel and initramfs files #[cfg(feature = "tdx")] let kernel = config .lock() @@ -896,10 +683,417 @@ impl Vm { #[cfg(not(target_arch = "riscv64"))] hypervisor, stop_on_boot, - load_payload_handle: _load_payload_handle, + load_payload_handle, }) } + /// Determine if IOMMU should be forced based on confidential computing features. + fn should_force_iommu(_config: &Arc>) -> bool { + #[cfg(feature = "tdx")] + if _config.lock().unwrap().is_tdx_enabled() { + return true; + } + #[cfg(feature = "sev_snp")] + if _config.lock().unwrap().is_sev_snp_enabled() { + return true; + } + false + } + + /// Determine if VM should stop on boot (for debugging). + fn should_stop_on_boot(config: &Arc>) -> bool { + #[cfg(feature = "guest_debug")] + { + config.lock().unwrap().gdb + } + #[cfg(not(feature = "guest_debug"))] + { + let _ = config; + false + } + } + + /// Create and configure the CPU manager. + #[allow(clippy::too_many_arguments)] + fn create_cpu_manager( + config: &Arc>, + vm: Arc, + exit_evt: EventFd, + reset_evt: EventFd, + #[cfg(feature = "guest_debug")] vm_debug_evt: EventFd, + hypervisor: &Arc, + seccomp_action: SeccompAction, + vm_ops: Arc, + numa_nodes: &NumaNodes, + ) -> Result>> { + #[cfg(feature = "tdx")] + let tdx_enabled = config.lock().unwrap().is_tdx_enabled(); + #[cfg(feature = "sev_snp")] + let sev_snp_enabled = config.lock().unwrap().is_sev_snp_enabled(); + + let cpus_config = config.lock().unwrap().cpus.clone(); + let cpu_manager = cpu::CpuManager::new( + &cpus_config, + vm, + exit_evt, + reset_evt, + #[cfg(feature = "guest_debug")] + vm_debug_evt, + hypervisor.clone(), + seccomp_action, + vm_ops, + #[cfg(feature = "tdx")] + tdx_enabled, + numa_nodes, + #[cfg(feature = "sev_snp")] + sev_snp_enabled, + ) + .map_err(Error::CpuManager)?; + + #[cfg(target_arch = "x86_64")] + cpu_manager + .lock() + .unwrap() + .populate_cpuid( + hypervisor.as_ref(), + #[cfg(feature = "tdx")] + tdx_enabled, + ) + .map_err(Error::CpuManager)?; + + Ok(cpu_manager) + } + + /// Initialize TDX if enabled. + #[cfg(feature = "tdx")] + fn init_tdx_if_enabled( + config: &Arc>, + vm: &Arc, + cpu_manager: &Arc>, + ) -> Result<()> { + if config.lock().unwrap().is_tdx_enabled() { + let cpuid = cpu_manager.lock().unwrap().common_cpuid(); + let max_vcpus = cpu_manager.lock().unwrap().max_vcpus(); + vm.tdx_init(&cpuid, max_vcpus) + .map_err(Error::InitializeTdxVm)?; + } + Ok(()) + } + + /// Create and configure the device manager. + #[allow(clippy::too_many_arguments)] + fn create_device_manager( + io_bus: Arc, + mmio_bus: Arc, + vm: Arc, + config: Arc>, + memory_manager: Arc>, + cpu_manager: Arc>, + exit_evt: EventFd, + reset_evt: EventFd, + seccomp_action: SeccompAction, + numa_nodes: NumaNodes, + activate_evt: &EventFd, + force_iommu: bool, + boot_id_list: BTreeSet, + #[cfg(not(target_arch = "riscv64"))] timestamp: Instant, + snapshot: Option<&Snapshot>, + _vm_config: &Arc>, + ) -> Result>> { + #[cfg(feature = "tdx")] + let dynamic = !_vm_config.lock().unwrap().is_tdx_enabled(); + #[cfg(not(feature = "tdx"))] + let dynamic = true; + + DeviceManager::new( + io_bus, + mmio_bus, + vm, + config, + memory_manager, + cpu_manager, + exit_evt, + reset_evt, + seccomp_action, + numa_nodes, + activate_evt, + force_iommu, + boot_id_list, + #[cfg(not(target_arch = "riscv64"))] + timestamp, + snapshot_from_id(snapshot, DEVICE_MANAGER_SNAPSHOT_ID), + dynamic, + ) + .map_err(Error::DeviceManager) + } + + /// Perform hypervisor-specific initialization. + /// + /// This handles the different initialization sequences required by: + /// - KVM (x86_64, aarch64, riscv64) + /// - MSHV (x86_64, aarch64) + /// - SEV-SNP (MSHV with confidential computing) + #[allow(clippy::too_many_arguments)] + fn hypervisor_specific_init( + vm: &Arc, + memory_manager: &Arc>, + cpu_manager: &Arc>, + device_manager: &Arc>, + config: &Arc>, + hypervisor: &Arc, + console_info: Option<&ConsoleInfo>, + console_resize_pipe: Option<&Arc>, + original_termios: &Arc>>, + snapshot: Option<&Snapshot>, + ) -> Result>>> { + #[cfg(feature = "mshv")] + let is_mshv = matches!( + hypervisor.hypervisor_type(), + hypervisor::HypervisorType::Mshv + ); + #[cfg(feature = "kvm")] + let is_kvm = matches!( + hypervisor.hypervisor_type(), + hypervisor::HypervisorType::Kvm + ); + + #[cfg(feature = "sev_snp")] + let sev_snp_enabled = config.lock().unwrap().is_sev_snp_enabled(); + + // MSHV-specific initialization (non-aarch64) + #[cfg(all(feature = "mshv", not(target_arch = "aarch64")))] + if is_mshv { + vm.init().map_err(Error::InitializeVm)?; + } + + // SEV-SNP specific initialization + #[cfg(feature = "sev_snp")] + if sev_snp_enabled { + return Self::init_sev_snp( + vm, + memory_manager, + cpu_manager, + device_manager, + config, + console_info, + console_resize_pipe, + original_termios, + snapshot, + ); + } + + // MSHV initialization (create interrupt controller and devices) + #[cfg(feature = "mshv")] + if is_mshv { + Self::init_mshv( + vm, + device_manager, + console_info, + console_resize_pipe, + original_termios, + )?; + } + + // Allocate address space for non-SEV-SNP guests + memory_manager + .lock() + .unwrap() + .allocate_address_space() + .map_err(Error::MemoryManager)?; + + // Add UEFI flash for aarch64 + #[cfg(target_arch = "aarch64")] + memory_manager + .lock() + .unwrap() + .add_uefi_flash() + .map_err(Error::MemoryManager)?; + + // Load payload asynchronously + let load_payload_handle = if snapshot.is_none() { + Self::load_payload_async( + memory_manager, + config, + #[cfg(feature = "igvm")] + cpu_manager, + #[cfg(feature = "sev_snp")] + false, + )? + } else { + None + }; + + // Create boot vCPUs + cpu_manager + .lock() + .unwrap() + .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) + .map_err(Error::CpuManager)?; + + // KVM-specific initialization + #[cfg(feature = "kvm")] + if is_kvm { + Self::init_kvm( + vm, + device_manager, + console_info.cloned(), + console_resize_pipe.cloned(), + original_termios.clone(), + )?; + } + + // Create fw_cfg device if configured + #[cfg(feature = "fw_cfg")] + Self::create_fw_cfg_if_enabled(config, device_manager)?; + + Ok(load_payload_handle) + } + + /// Initialize SEV-SNP specific components. + #[cfg(feature = "sev_snp")] + #[allow(clippy::too_many_arguments)] + fn init_sev_snp( + vm: &Arc, + memory_manager: &Arc>, + cpu_manager: &Arc>, + device_manager: &Arc>, + config: &Arc>, + console_info: Option<&ConsoleInfo>, + console_resize_pipe: Option<&Arc>, + original_termios: &Arc>>, + snapshot: Option<&Snapshot>, + ) -> Result>>> { + // Create boot vCPUs before SEV-SNP initialization + cpu_manager + .lock() + .unwrap() + .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) + .map_err(Error::CpuManager)?; + + // Initialize SEV-SNP - transitions guest into secure state + vm.sev_snp_init().map_err(Error::InitializeSevSnpVm)?; + + // Load payload for SEV-SNP (IGVM parser needs cpu_manager for cpuid) + let load_payload_handle = if snapshot.is_none() { + Self::load_payload_async( + memory_manager, + config, + #[cfg(feature = "igvm")] + cpu_manager, + true, + )? + } else { + None + }; + + // Create interrupt controller and devices for MSHV + let ic = device_manager + .lock() + .unwrap() + .create_interrupt_controller() + .map_err(Error::DeviceManager)?; + + #[cfg(target_arch = "aarch64")] + vm.init().map_err(Error::InitializeVm)?; + + device_manager + .lock() + .unwrap() + .create_devices( + console_info.cloned(), + console_resize_pipe.cloned(), + original_termios.clone(), + ic, + ) + .map_err(Error::DeviceManager)?; + + Ok(load_payload_handle) + } + + /// Initialize MSHV-specific components. + #[cfg(feature = "mshv")] + fn init_mshv( + _vm: &Arc, + device_manager: &Arc>, + console_info: Option<&ConsoleInfo>, + console_resize_pipe: Option<&Arc>, + original_termios: &Arc>>, + ) -> Result<()> { + let ic = device_manager + .lock() + .unwrap() + .create_interrupt_controller() + .map_err(Error::DeviceManager)?; + + #[cfg(target_arch = "aarch64")] + _vm.init().map_err(Error::InitializeVm)?; + + device_manager + .lock() + .unwrap() + .create_devices( + console_info.cloned(), + console_resize_pipe.cloned(), + original_termios.clone(), + ic, + ) + .map_err(Error::DeviceManager)?; + + Ok(()) + } + + /// Initialize KVM-specific components. + #[cfg(feature = "kvm")] + fn init_kvm( + vm: &Arc, + device_manager: &Arc>, + console_info: Option, + console_resize_pipe: Option>, + original_termios: Arc>>, + ) -> Result<()> { + // For KVM, create interrupt controller after boot vcpus + // because GIC state is restored from snapshot during vcpu creation + let ic = device_manager + .lock() + .unwrap() + .create_interrupt_controller() + .map_err(Error::DeviceManager)?; + + vm.init().map_err(Error::InitializeVm)?; + + device_manager + .lock() + .unwrap() + .create_devices(console_info, console_resize_pipe, original_termios, ic) + .map_err(Error::DeviceManager)?; + + Ok(()) + } + + /// Create fw_cfg device if enabled in configuration. + #[cfg(feature = "fw_cfg")] + fn create_fw_cfg_if_enabled( + config: &Arc>, + device_manager: &Arc>, + ) -> Result<()> { + let fw_cfg_enabled = config + .lock() + .unwrap() + .payload + .as_ref() + .is_some_and(|p| p.fw_cfg_config.is_some()); + + if fw_cfg_enabled { + device_manager + .lock() + .unwrap() + .create_fw_cfg_device() + .map_err(Error::DeviceManager)?; + } + + Ok(()) + } + #[cfg(feature = "fw_cfg")] fn populate_fw_cfg( fw_cfg_config: &FwCfgConfig, From 7095605d84e7ded3a2415c42ec40f389b9b7403f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 29 Jan 2026 15:47:41 +0100 Subject: [PATCH 0547/1893] 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 --- block/src/async_io.rs | 8 ++++++++ block/src/fixed_vhd_async.rs | 12 ++++++++++++ block/src/fixed_vhd_sync.rs | 12 ++++++++++++ block/src/qcow_sync.rs | 14 +++++++++++++- block/src/raw_async.rs | 12 ++++++++++++ block/src/raw_async_aio.rs | 12 ++++++++++++ block/src/raw_sync.rs | 12 ++++++++++++ block/src/vhdx_sync.rs | 14 +++++++++++++- 8 files changed, 94 insertions(+), 2 deletions(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index bd4c7bbe5d..f859f6e5b4 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -96,6 +96,12 @@ pub enum AsyncIoError { /// Failed synchronizing file. #[error("Failed synchronizing file")] Fsync(#[source] std::io::Error), + /// Failed punching hole. + #[error("Failed punching hole")] + PunchHole(#[source] std::io::Error), + /// Failed writing zeroes. + #[error("Failed writing zeroes")] + WriteZeroes(#[source] std::io::Error), /// Failed submitting batch requests. #[error("Failed submitting batch requests")] SubmitBatchRequests(#[source] std::io::Error), @@ -118,6 +124,8 @@ pub trait AsyncIo: Send { user_data: u64, ) -> AsyncIoResult<()>; fn fsync(&mut self, user_data: Option) -> AsyncIoResult<()>; + fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()>; + fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()>; fn next_completed_request(&mut self) -> Option<(u64, i32)>; fn batch_requests_enabled(&self) -> bool { false diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index df596a6165..6e858f74a5 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -115,6 +115,18 @@ impl AsyncIo for FixedVhdAsync { self.raw_file_async.next_completed_request() } + fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::PunchHole(std::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(std::io::Error::other( + "write_zeroes not supported for fixed VHD", + ))) + } + fn batch_requests_enabled(&self) -> bool { true } diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index fd44adfd05..ecb5e83ad0 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -113,4 +113,16 @@ impl AsyncIo for FixedVhdSync { fn next_completed_request(&mut self) -> Option<(u64, i32)> { self.raw_file_sync.next_completed_request() } + + fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::PunchHole(std::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(std::io::Error::other( + "write_zeroes not supported for fixed VHD", + ))) + } } diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 200fb36bd1..ad9fb42d88 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex}; use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ - AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, + AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; use crate::qcow::{QcowFile, RawFile, Result as QcowResult}; use crate::{AsyncAdaptor, BlockBackend}; @@ -146,4 +146,16 @@ impl AsyncIo for QcowSync { fn next_completed_request(&mut self) -> Option<(u64, i32)> { self.completion_list.pop_front() } + + fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::PunchHole(std::io::Error::other( + "punch_hole not supported for QCOW sync backend", + ))) + } + + fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::WriteZeroes(std::io::Error::other( + "write_zeroes not supported for QCOW sync backend", + ))) + } } diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index e36f249e82..165f5946e6 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -253,4 +253,16 @@ impl AsyncIo for RawFileAsync { Ok(()) } + + fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::PunchHole(std::io::Error::other( + "punch_hole not supported for raw async backend", + ))) + } + + fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::WriteZeroes(std::io::Error::other( + "write_zeroes not supported for raw async backend", + ))) + } } diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index ad41872c74..6d9fab027a 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -161,4 +161,16 @@ impl AsyncIo for RawFileAsyncAio { Some((events[0].data, events[0].res as i32)) } } + + fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::PunchHole(std::io::Error::other( + "punch_hole not supported with AIO backend", + ))) + } + + fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::WriteZeroes(std::io::Error::other( + "write_zeroes not supported with AIO backend", + ))) + } } diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 0f9ce07025..aeeb3bea62 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -146,4 +146,16 @@ impl AsyncIo for RawFileSync { fn next_completed_request(&mut self) -> Option<(u64, i32)> { self.completion_list.pop_front() } + + fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::PunchHole(std::io::Error::other( + "punch_hole not supported for raw sync backend", + ))) + } + + fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::WriteZeroes(std::io::Error::other( + "write_zeroes not supported for raw sync backend", + ))) + } } diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 47e7539dcb..fc236c15df 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -9,7 +9,7 @@ use std::os::fd::AsRawFd; use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ - AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, + AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; use crate::vhdx::{Result as VhdxResult, Vhdx}; use crate::{AsyncAdaptor, BlockBackend, Error}; @@ -114,4 +114,16 @@ impl AsyncIo for VhdxSync { fn next_completed_request(&mut self) -> Option<(u64, i32)> { self.completion_list.pop_front() } + + fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { + Err(AsyncIoError::PunchHole(std::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(std::io::Error::other( + "write_zeroes not supported for VHDX", + ))) + } } From d5dad48618298141234e32feccf94ba03f934b4e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 13 Feb 2026 16:30:15 +0100 Subject: [PATCH 0548/1893] 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 --- block/src/lib.rs | 105 ++++++++++++++++++++++++++++++++++++- vmm/src/seccomp_filters.rs | 6 +++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 72210302a1..f6c990ce72 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -42,7 +42,7 @@ use std::{cmp, result}; #[cfg(feature = "io_uring")] use io_uring::{IoUring, Probe, opcode}; use libc::{S_IFBLK, S_IFMT, ioctl}; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use thiserror::Error; @@ -684,6 +684,109 @@ pub fn block_io_uring_is_supported() -> bool { } } +/// Probe whether the file/device supports punch hole and zero range +pub fn probe_sparse_support(file: &File) -> bool { + let fd = file.as_raw_fd(); + + let is_block_device = { + let mut stat = std::mem::MaybeUninit::::uninit(); + // SAFETY: FFI call with valid fd and buffer + let ret = unsafe { libc::fstat(fd, stat.as_mut_ptr()) }; + if ret != 0 { + warn!( + "Failed to stat file descriptor for sparse probe: {}", + io::Error::last_os_error() + ); + return false; + } + // SAFETY: stat result is valid at this point + unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK } + }; + + if is_block_device { + probe_block_device_sparse_support(fd) + } else { + probe_file_sparse_support(fd) + } +} + +/// Probe sparse support for a regular file using fallocate(). +fn probe_file_sparse_support(fd: libc::c_int) -> bool { + const FALLOC_FL_KEEP_SIZE: libc::c_int = 0x01; + const FALLOC_FL_PUNCH_HOLE: libc::c_int = 0x02; + const FALLOC_FL_ZERO_RANGE: libc::c_int = 0x10; + + // SAFETY: FFI call with valid fd + let file_size = unsafe { libc::lseek(fd, 0, libc::SEEK_END) }; + if file_size < 0 { + let err = io::Error::last_os_error(); + warn!("Failed to get file size for sparse probe: {err}"); + return false; + } + + // SAFETY: FFI call with valid fd, probing past EOF is safe with KEEP_SIZE + let punch_hole = + unsafe { libc::fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, file_size, 1) } + == 0; + + if !punch_hole { + let err = io::Error::last_os_error(); + if err.raw_os_error() == Some(libc::EOPNOTSUPP) { + debug!("File does not support FALLOC_FL_PUNCH_HOLE: {err}"); + } else { + debug!("PUNCH_HOLE probe returned unexpected error: {err}"); + } + } + + // SAFETY: FFI call with valid fd, probing past EOF is safe with KEEP_SIZE + let zero_range = + unsafe { libc::fallocate(fd, FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE, file_size, 1) } + == 0; + + if !zero_range { + let err = io::Error::last_os_error(); + if err.raw_os_error() == Some(libc::EOPNOTSUPP) { + debug!("File does not support FALLOC_FL_ZERO_RANGE: {err}"); + } + } + + let supported = punch_hole || zero_range; + info!( + "Probed file sparse support: punch_hole={punch_hole}, zero_range={zero_range} => {supported}" + ); + supported +} + +/// Probe sparse support for a block device using ioctls. +fn probe_block_device_sparse_support(fd: libc::c_int) -> bool { + ioctl_io_nr!(BLKDISCARD, 0x12, 119); + ioctl_io_nr!(BLKZEROOUT, 0x12, 127); + + let range: [u64; 2] = [0, 0]; + + // SAFETY: FFI call with valid fd and valid range buffer + let punch_hole = unsafe { ioctl(fd, BLKDISCARD() as _, &range) } == 0; + + if !punch_hole { + let err = io::Error::last_os_error(); + debug!("Block device BLKDISCARD probe returned: {err}"); + } + + // SAFETY: FFI call with valid fd and valid range buffer + let zero_range = unsafe { ioctl(fd, BLKZEROOUT() as _, &range) } == 0; + + if !zero_range { + let err = io::Error::last_os_error(); + debug!("Block device BLKZEROOUT probe returned: {err}"); + } + + let supported = punch_hole || zero_range; + info!( + "Probed block device sparse support: punch_hole={punch_hole}, zero_range={zero_range} => {supported}" + ); + supported +} + pub trait AsyncAdaptor { fn read_vectored_sync( &mut self, diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 55e1531113..bb40b99f60 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -108,6 +108,10 @@ mod kvm { pub const KVM_SET_NESTED_STATE: u64 = 1082175167; } +// Block device ioctls for sparse support probing (not exported by libc) +const BLKDISCARD: u64 = 0x1277; // _IO(0x12, 119) +const BLKZEROOUT: u64 = 0x127f; // _IO(0x12, 127) + // MSHV IOCTL code. This is unstable until the kernel code has been declared stable. #[cfg(feature = "mshv")] use hypervisor::mshv::mshv_ioctls::*; @@ -259,6 +263,8 @@ fn create_vmm_ioctl_seccomp_rule_common( and![Cond::new(1, ArgLen::Dword, Eq, BLKPBSZGET as _)?], and![Cond::new(1, ArgLen::Dword, Eq, BLKIOMIN as _)?], and![Cond::new(1, ArgLen::Dword, Eq, BLKIOOPT as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, BLKDISCARD as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, BLKZEROOUT as _)?], and![Cond::new(1, ArgLen::Dword, Eq, FIOCLEX as _)?], and![Cond::new(1, ArgLen::Dword, Eq, FIONBIO as _)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFFLAGS)?], From 7f4b56b217fab1bb90a70fdb960b97a9c0263688 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 29 Jan 2026 21:49:42 +0100 Subject: [PATCH 0549/1893] 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 --- block/src/async_io.rs | 6 ++++++ block/src/qcow_sync.rs | 4 ++++ block/src/raw_async.rs | 6 +++++- block/src/raw_async_aio.rs | 6 +++++- block/src/raw_sync.rs | 6 +++++- virtio-devices/src/block.rs | 7 +++++++ 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index f859f6e5b4..3727f0d957 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -78,6 +78,12 @@ pub trait DiskFile: Send { Err(DiskFileError::Unsupported) } + /// Indicates support for sparse operations (punch hole, write zeroes, discard). + /// Override to return true when supported. + fn supports_sparse_operations(&self) -> bool { + false + } + /// Returns the file descriptor of the underlying disk image file. /// /// The file descriptor is supposed to be used for `fcntl()` calls but no diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index ad9fb42d88..189254bfd1 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -76,6 +76,10 @@ impl DiskFile for QcowDiskSync { .map_err(|e| DiskFileError::ResizeError(io::Error::other(e))) } + fn supports_sparse_operations(&self) -> bool { + true + } + fn fd(&mut self) -> BorrowedDiskFd<'_> { BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd()) } diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 165f5946e6..7d070ceb45 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -13,7 +13,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::{BatchRequest, DiskTopology, RequestType}; +use crate::{BatchRequest, DiskTopology, RequestType, probe_sparse_support}; pub struct RawFileDisk { file: File, @@ -59,6 +59,10 @@ impl DiskFile for RawFileDisk { self.file.set_len(size).map_err(DiskFileError::ResizeError) } + fn supports_sparse_operations(&self) -> bool { + probe_sparse_support(&self.file) + } + fn fd(&mut self) -> BorrowedDiskFd<'_> { BorrowedDiskFd::new(self.file.as_raw_fd()) } diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 6d9fab027a..6447a727d8 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -13,10 +13,10 @@ use log::warn; use vmm_sys_util::aio; use vmm_sys_util::eventfd::EventFd; -use crate::DiskTopology; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; +use crate::{DiskTopology, probe_sparse_support}; pub struct RawFileDiskAio { file: File, @@ -58,6 +58,10 @@ impl DiskFile for RawFileDiskAio { } } + fn supports_sparse_operations(&self) -> bool { + probe_sparse_support(&self.file) + } + fn fd(&mut self) -> BorrowedDiskFd<'_> { BorrowedDiskFd::new(self.file.as_raw_fd()) } diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index aeeb3bea62..b540eb0ec6 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -10,10 +10,10 @@ use std::os::unix::io::{AsRawFd, RawFd}; use log::warn; use vmm_sys_util::eventfd::EventFd; -use crate::DiskTopology; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; +use crate::{DiskTopology, probe_sparse_support}; pub struct RawFileDiskSync { file: File, @@ -52,6 +52,10 @@ impl DiskFile for RawFileDiskSync { } } + fn supports_sparse_operations(&self) -> bool { + probe_sparse_support(&self.file) + } + fn fd(&mut self) -> BorrowedDiskFd<'_> { BorrowedDiskFd::new(self.file.as_raw_fd()) } diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index d3a0e6df30..7de45bc1b0 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -702,6 +702,13 @@ impl Block { | (1u64 << VIRTIO_BLK_F_SEG_MAX) | (1u64 << VIRTIO_RING_F_EVENT_IDX) | (1u64 << VIRTIO_RING_F_INDIRECT_DESC); + + // Only advertise discard/write zeroes if the backend supports sparse operations + if disk_image.supports_sparse_operations() { + avail_features |= + (1u64 << VIRTIO_BLK_F_DISCARD) | (1u64 << VIRTIO_BLK_F_WRITE_ZEROES); + } + if iommu { avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; } From 7dfcbff309887507022219cabbd711478fa27e0c Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 30 Jan 2026 16:18:29 +0100 Subject: [PATCH 0550/1893] 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 --- fuzz/fuzz_targets/block.rs | 1 + virtio-devices/src/block.rs | 1 + vmm/src/config.rs | 12 ++++++++++-- vmm/src/device_manager.rs | 1 + vmm/src/vm_config.rs | 6 ++++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index c06f0dea23..be461d7a7a 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -67,6 +67,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { EventFd::new(EFD_NONBLOCK).unwrap(), None, queue_affinity, + true, ) .unwrap(); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 7de45bc1b0..e05900b5a4 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -672,6 +672,7 @@ impl Block { exit_evt: EventFd, state: Option, queue_affinity: BTreeMap>, + _sparse: bool, ) -> io::Result { let (disk_nsectors, avail_features, acked_features, config, paused) = if let Some(state) = state { diff --git a/vmm/src/config.rs b/vmm/src/config.rs index abadbb9367..8a8d637fd2 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1096,7 +1096,7 @@ impl DiskConfig { ops_size=,ops_one_time_burst=,ops_refill_time=,\ id=,pci_segment=,rate_limit_group=,\ queue_affinity=,\ - serial=,backing_files=on|off"; + serial=,backing_files=on|off,sparse=on|off"; pub fn parse(disk: &str) -> Result { let mut parser = OptionParser::new(); @@ -1122,7 +1122,8 @@ impl DiskConfig { .add("serial") .add("rate_limit_group") .add("queue_affinity") - .add("backing_files"); + .add("backing_files") + .add("sparse"); parser.parse(disk).map_err(Error::ParseDisk)?; let path = parser.get("path").map(PathBuf::from); @@ -1239,6 +1240,11 @@ impl DiskConfig { } else { None }; + let sparse = parser + .convert::("sparse") + .map_err(Error::ParseDisk)? + .unwrap_or_else(|| Toggle(default_diskconfig_sparse())) + .0; Ok(DiskConfig { path, @@ -1258,6 +1264,7 @@ impl DiskConfig { serial, queue_affinity, backing_files, + sparse, }) } @@ -3508,6 +3515,7 @@ mod unit_tests { serial: None, queue_affinity: None, backing_files: false, + sparse: true, } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 0a88b44008..a78e2ab2fb 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2799,6 +2799,7 @@ impl DeviceManager { state_from_id(self.snapshot.as_ref(), id.as_str()) .map_err(DeviceManagerError::RestoreGetState)?, queue_affinity, + disk_cfg.sparse, ) .map_err(DeviceManagerError::CreateVirtioBlock)?; diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index e0e74cb4ab..407d4e491d 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -286,6 +286,8 @@ pub struct DiskConfig { pub queue_affinity: Option>, #[serde(default)] pub backing_files: bool, + #[serde(default = "default_diskconfig_sparse")] + pub sparse: bool, } impl ApplyLandlock for DiskConfig { @@ -309,6 +311,10 @@ pub fn default_diskconfig_queue_size() -> u16 { DEFAULT_DISK_QUEUE_SIZE } +pub fn default_diskconfig_sparse() -> bool { + true +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct NetConfig { #[serde(default = "default_netconfig_tap")] From 46e6ecddfe6c689f5c80ef3d9a6ee826edf8f121 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 2 Feb 2026 00:04:20 +0100 Subject: [PATCH 0551/1893] 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 --- block/src/async_io.rs | 6 ++++++ block/src/qcow_sync.rs | 4 ++++ virtio-devices/src/block.rs | 15 +++++++++++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index 3727f0d957..a1e8fa3e46 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -84,6 +84,12 @@ pub trait DiskFile: Send { false } + /// Indicates support for zero flag optimization in WRITE_ZEROES. Override + /// to return true when supported. + fn supports_zero_flag(&self) -> bool { + false + } + /// Returns the file descriptor of the underlying disk image file. /// /// The file descriptor is supposed to be used for `fcntl()` calls but no diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 189254bfd1..6730fccb0c 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -80,6 +80,10 @@ impl DiskFile for QcowDiskSync { true } + fn supports_zero_flag(&self) -> bool { + true + } + fn fd(&mut self) -> BorrowedDiskFd<'_> { BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd()) } diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index e05900b5a4..a7da43e6ce 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -672,7 +672,7 @@ impl Block { exit_evt: EventFd, state: Option, queue_affinity: BTreeMap>, - _sparse: bool, + sparse: bool, ) -> io::Result { let (disk_nsectors, avail_features, acked_features, config, paused) = if let Some(state) = state { @@ -704,10 +704,17 @@ impl Block { | (1u64 << VIRTIO_RING_F_EVENT_IDX) | (1u64 << VIRTIO_RING_F_INDIRECT_DESC); - // Only advertise discard/write zeroes if the backend supports sparse operations + // When backend supports sparse operations: + // - Always advertise WRITE_ZEROES + // - Advertise DISCARD only if sparse=true OR format supports marking + // clusters as zero without deallocating if disk_image.supports_sparse_operations() { - avail_features |= - (1u64 << VIRTIO_BLK_F_DISCARD) | (1u64 << VIRTIO_BLK_F_WRITE_ZEROES); + avail_features |= 1u64 << VIRTIO_BLK_F_WRITE_ZEROES; + if sparse || disk_image.supports_zero_flag() { + avail_features |= 1u64 << VIRTIO_BLK_F_DISCARD; + } + } else if sparse { + warn!("sparse=on requested but backend does not support sparse operations"); } if iommu { From 0a287793df6405e2ec7ef0a673fb20db5290f33a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 1 Feb 2026 23:35:03 +0100 Subject: [PATCH 0552/1893] 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 --- block/src/qcow/mod.rs | 58 +++++++++++++++++++++++++-------------- block/src/qcow_sync.rs | 24 +++++++--------- vmm/src/device_manager.rs | 9 ++++-- 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 12a6c93d87..76bca46469 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -35,7 +35,7 @@ use crate::qcow::refcount::RefCount; use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache}; /// Nesting depth limit for disk formats that can open other disk files. -const MAX_NESTING_DEPTH: u32 = 10; +pub(super) const MAX_NESTING_DEPTH: u32 = 10; #[sorted] #[derive(Debug, Error)] @@ -788,6 +788,7 @@ impl BackingFile { backing_file_config: Option<&BackingFileConfig>, direct_io: bool, max_nesting_depth: u32, + sparse: bool, ) -> Result> { let Some(config) = backing_file_config else { return Ok(None); @@ -821,7 +822,7 @@ impl BackingFile { } ImageType::Qcow2 => { let backing_qcow = - QcowFile::from_with_nesting_depth(raw_file, max_nesting_depth - 1) + QcowFile::from_with_nesting_depth(raw_file, max_nesting_depth - 1, sparse) .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; let size = backing_qcow.virtual_size(); (Box::new(backing_qcow), size) @@ -902,6 +903,8 @@ pub struct QcowFile { // removal of references to them have been synced to disk. avail_clusters: Vec, backing_file: Option, + #[allow(dead_code)] // Used in later commits for sparse-aware deallocation + sparse: bool, } impl QcowFile { @@ -909,12 +912,16 @@ impl QcowFile { /// /// Additionally, max nesting depth of this qcow2 image will be set to default value 10. pub fn from(file: RawFile) -> Result { - Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH) + Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH, true) } /// Creates a QcowFile from `file` and with a max nesting depth. File must be a valid qcow2 /// image. - pub fn from_with_nesting_depth(mut file: RawFile, max_nesting_depth: u32) -> Result { + pub fn from_with_nesting_depth( + mut file: RawFile, + max_nesting_depth: u32, + sparse: bool, + ) -> Result { let header = QcowHeader::new(&mut file)?; // Only v2 and v3 files are supported. @@ -940,8 +947,12 @@ impl QcowFile { let direct_io = file.is_direct(); - let backing_file = - BackingFile::new(header.backing_file.as_ref(), direct_io, max_nesting_depth)?; + let backing_file = BackingFile::new( + header.backing_file.as_ref(), + direct_io, + max_nesting_depth, + sparse, + )?; // Validate refcount order to be 0..6 let refcount_bits: u64 = 0x01u64 @@ -1064,6 +1075,7 @@ impl QcowFile { unref_clusters: Vec::new(), avail_clusters: Vec::new(), backing_file, + sparse, }; // Check that the L1 and refcount tables fit in a 64bit address space. @@ -1093,9 +1105,9 @@ impl QcowFile { } /// Creates a new QcowFile at the given path. - pub fn new(file: RawFile, version: u32, virtual_size: u64) -> Result { + pub fn new(file: RawFile, version: u32, virtual_size: u64, sparse: bool) -> Result { let header = QcowHeader::create_for_size_and_path(version, virtual_size, None)?; - QcowFile::new_from_header(file, &header) + QcowFile::new_from_header(file, &header, sparse) } /// Creates a new QcowFile at the given path with a backing file. @@ -1104,6 +1116,7 @@ impl QcowFile { version: u32, backing_file_size: u64, backing_config: &BackingFileConfig, + sparse: bool, ) -> Result { let mut header = QcowHeader::create_for_size_and_path( version, @@ -1113,15 +1126,15 @@ impl QcowFile { if let Some(backing_file) = &mut header.backing_file { backing_file.format = backing_config.format; } - QcowFile::new_from_header(file, &header) + QcowFile::new_from_header(file, &header, sparse) // backing_file is loaded by new_from_header -> Self::from() based on the header } - fn new_from_header(mut file: RawFile, header: &QcowHeader) -> Result { + fn new_from_header(mut file: RawFile, header: &QcowHeader, sparse: bool) -> Result { file.rewind().map_err(Error::SeekingFile)?; header.write_to(&mut file)?; - let mut qcow = Self::from(file)?; + let mut qcow = Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH, sparse)?; // Set the refcount for each refcount table cluster. let cluster_size = 0x01u64 << qcow.header.cluster_bits; @@ -2597,7 +2610,7 @@ where match dst_type { ImageType::Qcow2 => { - let mut dst_writer = QcowFile::new(dst_file, 3, src_size)?; + let mut dst_writer = QcowFile::new(dst_file, 3, src_size, true)?; convert_reader_writer(reader, &mut dst_writer, src_size) } ImageType::Raw => { @@ -2625,7 +2638,7 @@ pub fn convert( match src_type { ImageType::Qcow2 => { let mut src_reader = - QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth)?; + QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth, true)?; convert_reader(&mut src_reader, dst_file, dst_type) } ImageType::Raw => { @@ -2747,7 +2760,7 @@ mod unit_tests { F: FnMut(QcowFile), { let tmp: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), direct); - let qcow_file = QcowFile::new(tmp, 3, file_size).unwrap(); + let qcow_file = QcowFile::new(tmp, 3, file_size, true).unwrap(); testfn(qcow_file); // File closed when the function exits. } @@ -2987,7 +3000,7 @@ mod unit_tests { .expect("Failed to write header to shm."); disk_file.rewind().unwrap(); // The maximum nesting depth is 0, which means backing file is not allowed. - QcowFile::from_with_nesting_depth(disk_file, 0).unwrap(); + QcowFile::from_with_nesting_depth(disk_file, 0, true).unwrap(); } #[test] @@ -3002,7 +3015,7 @@ mod unit_tests { .expect("Failed to write header to shm."); disk_file.rewind().unwrap(); // The maximum nesting depth is 0, which means backing file is not allowed. - let res = QcowFile::from_with_nesting_depth(disk_file, 0); + let res = QcowFile::from_with_nesting_depth(disk_file, 0, true); assert!(matches!(res.unwrap_err(), Error::MaxNestingDepthExceeded)); } @@ -3033,6 +3046,7 @@ mod unit_tests { false, ), MAX_NESTING_DEPTH, + true, ) .expect_err("Opening qcow file with itself as backing file should fail."); @@ -3450,7 +3464,7 @@ mod unit_tests { let new_size = 600 * 1024 * 1024; // 600 MB let tmp: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false); - let mut q = QcowFile::new(tmp, 3, initial_size).unwrap(); + let mut q = QcowFile::new(tmp, 3, initial_size, true).unwrap(); let original_l1_size = q.header().l1_size; assert_eq!(q.virtual_size(), initial_size); @@ -3503,7 +3517,7 @@ mod unit_tests { { let backing_raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); - let _backing_qcow = QcowFile::new(backing_raw, 3, backing_size).unwrap(); + let _backing_qcow = QcowFile::new(backing_raw, 3, backing_size, true).unwrap(); } let overlay_file = TempFile::new().unwrap(); @@ -3513,7 +3527,8 @@ mod unit_tests { format: Some(ImageType::Qcow2), }; let mut overlay = - QcowFile::new_from_backing(overlay_raw, 3, backing_size, &backing_config).unwrap(); + QcowFile::new_from_backing(overlay_raw, 3, backing_size, &backing_config, true) + .unwrap(); assert_eq!(overlay.virtual_size(), backing_size); @@ -3532,7 +3547,7 @@ mod unit_tests { { let backing_raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); - let mut backing_qcow = QcowFile::new(backing_raw, 3, backing_size).unwrap(); + let mut backing_qcow = QcowFile::new(backing_raw, 3, backing_size, true).unwrap(); let data = b"BACKING_DATA"; backing_qcow.rewind().unwrap(); backing_qcow.write_all(data).unwrap(); @@ -3552,7 +3567,8 @@ mod unit_tests { }; let overlay_size = backing_size * 2; // 2x the backing size let mut overlay = - QcowFile::new_from_backing(overlay_raw, 3, overlay_size, &backing_config).unwrap(); + QcowFile::new_from_backing(overlay_raw, 3, overlay_size, &backing_config, true) + .unwrap(); assert_eq!(overlay.virtual_size(), overlay_size); diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 6730fccb0c..a68a3c6174 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -13,7 +13,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::qcow::{QcowFile, RawFile, Result as QcowResult}; +use crate::qcow::{MAX_NESTING_DEPTH, QcowFile, RawFile, Result as QcowResult}; use crate::{AsyncAdaptor, BlockBackend}; pub struct QcowDiskSync { @@ -29,19 +29,15 @@ pub struct QcowDiskSync { } impl QcowDiskSync { - pub fn new(file: File, direct_io: bool, backing_files: bool) -> QcowResult { - if backing_files { - Ok(QcowDiskSync { - qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)), - }) - } else { - Ok(QcowDiskSync { - qcow_file: Arc::new(Mutex::new(QcowFile::from_with_nesting_depth( - RawFile::new(file, direct_io), - 0, - )?)), - }) - } + pub fn new(file: File, direct_io: bool, backing_files: bool, sparse: bool) -> QcowResult { + let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 }; + Ok(QcowDiskSync { + qcow_file: Arc::new(Mutex::new(QcowFile::from_with_nesting_depth( + RawFile::new(file, direct_io), + max_nesting_depth, + sparse, + )?)), + }) } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index a78e2ab2fb..e476cc7a9f 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2724,8 +2724,13 @@ impl DeviceManager { ImageType::Qcow2 => { info!("Using synchronous QCOW2 disk file"); Box::new( - QcowDiskSync::new(file, disk_cfg.direct, disk_cfg.backing_files) - .map_err(DeviceManagerError::CreateQcowDiskSync)?, + QcowDiskSync::new( + file, + disk_cfg.direct, + disk_cfg.backing_files, + disk_cfg.sparse, + ) + .map_err(DeviceManagerError::CreateQcowDiskSync)?, ) as Box } ImageType::Vhdx => { From 49a30cbbaf7e4dc8e5a1de3e99dad560c9d90d2d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 30 Jan 2026 18:57:33 +0100 Subject: [PATCH 0553/1893] 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 --- block/src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ vmm/src/device_manager.rs | 11 ++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index f6c990ce72..f718f3f82a 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -787,6 +787,42 @@ fn probe_block_device_sparse_support(fd: libc::c_int) -> bool { supported } +/// Preallocate disk space for a disk image file. +/// +/// Uses `fallocate()` to allocate all disk space upfront, ensuring storage +/// availability and reducing fragmentation. Allocating all blocks upfront is +/// more likely to place them contiguously than allocating on demand during +/// random writes. +pub fn preallocate_disk>(file: &File, path: P) { + let size = match file.metadata() { + Ok(m) => m.len(), + Err(e) => { + warn!("Failed to get metadata for {:?}: {}", path.as_ref(), e); + return; + } + }; + + if size == 0 { + return; + } + + // SAFETY: FFI call with valid file descriptor and size + let ret = unsafe { libc::fallocate(file.as_raw_fd(), 0, 0, size as libc::off_t) }; + + if ret != 0 { + warn!( + "Failed to preallocate disk space for {:?}: {}", + path.as_ref(), + io::Error::last_os_error() + ); + } else { + debug!( + "Preallocated {size} bytes for disk image {:?}", + path.as_ref() + ); + } +} + pub trait AsyncAdaptor { fn read_vectored_sync( &mut self, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index e476cc7a9f..842f529c81 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -39,7 +39,8 @@ use block::raw_async_aio::RawFileDiskAio; use block::raw_sync::RawFileDiskSync; use block::vhdx_sync::VhdxDiskSync; use block::{ - ImageType, block_aio_is_supported, block_io_uring_is_supported, detect_image_type, qcow, vhdx, + ImageType, block_aio_is_supported, block_io_uring_is_supported, detect_image_type, + preallocate_disk, qcow, vhdx, }; #[cfg(feature = "io_uring")] use block::{fixed_vhd_async::FixedVhdDiskAsync, raw_async::RawFileDisk}; @@ -2699,6 +2700,14 @@ impl DeviceManager { } } ImageType::Raw => { + // For non-sparse RAW disks, preallocate disk space + if !disk_cfg.readonly + && !disk_cfg.sparse + && let Some(path) = &disk_cfg.path + { + preallocate_disk(&file, path); + } + // Use asynchronous backend relying on io_uring if the // syscalls are supported. if cfg!(feature = "io_uring") From e913d0d9cb1c0e7a2ec5a7bf28c10d9833df26f8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 29 Jan 2026 15:02:30 +0100 Subject: [PATCH 0554/1893] 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 --- block/src/qcow/mod.rs | 135 +++++++++++++++++++++++++++--------------- 1 file changed, 87 insertions(+), 48 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 76bca46469..e1e70ccb9b 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -336,6 +336,11 @@ 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 +fn l2_entry_make_zero(cluster_addr: u64) -> u64 { + (cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG | ZERO_FLAG +} + // Make L1 entry with optional flags 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) @@ -903,7 +908,6 @@ pub struct QcowFile { // removal of references to them have been synced to disk. avail_clusters: Vec, backing_file: Option, - #[allow(dead_code)] // Used in later commits for sparse-aware deallocation sparse: bool, } @@ -1868,9 +1872,6 @@ impl QcowFile { let cluster_addr = if l2_entry_is_compressed(l2_entry) { // Writing to compressed cluster. - let (compressed_cluster_addr, compressed_cluster_size) = - l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); - // Allocate new cluster, decompress into new cluster, then use // offset of new cluster. let decompressed_cluster = self.decompress_l2_cluster(l2_entry)?; @@ -1886,31 +1887,10 @@ impl QcowFile { } // Decrement refcount for each cluster spanned by the old compressed data - let compressed_clusters_end = self.raw_file.cluster_address( - compressed_cluster_addr // Start of compressed data - + compressed_cluster_size as u64 // Add size to get end address - + self.raw_file.cluster_size() - - 1, // Catch possibly partially used last cluster - ); - let mut addr = self.raw_file.cluster_address(compressed_cluster_addr); - while addr < compressed_clusters_end { - let refcount = self - .refcounts - .get_cluster_refcount(&mut self.raw_file, addr) - .map_err(|e| { - if matches!(e, refcount::Error::RefblockUnaligned(_)) { - self.set_corrupt_bit_best_effort(); - } - io::Error::other(Error::GettingRefcount(e)) - })?; - if refcount > 0 { - self.set_cluster_refcount_track_freed(addr, refcount - 1)?; - } - addr += self.raw_file.cluster_size(); - } + self.deallocate_compressed_cluster(l2_entry)?; cluster_addr - } else if l2_entry_is_empty(l2_entry) { + } else if l2_entry_is_empty(l2_entry) || l2_entry_is_zero(l2_entry) { let initial_data = if let Some(backing) = self.backing_file.as_mut() { let cluster_size = self.raw_file.cluster_size(); let cluster_begin = address - (address % cluster_size); @@ -2072,6 +2052,43 @@ impl QcowFile { Ok(None) } + // Deallocate compressed cluster and all related clusters spanned by compressed data. + fn deallocate_compressed_cluster(&mut self, l2_entry: u64) -> std::io::Result<()> { + let (compressed_cluster_addr, compressed_cluster_size) = + l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); + + // Calculate the end of the compressed data region + let compressed_clusters_end = self.raw_file.cluster_address( + compressed_cluster_addr // Start of compressed data + + compressed_cluster_size as u64 // Add size to get end address + + self.raw_file.cluster_size() + - 1, // Catch possibly partially used last cluster + ); + + // Decrement refcount for each cluster spanned by the compressed data + let mut addr = self.raw_file.cluster_address(compressed_cluster_addr); + while addr < compressed_clusters_end { + let refcount = self + .refcounts + .get_cluster_refcount(&mut self.raw_file, addr) + .map_err(|e| { + if matches!(e, refcount::Error::RefblockUnaligned(_)) { + self.set_corrupt_bit_best_effort(); + } + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed to get cluster refcount: {e}"), + ) + })?; + if refcount > 0 { + self.set_cluster_refcount_track_freed(addr, refcount - 1)?; + } + addr += self.raw_file.cluster_size(); + } + + Ok(()) + } + // Deallocate the storage for the cluster starting at `address`. // Any future reads of this cluster will return all zeroes. fn deallocate_cluster(&mut self, address: u64) -> std::io::Result<()> { @@ -2094,12 +2111,21 @@ impl QcowFile { self.cache_l2_cluster(l1_index, l2_addr_disk, false)?; - let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index]; - if cluster_addr == 0 { - // This cluster is already unallocated; nothing to do. + let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; + if l2_entry_is_empty(l2_entry) || l2_entry_is_zero(l2_entry) { + // Already unallocated or zero. + return Ok(()); + } + + // Compressed clusters cannot use the zero flag optimization, thus fully deallocate instead. + if l2_entry_is_compressed(l2_entry) { + self.deallocate_compressed_cluster(l2_entry)?; + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = 0; return Ok(()); } + let cluster_addr = l2_entry_std_cluster_addr(l2_entry); + // Decrement the refcount. let refcount = self .refcounts @@ -2117,23 +2143,38 @@ impl QcowFile { return Err(std::io::Error::from_raw_os_error(EINVAL)); } - let new_refcount = refcount - 1; - self.set_cluster_refcount_track_freed(cluster_addr, new_refcount)?; + if self.sparse { + // Fully deallocate to reclaim storage space. + let new_refcount = refcount - 1; + self.set_cluster_refcount_track_freed(cluster_addr, new_refcount)?; - // Rewrite the L2 entry to remove the cluster mapping. - // unwrap is safe as we just checked/inserted this entry. - self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = 0; + // Rewrite the L2 entry to remove the cluster mapping (full deallocation). + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = 0; - if new_refcount == 0 { - let cluster_size = self.raw_file.cluster_size(); - // This cluster is no longer in use; deallocate the storage. - // The underlying FS may not support FALLOC_FL_PUNCH_HOLE, - // so don't treat an error as fatal. Future reads will return zeros anyways. - let _ = self - .raw_file - .file_mut() - .punch_hole(cluster_addr, cluster_size); - self.unref_clusters.push(cluster_addr); + if new_refcount == 0 { + let cluster_size = self.raw_file.cluster_size(); + // This cluster is no longer in use; deallocate the storage. + // The underlying FS may not support FALLOC_FL_PUNCH_HOLE, + // so don't treat an error as fatal. Future reads will return zeros anyways. + let _ = self + .raw_file + .file_mut() + .punch_hole(cluster_addr, cluster_size); + self.unref_clusters.push(cluster_addr); + } + } else { + // Zero flag optimization - mark cluster as reading zeros without deallocating. + // Only safe if refcount == 1 (no other references to this cluster). + if refcount == 1 { + // Single reference - safe to use zero flag optimization + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = + l2_entry_make_zero(cluster_addr); + } else { + // Multiple references - must decrement refcount and unmap this entry. + // Cannot use zero flag because other L2 entries still need the real data. + self.set_cluster_refcount_track_freed(cluster_addr, refcount - 1)?; + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = 0; + } } Ok(()) } @@ -2369,9 +2410,7 @@ impl Read for QcowFile { backing.read_at(curr_addr, &mut buf[nread..(nread + count)])?; } else { // Previously unwritten region, return zeros - for b in &mut buf[nread..(nread + count)] { - *b = 0; - } + buf[nread..(nread + count)].fill(0); } nread += count; From 81c075b3178880a249b46528a872967b7c364d31 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 4 Feb 2026 15:54:48 +0100 Subject: [PATCH 0555/1893] 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 --- block/src/lib.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index f718f3f82a..0d248fb202 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -50,7 +50,7 @@ use virtio_bindings::virtio_blk::*; use virtio_queue::DescriptorChain; use vm_memory::bitmap::Bitmap; use vm_memory::{ - ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard, + Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard, }; use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; @@ -156,6 +156,10 @@ pub enum ExecuteError { AsyncWrite(#[source] AsyncIoError), #[error("failed to async flush")] AsyncFlush(#[source] AsyncIoError), + #[error("Failed to async punch hole")] + AsyncPunchHole(#[source] AsyncIoError), + #[error("Failed to async write zeroes")] + AsyncWriteZeroes(#[source] AsyncIoError), #[error("Failed allocating a temporary buffer")] TemporaryBufferAllocation(#[source] io::Error), } @@ -177,6 +181,8 @@ impl ExecuteError { ExecuteError::AsyncRead(_) => VIRTIO_BLK_S_IOERR, ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR, ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR, + ExecuteError::AsyncPunchHole(_) => VIRTIO_BLK_S_IOERR, + ExecuteError::AsyncWriteZeroes(_) => VIRTIO_BLK_S_IOERR, ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR, }; status as u8 @@ -189,6 +195,8 @@ pub enum RequestType { Out, Flush, GetDeviceId, + Discard, + WriteZeroes, Unsupported(u32), } @@ -202,6 +210,8 @@ pub fn request_type( VIRTIO_BLK_T_OUT => Ok(RequestType::Out), VIRTIO_BLK_T_FLUSH => Ok(RequestType::Flush), VIRTIO_BLK_T_GET_ID => Ok(RequestType::GetDeviceId), + VIRTIO_BLK_T_DISCARD => Ok(RequestType::Discard), + VIRTIO_BLK_T_WRITE_ZEROES => Ok(RequestType::WriteZeroes), t => Ok(RequestType::Unsupported(t)), } } @@ -299,6 +309,12 @@ impl Request { if desc.is_write_only() && req.request_type == RequestType::Out { return Err(Error::UnexpectedWriteOnlyDescriptor); } + if desc.is_write_only() && req.request_type == RequestType::Discard { + return Err(Error::UnexpectedWriteOnlyDescriptor); + } + if desc.is_write_only() && req.request_type == RequestType::WriteZeroes { + return Err(Error::UnexpectedWriteOnlyDescriptor); + } if !desc.is_write_only() && req.request_type == RequestType::In { return Err(Error::UnexpectedReadOnlyDescriptor); } @@ -395,6 +411,12 @@ impl Request { mem.write_slice(serial, *data_addr) .map_err(ExecuteError::Write)?; } + RequestType::Discard => { + return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_DISCARD)); + } + RequestType::WriteZeroes => { + return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_WRITE_ZEROES)); + } RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), } } @@ -544,6 +566,62 @@ impl Request { ret.async_complete = false; return Ok(ret); } + RequestType::Discard => { + let (data_addr, data_len) = if self.data_descriptors.len() == 1 { + (self.data_descriptors[0].0, self.data_descriptors[0].1) + } else { + return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); + }; + + if data_len < 16 { + return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); + } + + let mut discard_sector = [0u8; 8]; + let mut discard_num_sectors = [0u8; 4]; + mem.read_slice(&mut discard_sector, data_addr) + .map_err(ExecuteError::Read)?; + mem.read_slice(&mut discard_num_sectors, data_addr.checked_add(8).unwrap()) + .map_err(ExecuteError::Read)?; + + let discard_sector = u64::from_le_bytes(discard_sector); + let discard_num_sectors = u32::from_le_bytes(discard_num_sectors); + + let discard_offset = discard_sector * SECTOR_SIZE; + let discard_length = (discard_num_sectors as u64) * SECTOR_SIZE; + + disk_image + .punch_hole(discard_offset, discard_length, user_data) + .map_err(ExecuteError::AsyncPunchHole)?; + } + RequestType::WriteZeroes => { + let (data_addr, data_len) = if self.data_descriptors.len() == 1 { + (self.data_descriptors[0].0, self.data_descriptors[0].1) + } else { + return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); + }; + + if data_len < 16 { + return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); + } + + let mut wz_sector = [0u8; 8]; + let mut wz_num_sectors = [0u8; 4]; + mem.read_slice(&mut wz_sector, data_addr) + .map_err(ExecuteError::Read)?; + mem.read_slice(&mut wz_num_sectors, data_addr.checked_add(8).unwrap()) + .map_err(ExecuteError::Read)?; + + let wz_sector = u64::from_le_bytes(wz_sector); + let wz_num_sectors = u32::from_le_bytes(wz_num_sectors); + + let wz_offset = wz_sector * SECTOR_SIZE; + let wz_length = (wz_num_sectors as u64) * SECTOR_SIZE; + + disk_image + .write_zeroes(wz_offset, wz_length, user_data) + .map_err(ExecuteError::AsyncWriteZeroes)?; + } RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), } From 6c94975c80aba9bdf756a5f244e94e4c12b4af1d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 4 Feb 2026 15:55:35 +0100 Subject: [PATCH 0556/1893] 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 --- block/src/qcow_sync.rs | 69 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index a68a3c6174..01bbc02816 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -9,6 +9,7 @@ use std::os::fd::AsRawFd; use std::sync::{Arc, Mutex}; use vmm_sys_util::eventfd::EventFd; +use vmm_sys_util::write_zeroes::PunchHole; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, @@ -151,15 +152,67 @@ impl AsyncIo for QcowSync { self.completion_list.pop_front() } - fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { - Err(AsyncIoError::PunchHole(std::io::Error::other( - "punch_hole not supported for QCOW sync backend", - ))) + fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { + // For QCOW2, punch_hole calls deallocate_cluster + let result = self + .qcow_file + .lock() + .unwrap() + .punch_hole(offset, length) + .map(|_| 0i32) + .map_err(AsyncIoError::PunchHole); + + match result { + Ok(res) => { + self.completion_list.push_back((user_data, res)); + self.eventfd.write(1).unwrap(); + Ok(()) + } + Err(e) => { + // CRITICAL: Always signal completion even on error to avoid hangs + let errno = if let AsyncIoError::PunchHole(io_err) = &e { + let err = io_err.raw_os_error().unwrap_or(libc::EIO); + -err + } else { + -libc::EIO + }; + self.completion_list.push_back((user_data, errno)); + self.eventfd.write(1).unwrap(); + Ok(()) + } + } } - fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { - Err(AsyncIoError::WriteZeroes(std::io::Error::other( - "write_zeroes not supported for QCOW sync backend", - ))) + fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { + // For QCOW2, write_zeroes is implemented by deallocating clusters via punch_hole. + // This is more efficient than writing actual zeros and reduces disk usage. + // Unallocated clusters inherently read as zero in the QCOW2 format. + let result = self + .qcow_file + .lock() + .unwrap() + .punch_hole(offset, length) + .map(|_| 0i32) + .map_err(AsyncIoError::WriteZeroes); + + match result { + Ok(res) => { + self.completion_list.push_back((user_data, res)); + self.eventfd.write(1).unwrap(); + Ok(()) + } + Err(e) => { + // Always signal completion even on error to avoid hangs + let errno = if let AsyncIoError::WriteZeroes(io_err) = &e { + let err = io_err.raw_os_error().unwrap_or(libc::EIO); + -err + } else { + -libc::EIO + }; + self.completion_list.push_back((user_data, errno)); + self.eventfd.write(1).unwrap(); + Ok(()) + } + } } } From 45b115aeb0c20f4ad9df01a98233dea15466a022 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 29 Jan 2026 15:48:36 +0100 Subject: [PATCH 0557/1893] 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 --- block/src/raw_async.rs | 58 ++++++++++++++++++++++++++++++++++++------ block/src/raw_sync.rs | 52 +++++++++++++++++++++++++++++++------ 2 files changed, 94 insertions(+), 16 deletions(-) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 7d070ceb45..539aaa9095 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -258,15 +258,57 @@ impl AsyncIo for RawFileAsync { Ok(()) } - fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { - Err(AsyncIoError::PunchHole(std::io::Error::other( - "punch_hole not supported for raw async backend", - ))) + fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { + let (submitter, mut sq, _) = self.io_uring.split(); + + const FALLOC_FL_PUNCH_HOLE: i32 = 0x02; + const FALLOC_FL_KEEP_SIZE: i32 = 0x01; + let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; + + // SAFETY: The file descriptor is known to be valid. + unsafe { + sq.push( + &opcode::Fallocate::new(types::Fd(self.fd), length) + .offset(offset) + .mode(mode) + .build() + .user_data(user_data), + ) + .map_err(|e| { + AsyncIoError::PunchHole(Error::other(format!("Submission queue is full: {e:?}"))) + })?; + }; + + sq.sync(); + submitter.submit().map_err(AsyncIoError::PunchHole)?; + + Ok(()) } - fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { - Err(AsyncIoError::WriteZeroes(std::io::Error::other( - "write_zeroes not supported for raw async backend", - ))) + fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { + let (submitter, mut sq, _) = self.io_uring.split(); + + const FALLOC_FL_ZERO_RANGE: i32 = 0x10; + const FALLOC_FL_KEEP_SIZE: i32 = 0x01; + let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE; + + // SAFETY: The file descriptor is known to be valid. + unsafe { + sq.push( + &opcode::Fallocate::new(types::Fd(self.fd), length) + .offset(offset) + .mode(mode) + .build() + .user_data(user_data), + ) + .map_err(|e| { + AsyncIoError::WriteZeroes(Error::other(format!("Submission queue is full: {e:?}"))) + })?; + }; + + sq.sync(); + submitter.submit().map_err(AsyncIoError::WriteZeroes)?; + + Ok(()) } } diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index b540eb0ec6..796d1e86b6 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -151,15 +151,51 @@ impl AsyncIo for RawFileSync { self.completion_list.pop_front() } - fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { - Err(AsyncIoError::PunchHole(std::io::Error::other( - "punch_hole not supported for raw sync backend", - ))) + fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { + const FALLOC_FL_PUNCH_HOLE: i32 = 0x02; + const FALLOC_FL_KEEP_SIZE: i32 = 0x01; + let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; + + // SAFETY: FFI call with valid arguments + let result = unsafe { + libc::fallocate( + self.fd as libc::c_int, + mode, + offset as libc::off_t, + length as libc::off_t, + ) + }; + if result < 0 { + return Err(AsyncIoError::PunchHole(std::io::Error::last_os_error())); + } + + self.completion_list.push_back((user_data, result)); + self.eventfd.write(1).unwrap(); + + Ok(()) } - fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { - Err(AsyncIoError::WriteZeroes(std::io::Error::other( - "write_zeroes not supported for raw sync backend", - ))) + fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { + const FALLOC_FL_ZERO_RANGE: i32 = 0x10; + const FALLOC_FL_KEEP_SIZE: i32 = 0x01; + let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE; + + // SAFETY: FFI call with valid arguments + let result = unsafe { + libc::fallocate( + self.fd as libc::c_int, + mode, + offset as libc::off_t, + length as libc::off_t, + ) + }; + if result < 0 { + return Err(AsyncIoError::WriteZeroes(std::io::Error::last_os_error())); + } + + self.completion_list.push_back((user_data, result)); + self.eventfd.write(1).unwrap(); + + Ok(()) } } From 4676fdb494628782a0600233fb50585e06b9a3b0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 29 Jan 2026 15:02:43 +0100 Subject: [PATCH 0558/1893] 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> 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 --- block/src/qcow/mod.rs | 42 +++++++ block/src/qcow_sync.rs | 262 +++++++++++++++++++++++++++++++++++++++++ block/src/raw_sync.rs | 174 +++++++++++++++++++++++++++ 3 files changed, 478 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index e1e70ccb9b..65027bfaf1 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -3686,6 +3686,48 @@ mod unit_tests { }); } + #[test] + fn discard_sets_zero_flag() { + with_basic_file(&valid_header_v3(), |disk_file: RawFile| { + let mut q = QcowFile::from(disk_file).unwrap(); + + // Write some test data to allocate a cluster + let test_data = [0x42u8; 4096]; + q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek."); + q.write_all(&test_data).expect("Failed to write test data."); + + // Verify data was written + let mut buf = [0u8; 4096]; + q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek."); + q.read_exact(&mut buf).expect("Failed to read."); + assert_eq!(buf[0], 0x42); + assert_eq!(buf[4095], 0x42); + + // DISCARD the full cluster (via write_zeroes which calls punch_hole) + q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek."); + let nwritten = q.write_zeroes(4096).expect("Failed to discard cluster."); + assert_eq!(nwritten, 4096); + + // Verify reads now return zeros (due to zero flag) + q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek."); + q.read_exact(&mut buf).expect("Failed to read."); + assert_eq!(buf[0], 0); + assert_eq!(buf[4095], 0); + + // Write new data to the trimmed cluster + let new_data = [0x99u8; 4096]; + q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek."); + q.write_all(&new_data) + .expect("Failed to write to trimmed cluster."); + + // Verify new data can be read (cluster was reallocated) + q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek."); + q.read_exact(&mut buf).expect("Failed to read."); + assert_eq!(buf[0], 0x99); + assert_eq!(buf[4095], 0x99); + }); + } + #[test] fn test_header() { with_basic_file(&valid_header_v2(), |disk_file: RawFile| { diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 01bbc02816..4c7486f22c 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -216,3 +216,265 @@ impl AsyncIo for QcowSync { } } } + +#[cfg(test)] +mod unit_tests { + use std::io::{Read, Seek, SeekFrom, Write}; + + use vmm_sys_util::tempfile::TempFile; + + use super::*; + use crate::qcow::{QcowFile, RawFile}; + + #[test] + fn test_qcow_async_punch_hole_completion() { + // Create a QCOW2 image with valid header + let temp_file = TempFile::new().unwrap(); + let raw_file = RawFile::new(temp_file.into_file(), false); + let file_size = 1024 * 1024 * 100; // 100MB + let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); + + // Write some data + let data = vec![0xDD; 128 * 1024]; // 128KB + let offset = 0; + qcow_file.seek(SeekFrom::Start(offset)).unwrap(); + qcow_file.write_all(&data).unwrap(); + qcow_file.flush().unwrap(); + + // Create async wrapper + let qcow_file = Arc::new(Mutex::new(qcow_file)); + let mut async_qcow = QcowSync::new(qcow_file.clone()); + + // Punch hole + async_qcow + .punch_hole(offset, data.len() as u64, 100) + .unwrap(); + + // Verify completion event was generated + let (user_data, result) = async_qcow.next_completed_request().unwrap(); + assert_eq!(user_data, 100); + assert_eq!(result, 0, "punch_hole should succeed"); + + // Verify data reads as zeros + let mut read_buf = vec![0; data.len()]; + qcow_file + .lock() + .unwrap() + .seek(SeekFrom::Start(offset)) + .unwrap(); + qcow_file.lock().unwrap().read_exact(&mut read_buf).unwrap(); + assert!( + read_buf.iter().all(|&b| b == 0), + "Punched hole should read as zeros" + ); + } + + #[test] + fn test_qcow_async_write_zeroes_completion() { + // Create a QCOW2 image with valid header + let temp_file = TempFile::new().unwrap(); + let raw_file = RawFile::new(temp_file.into_file(), false); + let file_size = 1024 * 1024 * 100; // 100MB + let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); + + // Write some data + let data = vec![0xEE; 256 * 1024]; // 256KB + let offset = 64 * 1024; // Start at 64KB offset + qcow_file.seek(SeekFrom::Start(offset)).unwrap(); + qcow_file.write_all(&data).unwrap(); + qcow_file.flush().unwrap(); + + // Create async wrapper + let qcow_file = Arc::new(Mutex::new(qcow_file)); + let mut async_qcow = QcowSync::new(qcow_file.clone()); + + // Write zeros + async_qcow + .write_zeroes(offset, data.len() as u64, 200) + .unwrap(); + + // Verify completion event was generated + let (user_data, result) = async_qcow.next_completed_request().unwrap(); + assert_eq!(user_data, 200); + assert_eq!(result, 0, "write_zeroes should succeed"); + + // Verify data reads as zeros + let mut read_buf = vec![0; data.len()]; + qcow_file + .lock() + .unwrap() + .seek(SeekFrom::Start(offset)) + .unwrap(); + qcow_file.lock().unwrap().read_exact(&mut read_buf).unwrap(); + assert!( + read_buf.iter().all(|&b| b == 0), + "Zeroed region should read as zeros" + ); + } + + #[test] + fn test_qcow_async_multiple_operations() { + // Create a QCOW2 image with valid header + let temp_file = TempFile::new().unwrap(); + let raw_file = RawFile::new(temp_file.into_file(), false); + let file_size = 1024 * 1024 * 100; // 100MB + let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); + + // Write data at multiple offsets + let data = vec![0xFF; 64 * 1024]; // 64KB chunks + for i in 0..4 { + let offset = i * 128 * 1024; // 128KB spacing + qcow_file.seek(SeekFrom::Start(offset)).unwrap(); + qcow_file.write_all(&data).unwrap(); + } + qcow_file.flush().unwrap(); + + // Create async wrapper + let qcow_file = Arc::new(Mutex::new(qcow_file)); + let mut async_qcow = QcowSync::new(qcow_file.clone()); + + // Queue multiple punch_hole operations + async_qcow.punch_hole(0, 64 * 1024, 1).unwrap(); + async_qcow.punch_hole(128 * 1024, 64 * 1024, 2).unwrap(); + async_qcow.punch_hole(256 * 1024, 64 * 1024, 3).unwrap(); + + // Verify all completions + let (user_data, result) = async_qcow.next_completed_request().unwrap(); + assert_eq!(user_data, 1); + assert_eq!(result, 0); + + let (user_data, result) = async_qcow.next_completed_request().unwrap(); + assert_eq!(user_data, 2); + assert_eq!(result, 0); + + let (user_data, result) = async_qcow.next_completed_request().unwrap(); + assert_eq!(user_data, 3); + assert_eq!(result, 0); + + // Verify no more completions + assert!(async_qcow.next_completed_request().is_none()); + } + + #[test] + fn test_qcow_punch_hole_with_shared_instance() { + // This test verifies that with Arc>, multiple async I/O operations + // share the same QcowFile instance and see each other's changes. + + // Create a QCOW2 image + let temp_file = TempFile::new().unwrap(); + let raw_file = RawFile::new(temp_file.into_file(), false); + let file_size = 1024 * 1024 * 100; // 100MB + let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); + + // Write some data at offset 0 + let data = vec![0xAB; 128 * 1024]; // 128KB of 0xAB pattern + let offset = 0; + qcow_file.seek(SeekFrom::Start(offset)).unwrap(); + qcow_file.write_all(&data).unwrap(); + qcow_file.flush().unwrap(); + + let qcow_shared = Arc::new(Mutex::new(qcow_file)); + + // First async I/O: punch hole + let mut async_qcow1 = QcowSync::new(qcow_shared.clone()); + async_qcow1 + .punch_hole(offset, data.len() as u64, 100) + .unwrap(); + + // Verify punch_hole completed + let (user_data, result) = async_qcow1.next_completed_request().unwrap(); + assert_eq!(user_data, 100); + assert_eq!(result, 0, "punch_hole should succeed"); + + // Second async I/O: read from same shared instance + // This should see the deallocated cluster because they share the same QcowFile + let mut read_buf = vec![0xFF; data.len()]; + qcow_shared + .lock() + .unwrap() + .seek(SeekFrom::Start(offset)) + .unwrap(); + qcow_shared + .lock() + .unwrap() + .read_exact(&mut read_buf) + .unwrap(); + + // The read should return zeros because the cluster was deallocated + assert!( + read_buf.iter().all(|&b| b == 0), + "After punch_hole, shared QcowFile instance should read zeros from deallocated cluster" + ); + } + + #[test] + fn test_qcow_disk_sync_punch_hole_with_new_async_io() { + // This test simulates the EXACT real usage pattern: QcowDiskSync.new_async_io() + // creates a new QcowSync with a cloned QcowFile for each I/O operation. + + use std::io::Write; + + use crate::async_io::DiskFile; + + // Create a QCOW2 image + let temp_file = TempFile::new().unwrap(); + let file_size = 1024 * 1024 * 100; // 100MB + + { + let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); + + // Write data at offset 1MB - use single cluster (64KB) to simplify test + let data = vec![0xCD; 64 * 1024]; // 64KB (one cluster) + let offset = 1024 * 1024u64; + qcow_file.seek(SeekFrom::Start(offset)).unwrap(); + qcow_file.write_all(&data).unwrap(); + qcow_file.flush().unwrap(); + } + + // Open with QcowDiskSync (like real code does) + let disk = + QcowDiskSync::new(temp_file.as_file().try_clone().unwrap(), false, true, true).unwrap(); + + // First async I/O: punch hole (simulates DISCARD command) + let mut async_io1 = disk.new_async_io(1).unwrap(); + let offset = 1024 * 1024u64; + let length = 64 * 1024u64; // Single cluster + async_io1.punch_hole(offset, length, 1).unwrap(); + let (user_data, result) = async_io1.next_completed_request().unwrap(); + assert_eq!(user_data, 1); + assert_eq!(result, 0, "punch_hole should succeed"); + drop(async_io1); + + // Second async I/O: read from the same location (simulates READ command) + let mut async_io2 = disk.new_async_io(1).unwrap(); + let mut read_buf = vec![0xFF; length as usize]; + let iovec = libc::iovec { + iov_base: read_buf.as_mut_ptr() as *mut libc::c_void, + iov_len: read_buf.len(), + }; + + // These assertions are critical to prevent compiler optimization bugs + // that can reorder operations. Without them, the test can fail even + // though the QCOW2 implementation is correct. + assert_eq!(iovec.iov_base as *const u8, read_buf.as_ptr()); + assert_eq!(iovec.iov_len, read_buf.len()); + + async_io2 + .read_vectored(offset as libc::off_t, &[iovec], 2) + .unwrap(); + + let (user_data, result) = async_io2.next_completed_request().unwrap(); + assert_eq!(user_data, 2); + assert_eq!( + result as usize, length as usize, + "read should complete successfully" + ); + + // Verify the data is all zeros + assert!( + read_buf.iter().all(|&b| b == 0), + "After punch_hole via new_async_io, read should return zeros" + ); + } +} diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 796d1e86b6..e1a5433b89 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -199,3 +199,177 @@ impl AsyncIo for RawFileSync { Ok(()) } } + +#[cfg(test)] +mod unit_tests { + use std::io::{Read, Seek, SeekFrom, Write}; + + use vmm_sys_util::tempfile::TempFile; + + use super::*; + + #[test] + fn test_punch_hole() { + let temp_file = TempFile::new().unwrap(); + let mut file = temp_file.into_file(); + + // Write 4MB of data + let data = vec![0xAA; 4 * 1024 * 1024]; + file.write_all(&data).unwrap(); + file.sync_all().unwrap(); + + // Create async IO instance + let mut async_io = RawFileSync::new(file.as_raw_fd()); + + // 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) = async_io.next_completed_request().unwrap(); + 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" + ); + } + + #[test] + fn test_write_zeroes() { + let temp_file = TempFile::new().unwrap(); + let mut file = temp_file.into_file(); + + // Write 4MB of data + let data = vec![0xBB; 4 * 1024 * 1024]; + file.write_all(&data).unwrap(); + file.sync_all().unwrap(); + + // Create async IO instance + let mut async_io = RawFileSync::new(file.as_raw_fd()); + + // 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) = async_io.next_completed_request().unwrap(); + 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" + ); + } + + #[test] + fn test_punch_hole_multiple_operations() { + let temp_file = TempFile::new().unwrap(); + let mut file = temp_file.into_file(); + + // Write 8MB of data + let data = vec![0xCC; 8 * 1024 * 1024]; + file.write_all(&data).unwrap(); + file.sync_all().unwrap(); + + // Create async IO instance + let mut async_io = RawFileSync::new(file.as_raw_fd()); + + // 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) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 10); + assert_eq!(result, 0); + + let (user_data, result) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 11); + assert_eq!(result, 0); + + let (user_data, result) = async_io.next_completed_request().unwrap(); + 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)); + } +} From 8e51c57aea27bad9e9510e13724e7f51b7e41ff5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 29 Jan 2026 15:02:43 +0100 Subject: [PATCH 0559/1893] 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 --- cloud-hypervisor/tests/integration.rs | 966 ++++++++++++++++++++++++++ 1 file changed, 966 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 1bc941fad9..6a58f96c22 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -4241,6 +4241,188 @@ mod common_parallel { }); } + #[test] + fn test_virtio_block_qcow2_multiqueue_discard_mount() { + run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + guest + .ssh_command("sudo mkfs.ext4 -F /dev/vdc") + .expect("Failed to format disk"); + + // Mount with discard option to enable automatic TRIM/DISCARD + guest + .ssh_command("sudo mkdir -p /mnt/test && sudo mount -o discard /dev/vdc /mnt/test") + .expect("Failed to mount disk with discard option"); + + guest + .ssh_command( + "for i in $(seq 1 4); do \n\ + sudo dd if=/dev/urandom of=/mnt/test/file$i bs=1M count=32 conv=fsync & \n\ + done; wait", + ) + .expect("Failed to write files in parallel"); + + assert_eq!( + guest + .ssh_command("ls /mnt/test/file* | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4, + "Expected 4 files to be created" + ); + + guest + .ssh_command("sudo rm -f /mnt/test/file*") + .expect("Failed to remove files"); + + guest + .ssh_command("sudo fstrim -v /mnt/test") + .expect("fstrim failed - DISCARD not working"); + + guest + .ssh_command( + "for i in $(seq 1 8); do \n\ + sudo dd if=/dev/urandom of=/mnt/test/file$i bs=1M count=16 conv=fsync & \n\ + done; wait", + ) + .expect("Failed to write files in second round"); + + assert_eq!( + guest + .ssh_command("ls /mnt/test/file* | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 8, + "Expected 8 files after second round" + ); + + guest + .ssh_command("sudo umount /mnt/test") + .expect("Failed to unmount"); + }); + } + #[test] + fn test_virtio_block_qcow2_multiqueue_wide_writes() { + run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("1G"), |guest| { + // Scattered write pattern - write to widely separated offsets in parallel. + // This should initiate many L2 table allocations simultaneously across different queues. + guest + .ssh_command( + "for i in $(seq 0 7); do \n\ + offset=$((i * 128)) \n\ + sudo dd if=/dev/urandom of=/dev/vdc bs=1M count=16 seek=$offset conv=notrunc,fsync & \n\ + done; wait", + ) + .expect("Failed to write sparse pattern in parallel"); + + // Write known patterns to the same sparse locations + guest + .ssh_command( + "for i in $(seq 0 7); do \n\ + offset=$((i * 128)) \n\ + sudo dd if=/dev/zero of=/dev/vdc bs=1M count=8 seek=$offset conv=notrunc,fsync & \n\ + done; wait", + ) + .expect("Failed second sparse write pattern"); + + // Even more aggressive sparse writes with smaller chunks but more of them + guest + .ssh_command( + "for i in $(seq 0 15); do \n\ + offset=$((i * 64)) \n\ + sudo dd if=/dev/urandom of=/dev/vdc bs=1M count=2 seek=$offset conv=notrunc,fsync & \n\ + done; wait", + ) + .expect("Failed third sparse write pattern"); + + guest + .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M count=64") + .expect("Failed to read back data after sparse writes"); + }); + } + + #[test] + fn test_virtio_block_qcow2_multiqueue_discard_stress() { + run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("512M"), |guest| { + guest + .ssh_command("sudo mkfs.ext4 -F /dev/vdc") + .expect("Failed to format disk"); + guest + .ssh_command("sudo mkdir -p /mnt/test && sudo mount -o discard /dev/vdc /mnt/test") + .expect("Failed to mount disk with discard option"); + + // Round 1: Start background writes while simultaneously doing DISCARD operations + // This stresses refcount table locking - writes increment refs, discard decrements + guest + .ssh_command( + "for i in $(seq 1 4); do \n\ + sudo dd if=/dev/urandom of=/mnt/test/file$i bs=1M count=32 & \n\ + done", + ) + .expect("Failed to start background writes"); + + guest + .ssh_command( + "for i in $(seq 5 8); do \n\ + sudo dd if=/dev/urandom of=/mnt/test/temp$i bs=1M count=16 conv=fsync \n\ + sudo rm -f /mnt/test/temp$i & \n\ + done; \n\ + wait; \n\ + sudo fstrim -v /mnt/test", + ) + .expect("Failed to do parallel write-delete-discard"); + + guest + .ssh_command("wait") + .expect("Failed to wait for background writes"); + + assert_eq!( + guest + .ssh_command("ls /mnt/test/file* 2>/dev/null | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4, + "Expected 4 files after round 1" + ); + + // Round 2: More aggressive - 8 parallel writes with simultaneous blkdiscard on raw device + guest + .ssh_command("sudo umount /mnt/test") + .expect("Failed to unmount"); + + guest + .ssh_command( + "for i in $(seq 0 7); do \n\ + offset=$((i * 64)) \n\ + sudo dd if=/dev/urandom of=/dev/vdc bs=1M count=4 seek=$offset conv=notrunc,fsync & \n\ + done; wait", + ) + .expect("Failed sparse writes"); + + // Now discard half the regions while writing to the other half + guest + .ssh_command( + "for i in $(seq 0 3); do \n\ + offset=$((i * 64 * 1024 * 1024)) \n\ + sudo blkdiscard -o $offset -l $((4 * 1024 * 1024)) /dev/vdc & \n\ + done; \n\ + for i in $(seq 4 7); do \n\ + offset=$((i * 64)) \n\ + sudo dd if=/dev/zero of=/dev/vdc bs=1M count=4 seek=$offset conv=notrunc,fsync & \n\ + done; wait", + ) + .expect("Failed parallel discard and write stress test"); + + guest + .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M count=128") + .expect("Failed to read back data after discard stress"); + }); + } #[test] fn test_virtio_block_qcow2_dirty_bit_unclean_shutdown() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); @@ -7023,6 +7205,790 @@ mod common_parallel { .expect("loop device not found"); } + // Helper function to verify sparse file + fn verify_sparse_file(test_disk_path: &str, expected_ratio: f64) { + let res = exec_host_command_output(&format!("ls -s --block-size=1 {}", test_disk_path)); + assert!(res.status.success(), "ls -s command failed"); + let out = String::from_utf8_lossy(&res.stdout); + let actual_bytes: u64 = out + .split_whitespace() + .next() + .and_then(|s| s.parse().ok()) + .expect("Failed to parse ls -s output"); + + let res = exec_host_command_output(&format!("ls -l {}", test_disk_path)); + assert!(res.status.success(), "ls -l command failed"); + let out = String::from_utf8_lossy(&res.stdout); + let apparent_size: u64 = out + .split_whitespace() + .nth(4) + .and_then(|s| s.parse().ok()) + .expect("Failed to parse ls -l output"); + + let threshold = (apparent_size as f64 * expected_ratio) as u64; + assert!( + actual_bytes < threshold, + "Expected file to be sparse: apparent_size={} bytes, actual_disk_usage={} bytes (threshold={})", + apparent_size, + actual_bytes, + threshold + ); + } + + // Helper function to count zero flagged regions in QCOW2 image + fn count_qcow2_zero_regions(test_disk_path: &str) -> Option { + let res = + exec_host_command_output(&format!("qemu-img map --output=json -U {}", test_disk_path)); + if !res.status.success() { + return None; + } + + let out = String::from_utf8_lossy(&res.stdout); + let map_json = serde_json::from_str::(&out).ok()?; + let regions = map_json.as_array()?; + + Some( + regions + .iter() + .filter(|r| { + let data = r["data"].as_bool().unwrap_or(true); + let zero = r["zero"].as_bool().unwrap_or(false); + // holes - data: false + // zero flagged regions - data: true, zero: true + !data || zero + }) + .count(), + ) + } + + // Helper function to verify file extents using FIEMAP after DISCARD + // TODO: Make verification more format-specific: + // - QCOW2: Check for fragmentation patterns showing deallocated clusters + // - RAW: Verify actual holes (unallocated extents) exist in sparse regions + // - Could parse extent output to count holes vs allocated regions + fn verify_fiemap_extents(test_disk_path: &str, format_type: &str) { + let blocksize_output = + exec_host_command_output(&format!("stat -f -c %S {}", test_disk_path)); + let blocksize = if blocksize_output.status.success() { + String::from_utf8_lossy(&blocksize_output.stdout) + .trim() + .parse::() + .unwrap_or(4096) + } else { + 4096 + }; + + let fiemap_output = + exec_host_command_output(&format!("filefrag -b {} -v {}", blocksize, test_disk_path)); + if fiemap_output.status.success() { + let fiemap_str = String::from_utf8_lossy(&fiemap_output.stdout); + + // Verify we have extent information indicating sparse regions + let has_extents = fiemap_str.contains("extent") || fiemap_str.contains("extents"); + let has_holes = fiemap_str.contains("hole"); + + assert!( + has_extents || has_holes, + "FIEMAP should show extent information or holes for {} file", + format_type + ); + } + } + + /// Helper function to verify a disk region reads as all zeros from within the guest + fn assert_guest_disk_region_is_zero(guest: &Guest, device: &str, offset: u64, length: u64) { + let result = guest + .ssh_command(&format!( + "sudo hexdump -v -s {} -n {} -e '1/1 \"%02x\"' {} | grep -qv '^00*$' && echo 'NONZERO' || echo 'ZEROS'", + offset, length, device + )) + .unwrap(); + + assert!( + result.trim() == "ZEROS", + "Expected {} region at offset {} length {} to read as zeros, but got: {}", + device, + offset, + length, + result.trim() + ); + } + + // Common test sizes for discard/fstrim tests (all formats): 9 small (≤256KB), then one 4MB + const BLOCK_DISCARD_TEST_SIZES_KB: &[u64] = &[64, 128, 256, 64, 128, 256, 64, 128, 256, 4096]; + + fn _test_virtio_block_discard( + format_name: &str, + qemu_img_format: &str, + extra_create_args: &[&str], + expect_discard_success: bool, + verify_disk: bool, + ) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_disk_path = guest + .tmp_dir + .as_path() + .join(format!("discard_test.{}", format_name.to_lowercase())); + + let mut cmd = format!("qemu-img create -f {} ", qemu_img_format); + if !extra_create_args.is_empty() { + cmd.push_str(&extra_create_args.join(" ")); + cmd.push(' '); + } + cmd.push_str(&format!("{} 2G", test_disk_path.to_str().unwrap())); + + let res = exec_host_command_output(&cmd); + assert!( + res.status.success(), + "Failed to create {} test image", + format_name + ); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={},num_queues=4", test_disk_path.to_str().unwrap()).as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + const CLUSTER_SIZE_BYTES: u64 = 64 * 1024; // One QCOW2 cluster + const WRITE_SIZE_MB: u64 = 4; + const WRITE_OFFSET_MB: u64 = 1; + + // Build discard operations within the written region + let write_start = WRITE_OFFSET_MB * 1024 * 1024; + let mut discard_operations: Vec<(u64, u64)> = Vec::new(); + let mut current_offset = write_start; + + for &size_kb in BLOCK_DISCARD_TEST_SIZES_KB { + let size = size_kb * 1024; + discard_operations.push((current_offset, size)); + current_offset += size + CLUSTER_SIZE_BYTES; // Add gap between operations + } + + let size_after_write = std::cell::Cell::new(0u64); + + let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Write one 4MB block at offset 1MB + guest + .ssh_command(&format!( + "sudo dd if=/dev/zero of=/dev/vdc bs=1M count={} seek={} oflag=direct", + WRITE_SIZE_MB, WRITE_OFFSET_MB + )) + .unwrap(); + guest.ssh_command("sync").unwrap(); + + // For QCOW2, measure file size after write to verify deallocation later + let write_size = if qemu_img_format == "qcow2" { + let res = exec_host_command_output(&format!( + "ls -s --block-size=1 {}", + test_disk_path.to_str().unwrap() + )); + assert!(res.status.success()); + String::from_utf8_lossy(&res.stdout) + .split_whitespace() + .next() + .and_then(|s| s.parse::().ok()) + .expect("Failed to parse file size after write") + } else { + 0 + }; + size_after_write.set(write_size); + + if expect_discard_success { + for (i, (offset, length)) in discard_operations.iter().enumerate() { + let result = guest + .ssh_command(&format!( + "sudo blkdiscard -v -o {} -l {} /dev/vdc 2>&1 || true", + offset, length + )) + .unwrap(); + + assert!( + !result.contains("Operation not supported") + && !result.contains("BLKDISCARD"), + "blkdiscard #{} at offset {} length {} failed: {}", + i, + offset, + length, + result + ); + } + + // Force sync to ensure async DISCARD operations complete + guest.ssh_command("sync").unwrap(); + + // Verify VM sees zeros in discarded regions + for (_i, (offset, length)) in discard_operations.iter().enumerate() { + assert_guest_disk_region_is_zero(&guest, "/dev/vdc", *offset, *length); + } + + guest.ssh_command("echo test").unwrap(); + } else { + // For unsupported formats, blkdiscard should fail with "not supported" + use test_infra::ssh_command_ip; + let result = ssh_command_ip( + "sudo blkdiscard -o 0 -l 4096 /dev/vdc 2>&1", + &guest.network.guest_ip0, + 0, + 5, + ); + assert!( + result.is_err(), + "blkdiscard should fail on unsupported format" + ); + guest.ssh_command("echo test").unwrap(); + } + + if expect_discard_success { + if qemu_img_format == "qcow2" { + let res = exec_host_command_output(&format!( + "ls -s --block-size=1 {}", + test_disk_path.to_str().unwrap() + )); + assert!(res.status.success()); + let size_after_discard: u64 = String::from_utf8_lossy(&res.stdout) + .split_whitespace() + .next() + .and_then(|s| s.parse().ok()) + .expect("Failed to parse file size after discard"); + + assert!( + size_after_discard < size_after_write.get(), + "QCOW2 file should shrink after DISCARD with sparse=true: after_write={} bytes, after_discard={} bytes", + size_after_write.get(), + size_after_discard + ); + + verify_fiemap_extents(test_disk_path.to_str().unwrap(), "QCOW2"); + } else if qemu_img_format == "raw" { + let mut file = File::open(&test_disk_path) + .expect("Failed to open test disk for verification"); + + // Verify each discarded region contains all zeros + for (offset, length) in &discard_operations { + file.seek(SeekFrom::Start(*offset)) + .expect("Failed to seek to discarded region"); + + let mut buffer = vec![0u8; *length as usize]; + file.read_exact(&mut buffer) + .expect("Failed to read discarded region"); + + let all_zeros = buffer.iter().all(|&b| b == 0); + assert!( + all_zeros, + "Expected discarded region at offset {} length {} to contain all zeros", + offset, length + ); + } + + verify_sparse_file(test_disk_path.to_str().unwrap(), 1.0); + + verify_fiemap_extents(test_disk_path.to_str().unwrap(), "RAW"); + } + } + })); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + if verify_disk { + disk_check_consistency(&test_disk_path, None); + } + } + + #[test] + fn test_virtio_block_discard_qcow2() { + _test_virtio_block_discard("qcow2", "qcow2", &[], true, true); + } + + #[test] + fn test_virtio_block_discard_raw() { + _test_virtio_block_discard("raw", "raw", &[], true, false); + } + + #[test] + fn test_virtio_block_discard_unsupported_vhd() { + _test_virtio_block_discard("vhd", "vpc", &["-o", "subformat=fixed"], false, false); + } + + #[test] + fn test_virtio_block_discard_unsupported_vhdx() { + _test_virtio_block_discard("vhdx", "vhdx", &[], false, false); + } + + fn _test_virtio_block_fstrim( + format_name: &str, + qemu_img_format: &str, + extra_create_args: &[&str], + expect_fstrim_success: bool, + verify_disk: bool, + ) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_disk_path = guest + .tmp_dir + .as_path() + .join(format!("fstrim_test.{}", format_name.to_lowercase())); + + let mut cmd = format!("qemu-img create -f {} ", qemu_img_format); + if !extra_create_args.is_empty() { + cmd.push_str(&extra_create_args.join(" ")); + cmd.push(' '); + } + cmd.push_str(&format!("{} 2G", test_disk_path.to_str().unwrap())); + + let res = exec_host_command_output(&cmd); + assert!( + res.status.success(), + "Failed to create {} test image", + format_name + ); + + const WRITE_SIZE_MB: u64 = 4; + const CLUSTER_SIZE_BYTES: u64 = 64 * 1024; + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={},num_queues=4", test_disk_path.to_str().unwrap()).as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let max_size_during_writes = std::cell::Cell::new(0u64); + + let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + guest.ssh_command("sudo mkfs.ext4 -F /dev/vdc").unwrap(); + + guest + .ssh_command("sudo mkdir -p /mnt/test && sudo mount /dev/vdc /mnt/test") + .unwrap(); + + for (iteration, &write_size_kb) in BLOCK_DISCARD_TEST_SIZES_KB.iter().enumerate() { + guest + .ssh_command(&format!( + "sudo dd if=/dev/zero of=/mnt/test/testfile{} bs=1K count={}", + iteration, write_size_kb + )) + .unwrap(); + + guest.ssh_command("sync").unwrap(); + + // Measure QCOW2 file size after writing + if qemu_img_format == "qcow2" { + let res = exec_host_command_output(&format!( + "ls -s --block-size=1 {}", + test_disk_path.to_str().unwrap() + )); + if res.status.success() { + if let Some(size) = String::from_utf8_lossy(&res.stdout) + .split_whitespace() + .next() + .and_then(|s| s.parse::().ok()) + { + max_size_during_writes.set(max_size_during_writes.get().max(size)); + } + } + } + + // Make blocks available for discard + guest + .ssh_command(&format!("sudo rm /mnt/test/testfile{}", iteration)) + .unwrap(); + + guest.ssh_command("sync").unwrap(); + + if expect_fstrim_success { + let fstrim_result = guest.ssh_command("sudo fstrim -v /mnt/test 2>&1").unwrap(); + + // Would output like "/mnt/test: X bytes (Y MB) trimmed" + assert!( + fstrim_result.contains("trimmed") || fstrim_result.contains("bytes"), + "fstrim iteration {} ({}KB) should report trimmed bytes: {}", + iteration, + write_size_kb, + fstrim_result + ); + } else { + // For unsupported formats, expect fstrim to fail + use test_infra::ssh_command_ip; + let result = ssh_command_ip( + "sudo fstrim -v /mnt/test 2>&1", + &guest.network.guest_ip0, + 0, + 5, + ); + assert!(result.is_err(), "fstrim should fail on unsupported format"); + guest.ssh_command("echo 'VM responsive'").unwrap(); + } + } + + guest.ssh_command("sudo umount /mnt/test").unwrap(); + + guest.ssh_command("echo test").unwrap(); + })); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + if expect_fstrim_success { + if qemu_img_format == "qcow2" { + // Verify QCOW2 file shrank after fstrim (sparse=true deallocates clusters) + let res = exec_host_command_output(&format!( + "ls -s --block-size=1 {}", + test_disk_path.to_str().unwrap() + )); + assert!(res.status.success()); + let size_after_fstrim: u64 = String::from_utf8_lossy(&res.stdout) + .split_whitespace() + .next() + .and_then(|s| s.parse().ok()) + .expect("Failed to parse file size after fstrim"); + + assert!( + size_after_fstrim < max_size_during_writes.get(), + "QCOW2 file should shrink after fstrim with sparse=true: max_during_writes={} bytes, after_fstrim={} bytes", + max_size_during_writes.get(), + size_after_fstrim + ); + } else if qemu_img_format == "raw" { + verify_sparse_file(test_disk_path.to_str().unwrap(), 0.5); + } + } + + handle_child_output(r, &output); + + if verify_disk { + disk_check_consistency(&test_disk_path, None); + } + } + + #[test] + fn test_virtio_block_fstrim_qcow2() { + _test_virtio_block_fstrim("qcow2", "qcow2", &[], true, true); + } + + #[test] + fn test_virtio_block_fstrim_raw() { + _test_virtio_block_fstrim("raw", "raw", &[], true, false); + } + + #[test] + fn test_virtio_block_fstrim_unsupported_vhd() { + _test_virtio_block_fstrim("vhd", "vpc", &["-o", "subformat=fixed"], false, false); + } + + // VHDX backend has a multiqueue bug causing filesystem corruption. + // The _test_virtio_block_fstrim helper uses num_queues>1 which triggers the bug. + // Ref: #7665 + #[test] + #[ignore] + fn test_virtio_block_fstrim_unsupported_vhdx() { + _test_virtio_block_fstrim("vhdx", "vhdx", &[], false, false); + } + + #[test] + #[ignore = "fallocate() preallocation requires native filesystem support (fails on overlay/tmpfs in CI)"] + fn test_virtio_block_sparse_off_raw() { + const TEST_DISK_SIZE: &str = "2G"; + const TEST_DISK_SIZE_BYTES: u64 = 2 * 1024 * 1024 * 1024; + const INITIAL_ALLOCATION_THRESHOLD: u64 = 1024 * 1024; + + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_disk_path = guest.tmp_dir.as_path().join("sparse_off_test.raw"); + let test_disk_path = test_disk_path.to_str().unwrap(); + + let res = exec_host_command_output(&format!( + "truncate -s {} {}", + TEST_DISK_SIZE, test_disk_path + )); + assert!(res.status.success(), "Failed to create sparse test file"); + + let res = exec_host_command_output(&format!("ls -s --block-size=1 {}", test_disk_path)); + assert!(res.status.success()); + let initial_bytes: u64 = String::from_utf8_lossy(&res.stdout) + .split_whitespace() + .next() + .and_then(|s| s.parse().ok()) + .expect("Failed to parse initial disk usage"); + assert!( + initial_bytes < INITIAL_ALLOCATION_THRESHOLD, + "File should be initially sparse: {} bytes allocated", + initial_bytes + ); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={},sparse=off", test_disk_path).as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + }); + + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + // After VM starts with sparse=off, verify file is fully allocated. + // Strategy is to compare compare physical vs logical bytes + // - physical >= logical is fully allocated, modulo block alignment + // - physical < logical is still sparse + + let res = exec_host_command_output(&format!("ls -l {}", test_disk_path)); + assert!(res.status.success()); + let logical_size: u64 = String::from_utf8_lossy(&res.stdout) + .split_whitespace() + .nth(4) + .and_then(|s| s.parse().ok()) + .expect("Failed to parse logical size"); + + let res = exec_host_command_output(&format!("ls -s --block-size=1 {}", test_disk_path)); + assert!(res.status.success()); + let physical_size: u64 = String::from_utf8_lossy(&res.stdout) + .split_whitespace() + .next() + .and_then(|s| s.parse().ok()) + .expect("Failed to parse physical size"); + + assert_eq!( + logical_size, TEST_DISK_SIZE_BYTES, + "Logical size should be exactly {} bytes, got {}", + TEST_DISK_SIZE_BYTES, logical_size + ); + + let res = exec_host_command_output(&format!("stat -c '%o' {}", test_disk_path)); + assert!(res.status.success()); + let block_size: u64 = String::from_utf8_lossy(&res.stdout) + .trim() + .parse() + .expect("Failed to parse block size from stat"); + + let expected_max = ((logical_size + block_size - 1) / block_size) * block_size; + + assert!( + physical_size >= logical_size, + "File should be fully allocated with sparse=off: logical={} bytes, physical={} bytes (physical < logical means still sparse)", + logical_size, + physical_size + ); + + assert!( + physical_size <= expected_max, + "Physical size seems too large: logical={} bytes, physical={} bytes, expected_max={} bytes (block_size={})", + logical_size, + physical_size, + expected_max, + block_size + ); + } + + #[test] + fn test_virtio_block_sparse_off_qcow2() { + const TEST_DISK_SIZE: &str = "2G"; + const CLUSTER_SIZE_BYTES: u64 = 64 * 1024; + + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_disk_path = guest.tmp_dir.as_path().join("sparse_off_test.qcow2"); + let test_disk_path = test_disk_path.to_str().unwrap(); + + let res = exec_host_command_output(&format!( + "qemu-img create -f qcow2 {} {}", + test_disk_path, TEST_DISK_SIZE + )); + assert!(res.status.success(), "Failed to create QCOW2 test image"); + + let zero_regions_before = count_qcow2_zero_regions(test_disk_path) + .expect("Failed to get initial zero regions count"); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={},sparse=off,num_queues=4", test_disk_path).as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + let mut current_offset_kb = 1024; + + for (_iteration, &size_kb) in BLOCK_DISCARD_TEST_SIZES_KB.iter().enumerate() { + guest + .ssh_command(&format!( + "sudo dd if=/dev/urandom of=/dev/vdc bs=1K count={} seek={} oflag=direct", + size_kb, current_offset_kb + )) + .unwrap(); + + guest.ssh_command("sync").unwrap(); + + guest + .ssh_command(&format!( + "sudo blkdiscard -o {} -l {} /dev/vdc", + current_offset_kb * 1024, + size_kb * 1024 + )) + .unwrap(); + + guest.ssh_command("sync").unwrap(); + + // Verify VM sees zeros in discarded region + assert_guest_disk_region_is_zero( + &guest, + "/dev/vdc", + current_offset_kb * 1024, + size_kb * 1024, + ); + + current_offset_kb += size_kb + 64; + } + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + let zero_regions_after = count_qcow2_zero_regions(test_disk_path) + .expect("Failed to get final zero regions count"); + + handle_child_output(r, &output); + + assert!( + zero_regions_after > zero_regions_before, + "Expected zero-flagged regions to increase with sparse=off: before={}, after={}", + zero_regions_before, + zero_regions_after + ); + + disk_check_consistency(&test_disk_path, None); + } + #[test] fn test_virtio_balloon_deflate_on_oom() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); From adfcd17bfcab87462dd6e69a0f5fb1253261502a Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Fri, 13 Feb 2026 13:30:59 +0100 Subject: [PATCH 0560/1893] vmm: Include invalid UUID string in ParseUuid err On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler --- arch/src/x86_64/smbios.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/src/x86_64/smbios.rs b/arch/src/x86_64/smbios.rs index ee1499982e..b9db0fcb96 100644 --- a/arch/src/x86_64/smbios.rs +++ b/arch/src/x86_64/smbios.rs @@ -33,8 +33,8 @@ pub enum Error { #[error("Failure to write additional data to memory")] WriteData, /// 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), } pub type Result = result::Result; @@ -198,7 +198,7 @@ pub fn setup_smbios( let uuid_number = uuid .map(Uuid::parse_str) .transpose() - .map_err(Error::ParseUuid)? + .map_err(|e| Error::ParseUuid(e, uuid.unwrap().to_string()))? .unwrap_or(Uuid::nil()); let smbios_sysinfo = SmbiosSysInfo { r#type: SYSTEM_INFORMATION, From 30c86d5999d943a5dbf4bfc1891f1d4b44bd9d6e Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Fri, 13 Feb 2026 13:21:29 +0100 Subject: [PATCH 0561/1893] vmm: fix --net fd helptext for list syntax The --net help text documented fd as fd=, 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 --- vmm/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 8a8d637fd2..16089e5583 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1334,7 +1334,7 @@ impl FromStr for VhostMode { impl NetConfig { pub const SYNTAX: &'static str = "Network parameters \ - \"tap=,ip=,mask=,mac=,fd=,iommu=on|off,\ + \"tap=,ip=,mask=,mac=,fd=<[fd1,fd2,...]>,iommu=on|off,\ num_queues=,queue_size=,id=,\ vhost_user=,socket=,vhost_mode=client|server,\ bw_size=,bw_one_time_burst=,bw_refill_time=,\ From ff4c8f7480ae6646e99515acae33493c220d8d71 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 13 Feb 2026 12:18:10 -0800 Subject: [PATCH 0562/1893] 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 --- hypervisor/src/mshv/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index b36b306a93..b7bdbf6a59 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -873,6 +873,19 @@ impl cpu::Vcpu for MshvVcpu { assert!(info.header.intercept_access_type == HV_INTERCEPT_ACCESS_EXECUTE as u8); match ghcb_op { + GHCB_INFO_SPECIAL_DBGPRINT => { + // Handle debug print from guest + // The guest sends a character to print via GHCB data + // Sample debug print implementation that only prints ASCII characters and ignores the rest + // let char_to_print = (ghcb_data & 0xFF) as u8; + // if char_to_print.is_ascii() { + // debug!("'{}'", char_to_print as char); + // } + // Not printing the character to slow down the guest a bit, + // as these debug prints can be very frequent. + // In real implementation, we might want to buffer these characters and + // print them out together or implement some rate limiting.] + } GHCB_INFO_HYP_FEATURE_REQUEST => { // Pre-condition: GHCB data must be zero assert!(ghcb_data == 0); From 80ea1fe62d9b8d405611cf68aed5c746b17db509 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 14 Feb 2026 02:54:34 -0500 Subject: [PATCH 0563/1893] 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 --- virtio-devices/src/block.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index a7da43e6ce..06e07eca4b 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -169,7 +169,9 @@ fn has_feature(features: u64, feature_flag: u64) -> bool { impl BlockEpollHandler { fn check_request(features: u64, request_type: RequestType) -> result::Result<(), ExecuteError> { if has_feature(features, VIRTIO_BLK_F_RO.into()) - && !(request_type == RequestType::In || request_type == RequestType::GetDeviceId) + && !(request_type == RequestType::In + || request_type == RequestType::GetDeviceId + || request_type == RequestType::Flush) { // For virtio spec compliance // "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request From ce93686ad22d9dde10a1fc6c381059f37a2a4549 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 14 Feb 2026 02:35:01 -0500 Subject: [PATCH 0564/1893] 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 --- virtio-devices/src/block.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 06e07eca4b..50d8dac65d 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -986,6 +986,13 @@ impl VirtioDevice for Block { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { + // See if the guest didn't ack the device being read-only. + // If so, warn and pretend it did. + let original_acked_features = self.common.acked_features; + self.common.acked_features |= self.common.avail_features & (1u64 << VIRTIO_BLK_F_RO); + if original_acked_features != self.common.acked_features { + warn!("Guest did not acknowledge that device is read-only, acting as if it did!"); + } self.common.activate(&queues, interrupt_cb.clone())?; self.update_writeback(); From 20f5cadb6f4eff4e57b8ae5a1091468eb96236ff Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 15 Feb 2026 10:55:07 +0100 Subject: [PATCH 0565/1893] 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 --- scripts/run_metrics.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run_metrics.sh b/scripts/run_metrics.sh index 8feeeecb7e..f70ccf1cde 100755 --- a/scripts/run_metrics.sh +++ b/scripts/run_metrics.sh @@ -28,7 +28,7 @@ build_fio() { process_common_args "$@" -cp scripts/sha1sums-"${TEST_ARCH}" "$WORKLOADS_DIR" +cp scripts/sha1sums-"${TEST_ARCH}"-common "$WORKLOADS_DIR" if [ "${TEST_ARCH}" == "aarch64" ]; then FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-arm64-custom-20210929-0.qcow2" @@ -58,7 +58,7 @@ if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then fi pushd "$WORKLOADS_DIR" || exit -if ! grep focal sha1sums-"${TEST_ARCH}" | sha1sum --check; then +if ! grep focal sha1sums-"${TEST_ARCH}"-common | sha1sum --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi From ecb971a491096767d97c08d8bc25bebc2dcbad7e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 15 Feb 2026 10:56:21 +0100 Subject: [PATCH 0566/1893] 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 --- performance-metrics/src/performance_tests.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 7dcf443d3f..6e84fdd8e1 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -413,6 +413,12 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { .unwrap() .to_string(); + let mut test_disk_arg = + format!("path={test_file},queue_size={queue_size},num_queues={num_queues}"); + if test_file == OVERLAY_WITH_QCOW2_BACKING || test_file == OVERLAY_WITH_RAW_BACKING { + test_disk_arg.push_str(",backing_files=on"); + } + let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("boot={num_queues}")]) .args(["--memory", "size=4G"]) @@ -430,7 +436,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - format!("path={test_file},queue_size={queue_size},num_queues={num_queues}").as_str(), + test_disk_arg.as_str(), ]) .default_net() .args(["--api-socket", &api_socket]) From 333db1acb39a1caa73c3a13278df00cbe701ebd2 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 15 Feb 2026 14:42:38 +0100 Subject: [PATCH 0567/1893] 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 --- performance-metrics/src/main.rs | 94 ++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index e27161683f..a08e6e15ec 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -333,7 +333,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 36] = [ +const TEST_LIST: [PerformanceTest; 42] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -816,6 +816,98 @@ const TEST_LIST: [PerformanceTest; 36] = [ }, unit_adjuster: adjuster::Bps_to_MiBps, }, + PerformanceTest { + name: "block_qcow2_multi_queue_backing_qcow2_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: OVERLAY_WITH_QCOW2_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_multi_queue_backing_qcow2_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: OVERLAY_WITH_QCOW2_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_multi_queue_backing_raw_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: OVERLAY_WITH_RAW_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_multi_queue_backing_raw_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: OVERLAY_WITH_RAW_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_multi_queue_backing_qcow2_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: OVERLAY_WITH_QCOW2_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_multi_queue_backing_raw_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: OVERLAY_WITH_RAW_BACKING, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, ]; fn run_test_with_timeout( From 16d6a16e5cfc25d5f536ccc3d2affe5b6224e47d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 15 Feb 2026 18:05:33 +0100 Subject: [PATCH 0568/1893] 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 --- performance-metrics/src/main.rs | 278 ++++++++++++++++++- performance-metrics/src/performance_tests.rs | 25 ++ 2 files changed, 302 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index a08e6e15ec..c48e5a906d 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -333,7 +333,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 42] = [ +const TEST_LIST: [PerformanceTest; 60] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -724,6 +724,282 @@ const TEST_LIST: [PerformanceTest; 42] = [ }, unit_adjuster: adjuster::identity, }, + PerformanceTest { + name: "block_qcow2_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_UNCOMPRESSED_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: QCOW2_UNCOMPRESSED_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_UNCOMPRESSED_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_multi_queue_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_UNCOMPRESSED_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_multi_queue_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: QCOW2_UNCOMPRESSED_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_multi_queue_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_UNCOMPRESSED_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zlib_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_ZLIB_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zlib_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: QCOW2_ZLIB_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zlib_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_ZLIB_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zlib_multi_queue_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_ZLIB_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zlib_multi_queue_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: QCOW2_ZLIB_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zlib_multi_queue_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_ZLIB_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zstd_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_ZSTD_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zstd_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: QCOW2_ZSTD_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zstd_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(1), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_ZSTD_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zstd_multi_queue_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_ZSTD_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zstd_multi_queue_random_read_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + block_control: Some(BlockControl { + fio_ops: FioOps::RandomRead, + bandwidth: true, + test_file: QCOW2_ZSTD_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, + PerformanceTest { + name: "block_qcow2_zstd_multi_queue_read_warm_MiBps", + func_ptr: performance_block_io, + control: PerformanceTestControl { + num_queues: Some(4), + queue_size: Some(128), + warmup_iterations: 2, + block_control: Some(BlockControl { + fio_ops: FioOps::Read, + bandwidth: true, + test_file: QCOW2_ZSTD_IMG, + }), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::Bps_to_MiBps, + }, PerformanceTest { name: "block_qcow2_backing_qcow2_read_MiBps", func_ptr: performance_block_io, diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 6e84fdd8e1..b959fce5c3 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -37,6 +37,9 @@ const QCOW2_BACKING_FILE: &str = "/var/tmp/ch-blk-io-test-qcow2-backing.qcow2"; pub const OVERLAY_WITH_QCOW2_BACKING: &str = "/var/tmp/ch-blk-io-test-overlay-qcow2.qcow2"; const RAW_BACKING_FILE: &str = "/var/tmp/ch-blk-io-test-raw-backing.raw"; pub const OVERLAY_WITH_RAW_BACKING: &str = "/var/tmp/ch-blk-io-test-overlay-raw.qcow2"; +pub const QCOW2_UNCOMPRESSED_IMG: &str = "/var/tmp/ch-blk-io-test-uncompressed.qcow2"; +pub const QCOW2_ZLIB_IMG: &str = "/var/tmp/ch-blk-io-test-zlib.qcow2"; +pub const QCOW2_ZSTD_IMG: &str = "/var/tmp/ch-blk-io-test-zstd.qcow2"; pub fn init_tests(overrides: &PerformanceTestOverrides) { let mut cmd = format!("dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096"); @@ -78,6 +81,22 @@ pub fn init_tests(overrides: &PerformanceTestOverrides) { "qemu-img create -f qcow2 -b {RAW_BACKING_FILE} -F raw {OVERLAY_WITH_RAW_BACKING} 4G" ); assert!(exec_host_command_output(&cmd).status.success()); + + // Standalone QCOW2 image with no backing file + cmd = format!("qemu-img create -f qcow2 -o preallocation=full {QCOW2_UNCOMPRESSED_IMG} 4G"); + assert!(exec_host_command_output(&cmd).status.success()); + + // Zlib compressed QCOW2 image, convert populates actual compressed clusters + cmd = format!( + "qemu-img convert -f qcow2 -O qcow2 -c -o compression_type=zlib {QCOW2_UNCOMPRESSED_IMG} {QCOW2_ZLIB_IMG}" + ); + assert!(exec_host_command_output(&cmd).status.success()); + + // Zstd compressed QCOW2 image, convert populates actual compressed clusters + cmd = format!( + "qemu-img convert -f qcow2 -O qcow2 -c -o compression_type=zstd {QCOW2_UNCOMPRESSED_IMG} {QCOW2_ZSTD_IMG}" + ); + assert!(exec_host_command_output(&cmd).status.success()); } pub fn cleanup_tests() { @@ -91,6 +110,12 @@ pub fn cleanup_tests() { .unwrap_or_else(|_| panic!("Failed to remove file '{RAW_BACKING_FILE}'.")); fs::remove_file(OVERLAY_WITH_RAW_BACKING) .unwrap_or_else(|_| panic!("Failed to remove file '{OVERLAY_WITH_RAW_BACKING}'.")); + fs::remove_file(QCOW2_UNCOMPRESSED_IMG) + .unwrap_or_else(|_| panic!("Failed to remove file '{QCOW2_UNCOMPRESSED_IMG}'.")); + fs::remove_file(QCOW2_ZLIB_IMG) + .unwrap_or_else(|_| panic!("Failed to remove file '{QCOW2_ZLIB_IMG}'.")); + fs::remove_file(QCOW2_ZSTD_IMG) + .unwrap_or_else(|_| panic!("Failed to remove file '{QCOW2_ZSTD_IMG}'.")); } // Performance tests are expected to be executed sequentially, so we can From 5dd565944051c1f295735d4f35c8dc03fbb431ac Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 16 Feb 2026 08:32:00 -0800 Subject: [PATCH 0569/1893] build: Switch to released version of acpi_tables Replace git version with latest released version from crates.io Signed-off-by: Rob Bradford --- Cargo.lock | 17 +++++++++-------- Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 753c53cc0c..09665ac0e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,8 +4,9 @@ version = 4 [[package]] name = "acpi_tables" -version = "0.1.0" -source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#e08a3f0b0a59b98859dbf59f5aa7fd4d2eb4018a" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad581b2b0fa02638f3df6ff3f852ebc30dc7cfe531e9745d1ca4c0f283a6dbe" dependencies = [ "zerocopy", ] @@ -70,7 +71,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -81,7 +82,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -617,7 +618,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -699,7 +700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -1932,7 +1933,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2140,7 +2141,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3005f232b3..d0c08caeeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ resolver = "3" [workspace.dependencies] # rust-vmm crates -acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" } +acpi_tables = "0.2.0" kvm-bindings = "0.12.1" kvm-ioctls = "0.22.1" linux-loader = "0.13.1" From 2a5c4f0e4d34ce9a56e26f395c6e621cc41a9829 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:56:10 +0000 Subject: [PATCH 0570/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 4ab462f329..0427708458 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.43.4 + - uses: crate-ci/typos@v1.43.5 From 08f338bcf651dfccb27369f1c84851d6b42883a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:12:57 +0000 Subject: [PATCH 0571/1893] 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] --- Cargo.lock | 143 ++++++++++++++++++++---------------------- Cargo.toml | 8 +-- fuzz/Cargo.lock | 87 ++++++++++++------------- fuzz/Cargo.toml | 4 +- hypervisor/Cargo.toml | 2 +- vmm/Cargo.toml | 2 +- 6 files changed, 120 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09665ac0e8..5541f19fc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" dependencies = [ "rustversion", ] @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -315,15 +315,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block" version = "0.1.0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "crc-any", "flate2", @@ -370,9 +370,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -399,18 +399,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" dependencies = [ "anstream", "anstyle", @@ -421,9 +421,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cloud-hypervisor" @@ -564,7 +564,7 @@ dependencies = [ "anyhow", "arch", "bitfield-struct 0.12.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "event_monitor", "hypervisor", @@ -656,9 +656,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" dependencies = [ "log", "regex", @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ "anstream", "anstyle", @@ -683,7 +683,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", ] @@ -792,9 +792,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -807,9 +807,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -817,15 +817,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -834,9 +834,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -853,9 +853,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -864,21 +864,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -888,7 +888,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -898,7 +897,7 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bf845b08f7c2ef3b5ad19f80779d43ae20d278652b91bb80adda65baf2d8ed6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "log", "managed", @@ -1098,7 +1097,7 @@ version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "libc", ] @@ -1135,9 +1134,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" +checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" dependencies = [ "jiff-static", "log", @@ -1148,9 +1147,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" +checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" dependencies = [ "proc-macro2", "quote", @@ -1194,7 +1193,7 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "kvm-bindings", "libc", "vmm-sys-util", @@ -1225,9 +1224,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libredox" @@ -1235,7 +1234,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", ] @@ -1593,12 +1592,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "piper" version = "0.2.4" @@ -1857,7 +1850,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -1929,7 +1922,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -2102,7 +2095,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f84d13b3b8a0d4e91a2629911e951db1bb8671512f5c09d7d4ba34500ba68c8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "libssh2-sys", "parking_lot", @@ -2122,9 +2115,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -2238,9 +2231,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -2311,9 +2304,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-xid" @@ -2329,11 +2322,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "rand 0.9.2", "serde_core", @@ -2380,7 +2373,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "log", "serde", @@ -2398,7 +2391,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "uuid", "vm-memory", @@ -2573,7 +2566,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.10.0", + "bitflags 2.11.0", "block", "blocking", "cfg-if", @@ -2755,7 +2748,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap", "semver", @@ -2939,7 +2932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "indexmap", "log", "serde", @@ -3052,9 +3045,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index d0c08caeeb..6d64650487 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,21 +75,21 @@ serde_with = { version = "3.16.1", default-features = false } # other crates anyhow = "1.0.101" -bitflags = "2.10.0" +bitflags = "2.11.0" byteorder = "1.5.0" cfg-if = "1.0.4" -clap = "4.5.57" +clap = "4.5.59" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" epoll = "4.4.0" flume = "0.12.0" itertools = "0.14.0" -libc = "0.2.181" +libc = "0.2.182" log = "0.4.29" signal-hook = "0.4.3" thiserror = "2.0.18" -uuid = { version = "1.20.0" } +uuid = { version = "1.21.0" } wait-timeout = "0.2.1" zerocopy = { version = "0.8.39", default-features = false } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 55be75cd53..37e0f2aa2d 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -4,8 +4,9 @@ version = 4 [[package]] name = "acpi_tables" -version = "0.1.0" -source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#e08a3f0b0a59b98859dbf59f5aa7fd4d2eb4018a" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad581b2b0fa02638f3df6ff3f852ebc30dc7cfe531e9745d1ca4c0f283a6dbe" dependencies = [ "zerocopy", ] @@ -80,9 +81,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" dependencies = [ "rustversion", ] @@ -131,15 +132,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block" version = "0.1.0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "crc-any", "flate2", @@ -172,9 +173,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -190,18 +191,18 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.57" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" dependencies = [ "anstream", "anstyle", @@ -211,9 +212,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cloud-hypervisor-fuzz" @@ -323,7 +324,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "event_monitor", "hypervisor", @@ -373,7 +374,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", ] @@ -461,15 +462,15 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "gdbstub" @@ -477,7 +478,7 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bf845b08f7c2ef3b5ad19f80779d43ae20d278652b91bb80adda65baf2d8ed6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "log", "managed", @@ -672,7 +673,7 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "kvm-bindings", "libc", "vmm-sys-util", @@ -703,15 +704,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libfuzzer-sys" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" dependencies = [ "arbitrary", "cc", @@ -1169,9 +1170,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -1241,9 +1242,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -1272,9 +1273,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-xid" @@ -1290,11 +1291,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "rand", "wasm-bindgen", @@ -1332,7 +1333,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "log", "serde", @@ -1350,7 +1351,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "uuid", "vm-memory", @@ -1471,7 +1472,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.10.0", + "bitflags 2.11.0", "block", "cfg-if", "clap", @@ -1635,7 +1636,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap", "semver", @@ -1745,7 +1746,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "indexmap", "log", "serde", @@ -1797,9 +1798,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zstd" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 931b7862c8..888928af06 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,8 +21,8 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.181" -libfuzzer-sys = "0.4.10" +libc = "0.2.182" +libfuzzer-sys = "0.4.12" linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } mshv-bindings = "0.6.6" diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index f7336aadc7..e18f9ba390 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -14,7 +14,7 @@ tdx = [] [dependencies] anyhow = { workspace = true } -arc-swap = "1.8.1" +arc-swap = "1.8.2" bitfield-struct = "0.12.0" byteorder = { workspace = true } cfg-if = { workspace = true } diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 6310e3428b..4cfa4ed3a1 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -47,7 +47,7 @@ dhat = { workspace = true, optional = true } epoll = { workspace = true } event_monitor = { path = "../event_monitor" } flume = { workspace = true } -futures = { version = "0.3.31", optional = true } +futures = { version = "0.3.32", optional = true } gdbstub = { version = "0.7.9", optional = true } gdbstub_arch = { version = "0.3.2", optional = true } hex = { version = "0.4.3", optional = true } From 0a5e79afce6166b3bb22b92579158c6f66a41cdf Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 17 Feb 2026 22:36:45 +0000 Subject: [PATCH 0572/1893] vmm: api: Expose the nested option in API description Signed-off-by: Wei Liu --- vmm/src/api/openapi/cloud-hypervisor.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index d1c67c97f9..50d6351a7a 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -703,6 +703,9 @@ components: default: false max_phys_bits: type: integer + nested: + type: boolean + default: true affinity: type: array items: From 924baa9c0fc0b8d4fd19abdea918bcf4f26e766c Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Tue, 17 Feb 2026 23:25:22 +0000 Subject: [PATCH 0573/1893] 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 --- vmm/src/vm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index dc5a96a4b1..db29072bed 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1243,6 +1243,8 @@ impl Vm { } } + node.device_id = config.device_id.clone(); + numa_nodes.insert(config.guest_numa_id, node); } } From 17b0749e1cf6db00f689c84850208c73c21909ad Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Tue, 17 Feb 2026 23:44:32 +0000 Subject: [PATCH 0574/1893] 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 --- cloud-hypervisor/tests/integration.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 6a58f96c22..3d54b4bd75 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -12030,7 +12030,8 @@ mod vfio { handle_child_output(r, &output); } - fn test_guest_numa_generic_initiator() { + #[test] + fn test_nvidia_guest_numa_generic_initiator() { // Skip test if VFIO device is not available or not ready if !std::path::Path::new(NVIDIA_VFIO_DEVICE).exists() { println!("SKIPPED: VFIO device {} not found", NVIDIA_VFIO_DEVICE); From 003ef780e01b94f0dd9be1116ccfd23af9e1af53 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 18 Feb 2026 04:19:57 -0800 Subject: [PATCH 0575/1893] build: Bump to latest igvm released version 0.4.0 Signed-off-by: Rob Bradford --- Cargo.lock | 6 ++++-- Cargo.toml | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5541f19fc6..80afaecdb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1054,7 +1054,8 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "igvm" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#9848d1f2a8a44931395a30debeae287b6d01505d" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67578b05ebcdfa1aa0fe13f77a13bdd7d87036128898a327f1bf8e7356cf09cd" dependencies = [ "bitfield-struct 0.10.1", "crc32fast", @@ -1071,7 +1072,8 @@ dependencies = [ [[package]] name = "igvm_defs" version = "0.4.0" -source = "git+https://github.com/microsoft/igvm?branch=main#9848d1f2a8a44931395a30debeae287b6d01505d" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eedd8c64460676101062f9f2ecdeb52d8f43e622da6a6c5bf5158f4ef08b0906" dependencies = [ "bitfield-struct 0.10.1", "open-enum", diff --git a/Cargo.toml b/Cargo.toml index 6d64650487..53537895d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,9 +64,8 @@ vm-memory = "0.16.1" vmm-sys-util = "0.14.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" From 68f746fbea328d0a3319104e6a718efb562db928 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Wed, 18 Feb 2026 07:34:26 +0000 Subject: [PATCH 0576/1893] 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 --- hypervisor/src/mshv/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index b7bdbf6a59..613c4dc77e 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -19,8 +19,7 @@ use mshv_bindings::*; #[cfg(target_arch = "x86_64")] use mshv_ioctls::InterruptRequest; use mshv_ioctls::{ - Mshv, NoDatamatch, VcpuFd, VmFd, VmType, make_default_partition_create_arg, - make_default_synthetic_features_mask, set_registers_64, + Mshv, NoDatamatch, VcpuFd, VmFd, VmType, make_default_synthetic_features_mask, set_registers_64, }; use vfio_ioctls::VfioDeviceFd; use vm::DataMatch; @@ -286,7 +285,8 @@ impl hypervisor::Hypervisor for MshvHypervisor { VmType::Normal }; } - let mut create_args = make_default_partition_create_arg(mshv_vm_type); + + let mut create_args = self.mshv.make_default_partition_create_arg(mshv_vm_type); let mut disable_proc_features = hv_partition_processor_features::default(); // SAFETY: Accessing a union element from bindgen generated bindings. unsafe { From 94368c622e1571ca9f169ea4cfcb2c4b8969ffb2 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 19 Feb 2026 11:22:28 +0100 Subject: [PATCH 0577/1893] 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 --- block/src/qcow/mod.rs | 2 ++ block/src/qcow_sync.rs | 17 +++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 65027bfaf1..fd932406c0 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -44,6 +44,8 @@ pub enum Error { BackingFileIo(#[source] io::Error), #[error("Backing file open error")] BackingFileOpen(#[source] Box), + #[error("Backing file support is disabled")] + BackingFilesDisabled, #[error("Backing file name is too long: {0} bytes over")] BackingFileTooLong(usize), #[error("Image is marked corrupt and cannot be opened for writing")] diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 4c7486f22c..d4dfc9b787 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -14,7 +14,7 @@ use vmm_sys_util::write_zeroes::PunchHole; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::qcow::{MAX_NESTING_DEPTH, QcowFile, RawFile, Result as QcowResult}; +use crate::qcow::{Error as QcowError, MAX_NESTING_DEPTH, QcowFile, RawFile, Result as QcowResult}; use crate::{AsyncAdaptor, BlockBackend}; pub struct QcowDiskSync { @@ -32,12 +32,17 @@ pub struct QcowDiskSync { impl QcowDiskSync { pub fn new(file: File, direct_io: bool, backing_files: bool, sparse: bool) -> QcowResult { let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 }; + let qcow_file = QcowFile::from_with_nesting_depth( + RawFile::new(file, direct_io), + max_nesting_depth, + sparse, + ) + .map_err(|e| match e { + QcowError::MaxNestingDepthExceeded if !backing_files => QcowError::BackingFilesDisabled, + other => other, + })?; Ok(QcowDiskSync { - qcow_file: Arc::new(Mutex::new(QcowFile::from_with_nesting_depth( - RawFile::new(file, direct_io), - max_nesting_depth, - sparse, - )?)), + qcow_file: Arc::new(Mutex::new(qcow_file)), }) } } From 4d30ba12c8b2269e65fe31168e63ea1db065b9e8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 19 Feb 2026 11:27:35 +0100 Subject: [PATCH 0578/1893] 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 --- block/src/qcow_sync.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index d4dfc9b787..2707f5dfba 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -229,7 +229,7 @@ mod unit_tests { use vmm_sys_util::tempfile::TempFile; use super::*; - use crate::qcow::{QcowFile, RawFile}; + use crate::qcow::{QcowFile, QcowHeader, RawFile}; #[test] fn test_qcow_async_punch_hole_completion() { @@ -482,4 +482,23 @@ mod unit_tests { "After punch_hole via new_async_io, read should return zeros" ); } + + #[test] + fn backing_files_disabled_error() { + let header = + QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/path/to/backing/file")) + .expect("Failed to create header."); + let temp_file = TempFile::new().unwrap(); + let mut raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + header + .write_to(&mut raw_file) + .expect("Failed to write header."); + + let file = temp_file.into_file(); + match QcowDiskSync::new(file, false, false, true) { + Err(QcowError::BackingFilesDisabled) => {} + Err(other) => panic!("Expected BackingFilesDisabled, got: {other:?}"), + Ok(_) => panic!("Expected BackingFilesDisabled error, but succeeded"), + } + } } From e36096db3e8a9adba23348185f97cac59812db14 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 19 Feb 2026 19:41:34 +0100 Subject: [PATCH 0579/1893] vmm: openapi: Sync DiskConfig OpenAPI spec Add backing_files and sparse fields to the REST API. Signed-off-by: Anatol Belski --- vmm/src/api/openapi/cloud-hypervisor.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 50d6351a7a..01c1b9ed9c 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -941,6 +941,12 @@ components: type: array items: $ref: "#/components/schemas/VirtQueueAffinity" + backing_files: + type: boolean + default: false + sparse: + type: boolean + default: true NetConfig: type: object From 6f2357c14ed83582fc085bc74acfe3a270e6320e Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Feb 2026 11:25:53 +0000 Subject: [PATCH 0580/1893] 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 --- block/src/lib.rs | 37 ++++++++++- cloud-hypervisor/src/main.rs | 14 ++-- cloud-hypervisor/tests/integration.rs | 81 ++++++++++++++++++----- vmm/src/api/openapi/cloud-hypervisor.yaml | 3 + vmm/src/config.rs | 21 +++++- vmm/src/device_manager.rs | 42 +++++++++++- vmm/src/vm_config.rs | 3 + 7 files changed, 172 insertions(+), 29 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 0d248fb202..d8ae276474 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -30,12 +30,13 @@ pub mod vhdx_sync; use std::alloc::{Layout, alloc_zeroed, dealloc}; use std::collections::VecDeque; -use std::fmt::Debug; +use std::fmt::{self, Debug}; use std::fs::File; use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; use std::os::linux::fs::MetadataExt; use std::os::unix::io::AsRawFd; use std::path::Path; +use std::str::FromStr; use std::time::Instant; use std::{cmp, result}; @@ -1005,12 +1006,44 @@ pub trait AsyncAdaptor { } } -#[derive(PartialEq, Eq, Debug)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum ImageType { FixedVhd, Qcow2, Raw, Vhdx, + #[default] + Unknown, +} + +impl fmt::Display for ImageType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ImageType::FixedVhd => write!(f, "vhd"), + ImageType::Qcow2 => write!(f, "qcow2"), + ImageType::Raw => write!(f, "raw"), + ImageType::Vhdx => write!(f, "vhdx"), + ImageType::Unknown => write!(f, "unknown"), + } + } +} + +pub enum ImageTypeParseError { + InvalidValue(String), +} + +impl FromStr for ImageType { + type Err = ImageTypeParseError; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "vhd" => Ok(ImageType::FixedVhd), + "qcow2" => Ok(ImageType::Qcow2), + "raw" => Ok(ImageType::Raw), + "vhdx" => Ok(ImageType::Vhdx), + _ => Err(ImageTypeParseError::InvalidValue(s.to_string())), + } + } } const QCOW_MAGIC: u32 = 0x5146_49fb; diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 3abc382f35..1d78b400bc 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -1199,14 +1199,14 @@ mod unit_tests { "--kernel", "/path/to/kernel", "--disk", - "path=/path/to/disk/1", + "path=/path/to/disk/1,image_type=raw", "path=/path/to/disk/2", ], r#"{ "payload": {"kernel": "/path/to/kernel"}, "disks": [ - {"path": "/path/to/disk/1"}, - {"path": "/path/to/disk/2"} + {"path": "/path/to/disk/1", "image_type": "Raw"}, + {"path": "/path/to/disk/2", "image_type": "Unknown"} ] }"#, true, @@ -1217,8 +1217,8 @@ mod unit_tests { "--kernel", "/path/to/kernel", "--disk", - "path=/path/to/disk/1", - "path=/path/to/disk/2", + "path=/path/to/disk/1,image_type=raw", + "path=/path/to/disk/2,image_type=qcow2", ], r#"{ "payload": {"kernel": "/path/to/kernel"}, @@ -1280,8 +1280,8 @@ mod unit_tests { r#"{ "payload": {"kernel": "/path/to/kernel"}, "disks": [ - {"path": "/path/to/disk/1", "rate_limit_group": "group0"}, - {"path": "/path/to/disk/2", "rate_limit_group": "group0"} + {"path": "/path/to/disk/1", "rate_limit_group": "group0", "image_type": "Unknown"}, + {"path": "/path/to/disk/2", "rate_limit_group": "group0", "image_type": "Unknown"} ], "rate_limit_groups": [ {"id": "group0", "rate_limiter_config": {"bandwidth": {"size": 1000, "one_time_burst": 0, "refill_time": 100}}} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 3d54b4bd75..1aad1a372c 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2627,6 +2627,8 @@ mod common_parallel { use std::io::{self, SeekFrom}; use std::process::Command; + use block::ImageType; + use crate::*; #[test] @@ -3186,7 +3188,7 @@ mod common_parallel { guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - format!("path={test_disk_path},pci_segment=15").as_str(), + format!("path={test_disk_path},pci_segment=15,image_type=raw").as_str(), ]) .capture_output() .default_net(); @@ -3423,6 +3425,7 @@ mod common_parallel { disable_aio: bool, verify_os_disk: bool, backing_files: bool, + image_type: ImageType, ) { let disk_config = UbuntuDiskConfig::new(image_name.to_string()); let guest = Guest::new(Box::new(disk_config)); @@ -3449,9 +3452,9 @@ mod common_parallel { .args([ "--disk", format!( - "path={},backing_files={}", + "path={},backing_files={},image_type={image_type}", guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), - if backing_files { "on"} else {"off"} + if backing_files { "on"} else {"off"}, ) .as_str(), format!( @@ -3530,17 +3533,17 @@ mod common_parallel { #[test] fn test_virtio_block_io_uring() { - _test_virtio_block(FOCAL_IMAGE_NAME, false, true, false, false); + _test_virtio_block(FOCAL_IMAGE_NAME, false, true, false, false, ImageType::Raw); } #[test] fn test_virtio_block_aio() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, false, false, false); + _test_virtio_block(FOCAL_IMAGE_NAME, true, false, false, false, ImageType::Raw); } #[test] fn test_virtio_block_sync() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, true, false, false); + _test_virtio_block(FOCAL_IMAGE_NAME, true, true, false, false, ImageType::Raw); } fn run_qemu_img(path: &std::path::Path, args: &[&str]) -> std::process::Output { @@ -3770,17 +3773,38 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2, false, false, true, false); + _test_virtio_block( + JAMMY_IMAGE_NAME_QCOW2, + false, + false, + true, + false, + ImageType::Qcow2, + ); } #[test] fn test_virtio_block_qcow2_zlib() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZLIB, false, false, true, false); + _test_virtio_block( + JAMMY_IMAGE_NAME_QCOW2_ZLIB, + false, + false, + true, + false, + ImageType::Qcow2, + ); } #[test] fn test_virtio_block_qcow2_zstd() { - _test_virtio_block(JAMMY_IMAGE_NAME_QCOW2_ZSTD, false, false, true, false); + _test_virtio_block( + JAMMY_IMAGE_NAME_QCOW2_ZSTD, + false, + false, + true, + false, + ImageType::Qcow2, + ); } #[test] @@ -3791,6 +3815,7 @@ mod common_parallel { false, true, true, + ImageType::Qcow2, ); } @@ -3802,6 +3827,7 @@ mod common_parallel { false, true, true, + ImageType::Qcow2, ); } @@ -3813,6 +3839,7 @@ mod common_parallel { false, true, true, + ImageType::Qcow2, ); } @@ -3889,7 +3916,7 @@ mod common_parallel { guest.disk_config.disk(DiskType::CloudInit).unwrap() ), &format!( - "path={},num_queues=8,backing_files={}", + "path={},num_queues=8,backing_files={},image_type=qcow2", test_image_path.to_str().unwrap(), if initial_backing_checksum.is_some() { "on" @@ -4696,7 +4723,14 @@ mod common_parallel { .output() .expect("Expect generating VHD image from RAW image"); - _test_virtio_block(FOCAL_IMAGE_NAME_VHD, false, false, false, false); + _test_virtio_block( + FOCAL_IMAGE_NAME_VHD, + false, + false, + false, + false, + ImageType::FixedVhd, + ); } #[test] @@ -4720,7 +4754,14 @@ mod common_parallel { .output() .expect("Expect generating dynamic VHDx image from RAW image"); - _test_virtio_block(FOCAL_IMAGE_NAME_VHDX, false, false, true, false); + _test_virtio_block( + FOCAL_IMAGE_NAME_VHDX, + false, + false, + true, + false, + ImageType::Vhdx, + ); } #[test] @@ -5785,7 +5826,7 @@ mod common_parallel { guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - format!("path={}", vfio_disk_path.to_str().unwrap()).as_str(), + format!("path={},image_type=raw", vfio_disk_path.to_str().unwrap()).as_str(), format!("path={},iommu=on,readonly=true", blk_file_path.to_str().unwrap()).as_str(), ]) .args([ @@ -7364,7 +7405,12 @@ mod common_parallel { guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - format!("path={},num_queues=4", test_disk_path.to_str().unwrap()).as_str(), + format!( + "path={},num_queues=4,image_type={}", + test_disk_path.to_str().unwrap(), + format_name.to_lowercase() + ) + .as_str(), ]) .default_net() .capture_output() @@ -7600,7 +7646,12 @@ mod common_parallel { guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - format!("path={},num_queues=4", test_disk_path.to_str().unwrap()).as_str(), + format!( + "path={},num_queues=4,image_type={}", + test_disk_path.to_str().unwrap(), + format_name.to_lowercase() + ) + .as_str(), ]) .default_net() .capture_output() diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 01c1b9ed9c..359a7d9af3 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -947,6 +947,9 @@ components: sparse: type: boolean default: true + image_type: + type: enum ["FixedVhd", "Qcow2", "Raw", "Vhdx"] + NetConfig: type: object diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 16089e5583..a4339c27b8 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -10,6 +10,7 @@ use std::path::PathBuf; use std::result; use std::str::FromStr; +use block::ImageType; use clap::ArgMatches; use log::{debug, warn}; use option_parser::{ @@ -1096,7 +1097,8 @@ impl DiskConfig { ops_size=,ops_one_time_burst=,ops_refill_time=,\ id=,pci_segment=,rate_limit_group=,\ queue_affinity=,\ - serial=,backing_files=on|off,sparse=on|off"; + serial=,backing_files=on|off,sparse=on|off,\ + image_type="; pub fn parse(disk: &str) -> Result { let mut parser = OptionParser::new(); @@ -1123,7 +1125,9 @@ impl DiskConfig { .add("rate_limit_group") .add("queue_affinity") .add("backing_files") - .add("sparse"); + .add("sparse") + .add("image_type"); + parser.parse(disk).map_err(Error::ParseDisk)?; let path = parser.get("path").map(PathBuf::from); @@ -1208,12 +1212,22 @@ impl DiskConfig { }) .collect() }); + let backing_files = parser .convert::("backing_files") .map_err(Error::ParseDisk)? .unwrap_or(Toggle(false)) .0; + let image_type = if vhost_socket.is_none() { + parser + .convert::("image_type") + .map_err(Error::ParseDisk)? + .unwrap_or(ImageType::Unknown) + } else { + ImageType::Unknown + }; + let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { Some(TokenBucketConfig { size: bw_size, @@ -1265,6 +1279,7 @@ impl DiskConfig { queue_affinity, backing_files, sparse, + image_type, }) } @@ -3516,6 +3531,7 @@ mod unit_tests { queue_affinity: None, backing_files: false, sparse: true, + image_type: ImageType::Unknown, } } @@ -3538,6 +3554,7 @@ mod unit_tests { path: None, vhost_socket: Some(String::from("/tmp/sock")), vhost_user: true, + image_type: ImageType::Unknown, ..disk_fixture() } ); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 842f529c81..d12317d8b3 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -677,6 +677,15 @@ pub enum DeviceManagerError { /// Disk resizing failed. #[error("Disk resize error")] DiskResize(#[source] virtio_devices::block::Error), + + /// Disk image type does not match expected type. + #[error( + "Disk image type does not match expected type: specified = {specified}, detected = {detected}" + )] + DiskImageTypeMismatch { + specified: ImageType, + detected: ImageType, + }, } pub type DeviceManagerResult = result::Result; @@ -2665,14 +2674,40 @@ impl DeviceManager { .clone(), ) .map_err(DeviceManagerError::Disk)?; - let image_type = + + let detected_image_type = detect_image_type(&mut file).map_err(DeviceManagerError::DetectImageType)?; + if disk_cfg.image_type == ImageType::Unknown { + warn!( + "No image_type specified - detected as {detected_image_type}. \ + Configuration updated to persist type across reboots and migrations." + ); + + if detected_image_type != ImageType::Raw { + warn!( + "Non-raw image type detected. In the future it will be necessary \ + to specify image_type for non-raw files." + ); + } + + if detected_image_type == ImageType::Qcow2 && disk_cfg.backing_files { + warn!("QCOW2 image type autodetected. Disabling backing files"); + disk_cfg.backing_files = false; + } + + disk_cfg.image_type = detected_image_type; + } else if disk_cfg.image_type != detected_image_type { + return Err(DeviceManagerError::DiskImageTypeMismatch { + specified: disk_cfg.image_type, + detected: detected_image_type, + }); + } - if image_type != ImageType::Qcow2 && disk_cfg.backing_files { + if disk_cfg.image_type != ImageType::Qcow2 && disk_cfg.backing_files { warn!("Enabling backing_files option only applies for QCOW2 files"); } - let image = match image_type { + let image = match disk_cfg.image_type { ImageType::FixedVhd => { // Use asynchronous backend relying on io_uring if the // syscalls are supported. @@ -2749,6 +2784,7 @@ impl DeviceManager { .map_err(DeviceManagerError::CreateFixedVhdxDiskSync)?, ) as Box } + ImageType::Unknown => unreachable!(), }; let rate_limit_group = diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 407d4e491d..5de8c31452 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -8,6 +8,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fs, result}; +use block::ImageType; use log::{debug, warn}; use net_util::MacAddr; use serde::{Deserialize, Serialize}; @@ -288,6 +289,8 @@ pub struct DiskConfig { pub backing_files: bool, #[serde(default = "default_diskconfig_sparse")] pub sparse: bool, + #[serde(default)] + pub image_type: ImageType, } impl ApplyLandlock for DiskConfig { From b3e8e2abc507030bedd543169c57322b65b7a922 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Feb 2026 22:51:56 +0000 Subject: [PATCH 0581/1893] 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 --- fuzz/fuzz_targets/block.rs | 1 + virtio-devices/src/block.rs | 24 +++++++++++++++++++++--- vmm/src/device_manager.rs | 8 +++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index be461d7a7a..7d1fbdf38f 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -68,6 +68,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { None, queue_affinity, true, + false, ) .unwrap(); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 50d8dac65d..1822762fcf 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -160,6 +160,7 @@ struct BlockEpollHandler { access_platform: Option>, host_cpus: Option>, acked_features: u64, + disable_sector0_writes: bool, } fn has_feature(features: u64, feature_flag: u64) -> bool { @@ -167,8 +168,13 @@ fn has_feature(features: u64, feature_flag: u64) -> bool { } impl BlockEpollHandler { - fn check_request(features: u64, request_type: RequestType) -> result::Result<(), ExecuteError> { - if has_feature(features, VIRTIO_BLK_F_RO.into()) + fn check_request( + features: u64, + request: &Request, + disable_sector0_writes: bool, + ) -> result::Result<(), ExecuteError> { + let request_type = request.request_type; + if (has_feature(features, VIRTIO_BLK_F_RO.into())) && !(request_type == RequestType::In || request_type == RequestType::GetDeviceId || request_type == RequestType::Flush) @@ -178,6 +184,11 @@ impl BlockEpollHandler { // if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data." return Err(ExecuteError::ReadOnly); } + + if request_type == RequestType::Out && disable_sector0_writes && request.sector == 0 { + return Err(ExecuteError::ReadOnly); + } + Ok(()) } @@ -193,7 +204,10 @@ impl BlockEpollHandler { // For virtio spec compliance // "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request // if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data." - if let Err(e) = Self::check_request(self.acked_features, request.request_type) { + // Also, if sector 0 writes are disabled, treat writes to sector 0 as read-only as well. + if let Err(e) = + Self::check_request(self.acked_features, &request, self.disable_sector0_writes) + { warn!("Request check failed: {request:x?} {e:?}"); desc_chain .memory() @@ -646,6 +660,7 @@ pub struct Block { exit_evt: EventFd, serial: Vec, queue_affinity: BTreeMap>, + disable_sector0_writes: bool, } #[derive(Serialize, Deserialize)] @@ -675,6 +690,7 @@ impl Block { state: Option, queue_affinity: BTreeMap>, sparse: bool, + disable_sector0_writes: bool, ) -> io::Result { let (disk_nsectors, avail_features, acked_features, config, paused) = if let Some(state) = state { @@ -789,6 +805,7 @@ impl Block { exit_evt, serial, queue_affinity, + disable_sector0_writes, }) } @@ -1040,6 +1057,7 @@ impl VirtioDevice for Block { access_platform: self.common.access_platform.clone(), host_cpus: self.queue_affinity.get(&queue_idx).cloned(), acked_features: self.common.acked_features, + disable_sector0_writes: self.disable_sector0_writes, }; let paused = self.common.paused.clone(); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index d12317d8b3..a7d3254c3f 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2677,13 +2677,18 @@ impl DeviceManager { let detected_image_type = detect_image_type(&mut file).map_err(DeviceManagerError::DetectImageType)?; + let mut disable_sector0_writes = false; + if disk_cfg.image_type == ImageType::Unknown { warn!( "No image_type specified - detected as {detected_image_type}. \ Configuration updated to persist type across reboots and migrations." ); - if detected_image_type != ImageType::Raw { + if detected_image_type == ImageType::Raw { + warn!("Autodetected raw image type. Disabling sector 0 writes."); + disable_sector0_writes = true; + } else { warn!( "Non-raw image type detected. In the future it will be necessary \ to specify image_type for non-raw files." @@ -2850,6 +2855,7 @@ impl DeviceManager { .map_err(DeviceManagerError::RestoreGetState)?, queue_affinity, disk_cfg.sparse, + disable_sector0_writes, ) .map_err(DeviceManagerError::CreateVirtioBlock)?; From 76e233504bea53bba731bb7f2c8324b433fedbf4 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 19 Feb 2026 04:11:28 -0800 Subject: [PATCH 0582/1893] 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 --- vhost_user_block/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 456af40d9e..13668e1cdb 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -224,9 +224,9 @@ impl VhostUserBlkBackend { let image_type = qcow::detect_image_type(&mut raw_img).unwrap(); let image = match image_type { ImageType::Raw => Arc::new(Mutex::new(raw_img)) as Arc>, - ImageType::Qcow2 => { - Arc::new(Mutex::new(QcowFile::from(raw_img).unwrap())) as Arc> - } + ImageType::Qcow2 => Arc::new(Mutex::new( + QcowFile::from_with_nesting_depth(raw_img, 0, true).unwrap(), + )) as Arc>, }; let nsectors = (image.lock().unwrap().seek(SeekFrom::End(0)).unwrap()) / SECTOR_SIZE; From a63315df54e06f6ec867f17b63076c266e2d8648 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 19 Feb 2026 11:55:36 -0800 Subject: [PATCH 0583/1893] 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 --- block/src/lib.rs | 10 ++++++++++ virtio-devices/src/block.rs | 1 + 2 files changed, 11 insertions(+) diff --git a/block/src/lib.rs b/block/src/lib.rs index d8ae276474..9f78cefd9e 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -430,6 +430,7 @@ impl Request { disk_nsectors: u64, disk_image: &mut dyn AsyncIo, serial: &[u8], + disable_sector0_writes: bool, user_data: u64, ) -> result::Result { let sector = self.sector; @@ -586,6 +587,11 @@ impl Request { .map_err(ExecuteError::Read)?; let discard_sector = u64::from_le_bytes(discard_sector); + + if discard_sector == 0 && disable_sector0_writes { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + let discard_num_sectors = u32::from_le_bytes(discard_num_sectors); let discard_offset = discard_sector * SECTOR_SIZE; @@ -607,6 +613,7 @@ impl Request { } let mut wz_sector = [0u8; 8]; + let mut wz_num_sectors = [0u8; 4]; mem.read_slice(&mut wz_sector, data_addr) .map_err(ExecuteError::Read)?; @@ -617,6 +624,9 @@ impl Request { let wz_num_sectors = u32::from_le_bytes(wz_num_sectors); let wz_offset = wz_sector * SECTOR_SIZE; + if wz_offset == 0 && disable_sector0_writes { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } let wz_length = (wz_num_sectors as u64) * SECTOR_SIZE; disk_image diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 1822762fcf..0d2b1fb271 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -263,6 +263,7 @@ impl BlockEpollHandler { self.disk_nsectors.load(Ordering::SeqCst), self.disk_image.as_mut(), &self.serial, + self.disable_sector0_writes, desc_chain.head_index() as u64, ); From 00e106e53e7ba48b01f194f7887b20b9a0bfb905 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 19 Feb 2026 03:37:17 +0000 Subject: [PATCH 0584/1893] build: Release v51.0 This release contains security fixes. Details can be found in the release notes. Signed-off-by: Bo Chen --- Cargo.lock | 2 +- cloud-hypervisor/Cargo.toml | 2 +- release-notes.md | 318 +++++++++++++++++++++++++----------- 3 files changed, 228 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80afaecdb5..12304950bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -427,7 +427,7 @@ checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cloud-hypervisor" -version = "50.0.0" +version = "51.0.0" dependencies = [ "anyhow", "api_client", diff --git a/cloud-hypervisor/Cargo.toml b/cloud-hypervisor/Cargo.toml index 69ddf345c2..542d1859d8 100644 --- a/cloud-hypervisor/Cargo.toml +++ b/cloud-hypervisor/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" license = "Apache-2.0 AND BSD-3-Clause" name = "cloud-hypervisor" -version = "50.0.0" +version = "51.0.0" # Minimum buildable version: # Keep in sync with version in .github/workflows/build.yaml # Policy on MSRV (see #4318): diff --git a/release-notes.md b/release-notes.md index 82df4ed948..aff2dbe882 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,19 +1,28 @@ +- [v51.0](#v510) + - [Security Fixes](#security-fixes) + - [Significant QCOW2 v3 Improvements](#significant-qcow2-v3-improvements) + - [ACPI Generic Initiator Support](#acpi-generic-initiator-support) + - [Block Device DISCARD and WRITE_ZEROES Support](#block-device-discard-and-write_zeroes-support) + - [Notable Performance Improvements](#notable-performance-improvements) + - [MSHV Support Improvements](#mshv-support-improvements) + - [Notable Bug Fixes](#notable-bug-fixes) + - [Contributors](#contributors) - [v50.0](#v500) - [Configurable Nested Virtualization Option on x86_64](#configurable-nested-virtualization-option-on-x86_64) - [Compression Support for QCOW2](#compression-support-for-qcow2) - - [Notable Performance Improvements](#notable-performance-improvements) + - [Notable Performance Improvements](#notable-performance-improvements-1) + - [Live Disk Resizing Support for Raw Images](#live-disk-resizing-support-for-raw-images) - [Developer Experience Improvements](#developer-experience-improvements) - [Improved File-level Locking Support](#improved-file-level-locking-support) - - [Live Disk Resizing Support for Raw Images](#live-disk-resizing-support-for-raw-images) - [Logging Improvements](#logging-improvements) - - [Notable Bug Fixes](#notable-bug-fixes) - - [Contributors](#contributors) + - [Notable Bug Fixes](#notable-bug-fixes-1) + - [Contributors](#contributors-1) - [v49.0](#v490) - - [MSHV Support Improvements](#mshv-support-improvements) + - [MSHV Support Improvements](#mshv-support-improvements-1) - [Logging Improvements](#logging-improvements-1) - [Removed Default IP and Mask for `virtio-net` Devices](#removed-default-ip-and-mask-for-virtio-net-devices) - - [Notable Bug Fixes](#notable-bug-fixes-1) - - [Contributors](#contributors-1) + - [Notable Bug Fixes](#notable-bug-fixes-2) + - [Contributors](#contributors-2) - [v48.0](#v480) - [Experimental `fw_cfg` Device Support](#experimental-fw_cfg-device-support) - [Experimental `ivshmem` Device Support](#experimental-ivshmem-device-support) @@ -24,62 +33,62 @@ - [Updated Documentation on Windows Guest Support](#updated-documentation-on-windows-guest-support) - [Policy on AI Generated Code](#policy-on-ai-generated-code) - [Removed SGX Support](#removed-sgx-support) - - [Notable Bug Fixes](#notable-bug-fixes-2) - - [Contributors](#contributors-2) + - [Notable Bug Fixes](#notable-bug-fixes-3) + - [Contributors](#contributors-3) - [v47.0](#v470) - [Block Device Error Reporting to the Guest](#block-device-error-reporting-to-the-guest) - [Nice Error Messages on Exit](#nice-error-messages-on-exit) - [Alphabetically Sorted CLI Options for ch-remote](#alphabetically-sorted-cli-options-for-ch-remote) - - [Notable Bug Fixes](#notable-bug-fixes-3) + - [Notable Bug Fixes](#notable-bug-fixes-4) - [Deprecations](#deprecations) - - [Contributors](#contributors-3) + - [Contributors](#contributors-4) - [v46.0](#v460) - [File-level Locking Support with `--disk`](#file-level-locking-support-with---disk) - [Improved Error Reporting with VM Resizing](#improved-error-reporting-with-vm-resizing) - [IPv6 Address Support with `--net`](#ipv6-address-support-with---net) - [Experimental AArch64 Support with the MSHV Hypervisor](#experimental-aarch64-support-with-the-mshv-hypervisor) - [Deprecated SGX Support](#deprecated-sgx-support) - - [Notable Bug Fixes](#notable-bug-fixes-4) - - [Contributors](#contributors-4) + - [Notable Bug Fixes](#notable-bug-fixes-5) + - [Contributors](#contributors-5) - [v45.0](#v450) - [Experimental `riscv64` Architecture Support](#experimental-riscv64-architecture-support) - [Alphabetically Sorted CLI Options](#alphabetically-sorted-cli-options) - [Improved Downtime of VM Live Migration](#improved-downtime-of-vm-live-migration) - - [Notable Bug Fixes](#notable-bug-fixes-5) - - [Contributors](#contributors-5) -- [v44.0](#v440) - - [Configurable `virtio-iommu` Address Width](#configurable-virtio-iommu-address-width) - - [Notable Performance Improvements](#notable-performance-improvements-1) - - [New Fuzzers](#new-fuzzers) - [Notable Bug Fixes](#notable-bug-fixes-6) - [Contributors](#contributors-6) -- [v43.0](#v430) - - [Live Migration over TCP Connections](#live-migration-over-tcp-connections) +- [v44.0](#v440) + - [Configurable `virtio-iommu` Address Width](#configurable-virtio-iommu-address-width) - [Notable Performance Improvements](#notable-performance-improvements-2) + - [New Fuzzers](#new-fuzzers) - [Notable Bug Fixes](#notable-bug-fixes-7) - [Contributors](#contributors-7) +- [v43.0](#v430) + - [Live Migration over TCP Connections](#live-migration-over-tcp-connections) + - [Notable Performance Improvements](#notable-performance-improvements-3) + - [Notable Bug Fixes](#notable-bug-fixes-8) + - [Contributors](#contributors-8) - [v42.0](#v420) - [SVE/SVE2 Support on AArch64](#svesve2-support-on-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-8) + - [Notable Bug Fixes](#notable-bug-fixes-9) - [Sponsorships](#sponsorships) - - [Contributors](#contributors-8) + - [Contributors](#contributors-9) - [v41.0](#v410) - [Experimental "Pvmemcontrol" Support](#experimental-pvmemcontrol-support) - [Sandboxing With Landlock Support](#sandboxing-with-landlock-support) - - [Notable Performance Improvements](#notable-performance-improvements-3) - - [Notable Bug Fixes](#notable-bug-fixes-9) - - [Contributors](#contributors-9) -- [v40.0](#v400) - - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) + - [Notable Performance Improvements](#notable-performance-improvements-4) - [Notable Bug Fixes](#notable-bug-fixes-10) - [Contributors](#contributors-10) +- [v40.0](#v400) + - [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices) + - [Notable Bug Fixes](#notable-bug-fixes-11) + - [Contributors](#contributors-11) - [v39.0](#v390) - [Variable Sizing of PCI Apertures for Segments](#variable-sizing-of-pci-apertures-for-segments) - [Direct Booting with bzImages](#direct-booting-with-bzimages) - [Support for NVIDIA GPUDirect P2P Support](#support-for-nvidia-gpudirect-p2p-support) - [Guest NMI Injection Support](#guest-nmi-injection-support) - - [Notable Bug Fixes](#notable-bug-fixes-11) - - [Contributors](#contributors-11) + - [Notable Bug Fixes](#notable-bug-fixes-12) + - [Contributors](#contributors-12) - [v38.0](#v380) - [Group Rate Limiter on Block Devices](#group-rate-limiter-on-block-devices) - [CPU Pinning Support for Block Device Worker Thread](#cpu-pinning-support-for-block-device-worker-thread) @@ -87,16 +96,16 @@ - [New 'debug-console' Device](#new-debug-console-device) - [Improved VFIO Device Support](#improved-vfio-device-support) - [Extended CPU Affinity Support](#extended-cpu-affinity-support) - - [Notable Bug Fixes](#notable-bug-fixes-12) - - [Contributors](#contributors-12) + - [Notable Bug Fixes](#notable-bug-fixes-13) + - [Contributors](#contributors-13) - [v37.0](#v370) - [Long Term Support (LTS) Release](#long-term-support-lts-release) - [Multiple PCI segments Support for 32-bit VFIO devices](#multiple-pci-segments-support-for-32-bit-vfio-devices) - [Configurable Named TAP Devices](#configurable-named-tap-devices) - [TTY Output from Both Serial Device and Virtio Console](#tty-output-from-both-serial-device-and-virtio-console) - [Faster VM Restoration from Snapshots](#faster-vm-restoration-from-snapshots) - - [Notable Bug Fixes](#notable-bug-fixes-13) - - [Contributors](#contributors-13) + - [Notable Bug Fixes](#notable-bug-fixes-14) + - [Contributors](#contributors-14) - [v36.0](#v360) - [Command Line Changes](#command-line-changes) - [Enabled Features Reported via API Endpoint and CLI](#enabled-features-reported-via-api-endpoint-and-cli) @@ -105,31 +114,31 @@ - [Unix Socket Backend for Serial Port](#unix-socket-backend-for-serial-port) - [AIO Backend for Block Devices](#aio-backend-for-block-devices) - [Documentation Improvements](#documentation-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-14) - - [Contributors](#contributors-14) + - [Notable Bug Fixes](#notable-bug-fixes-15) + - [Contributors](#contributors-15) - [v35.0](#v350) - [`virtio-vsock` Support for Linux Guest Kernel v6.3+](#virtio-vsock-support-for-linux-guest-kernel-v63) - [User Specified Serial Number for `virtio-block`](#user-specified-serial-number-for-virtio-block) - [vCPU TSC Frequency Included in Migration State](#vcpu-tsc-frequency-included-in-migration-state) - - [Notable Bug Fixes](#notable-bug-fixes-15) - - [Contributors](#contributors-15) + - [Notable Bug Fixes](#notable-bug-fixes-16) + - [Contributors](#contributors-16) - [v34.0](#v340) - [Paravirtualised Panic Device Support](#paravirtualised-panic-device-support) - [Improvements to VM Core Dump](#improvements-to-vm-core-dump) - [QCOW2 Support for Backing Files](#qcow2-support-for-backing-files) - [Minimum Host Kernel Bump](#minimum-host-kernel-bump) - - [Notable Bug Fixes](#notable-bug-fixes-16) - - [Contributors](#contributors-16) + - [Notable Bug Fixes](#notable-bug-fixes-17) + - [Contributors](#contributors-17) - [v33.0](#v330) - [D-Bus based API](#d-bus-based-api) - [Expose Host CPU Cache Details for AArch64](#expose-host-cpu-cache-details-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-17) - - [Contributors](#contributors-17) + - [Notable Bug Fixes](#notable-bug-fixes-18) + - [Contributors](#contributors-18) - [v32.0](#v320) - [Increased PCI Segment Limit](#increased-pci-segment-limit) - [API Changes](#api-changes) - - [Notable Bug Fixes](#notable-bug-fixes-18) - - [Contributors](#contributors-18) + - [Notable Bug Fixes](#notable-bug-fixes-19) + - [Contributors](#contributors-19) - [v31.1](#v311) - [v31.0](#v310) - [Update to Latest `acpi_tables`](#update-to-latest-acpi_tables) @@ -137,15 +146,15 @@ - [Improvements on Console `SIGWINCH` Handler](#improvements-on-console-sigwinch-handler) - [Remove Directory Support from `MemoryZoneConfig::file`](#remove-directory-support-from-memoryzoneconfigfile) - [Documentation Improvements](#documentation-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-19) - - [Contributors](#contributors-19) + - [Notable Bug Fixes](#notable-bug-fixes-20) + - [Contributors](#contributors-20) - [v30.0](#v300) - [Command Line Changes for Reduced Binary Size](#command-line-changes-for-reduced-binary-size) - [Basic vfio-user Server Support](#basic-vfio-user-server-support) - [Heap Profiling Support](#heap-profiling-support) - [Documentation Improvements](#documentation-improvements-2) - - [Notable Bug Fixes](#notable-bug-fixes-20) - - [Contributors](#contributors-20) + - [Notable Bug Fixes](#notable-bug-fixes-21) + - [Contributors](#contributors-21) - [v28.2](#v282) - [v29.0](#v290) - [Release Binary Supports Both MSHV and KVM](#release-binary-supports-both-mshv-and-kvm) @@ -155,10 +164,10 @@ - [`AArch64` Documentation Integration](#aarch64-documentation-integration) - [`virtio-block` Counters Enhancement](#virtio-block-counters-enhancement) - [TCP Offload Control](#tcp-offload-control) - - [Notable Bug Fixes](#notable-bug-fixes-21) + - [Notable Bug Fixes](#notable-bug-fixes-22) - [Removals](#removals) - [Deprecations](#deprecations-1) - - [Contributors](#contributors-21) + - [Contributors](#contributors-22) - [v28.1](#v281) - [v28.0](#v280) - [Community Engagement (Reminder)](#community-engagement-reminder) @@ -166,9 +175,9 @@ - [Virtualised TPM Support](#virtualised-tpm-support) - [Transparent Huge Page Support](#transparent-huge-page-support) - [README Quick Start Improved](#readme-quick-start-improved) - - [Notable Bug Fixes](#notable-bug-fixes-22) + - [Notable Bug Fixes](#notable-bug-fixes-23) - [Removals](#removals-1) - - [Contributors](#contributors-22) + - [Contributors](#contributors-23) - [v27.0](#v270) - [Community Engagement](#community-engagement) - [Prebuilt Packages](#prebuilt-packages) @@ -177,41 +186,41 @@ - [Simplified Build Feature Flags](#simplified-build-feature-flags) - [Asynchronous Kernel Loading](#asynchronous-kernel-loading) - [GDB Support for AArch64](#gdb-support-for-aarch64) - - [Notable Bug Fixes](#notable-bug-fixes-23) + - [Notable Bug Fixes](#notable-bug-fixes-24) - [Deprecations](#deprecations-2) - - [Contributors](#contributors-23) + - [Contributors](#contributors-24) - [v26.0](#v260) - [SMBIOS Improvements via `--platform`](#smbios-improvements-via---platform) - [Unified Binary MSHV and KVM Support](#unified-binary-mshv-and-kvm-support) - - [Notable Bug Fixes](#notable-bug-fixes-24) + - [Notable Bug Fixes](#notable-bug-fixes-25) - [Deprecations](#deprecations-3) - [Removals](#removals-2) - - [Contributors](#contributors-24) + - [Contributors](#contributors-25) - [v25.0](#v250) - [`ch-remote` Improvements](#ch-remote-improvements-1) - [VM "Coredump" Support](#vm-coredump-support) - - [Notable Bug Fixes](#notable-bug-fixes-25) + - [Notable Bug Fixes](#notable-bug-fixes-26) - [Removals](#removals-3) - - [Contributors](#contributors-25) + - [Contributors](#contributors-26) - [v24.0](#v240) - [Bypass Mode for `virtio-iommu`](#bypass-mode-for-virtio-iommu) - [Ensure Identifiers Uniqueness](#ensure-identifiers-uniqueness) - [Sparse Mmap support](#sparse-mmap-support) - [Expose Platform Serial Number](#expose-platform-serial-number) - - [Notable Bug Fixes](#notable-bug-fixes-26) + - [Notable Bug Fixes](#notable-bug-fixes-27) - [Notable Improvements](#notable-improvements) - [Deprecations](#deprecations-4) - [New on the Website](#new-on-the-website) - - [Contributors](#contributors-26) + - [Contributors](#contributors-27) - [v23.1](#v231) - [v23.0](#v230) - [vDPA Support](#vdpa-support) - [Updated OS Support list](#updated-os-support-list) - [`AArch64` Memory Map Improvements](#aarch64-memory-map-improvements) - [`AMX` Support](#amx-support) - - [Notable Bug Fixes](#notable-bug-fixes-27) + - [Notable Bug Fixes](#notable-bug-fixes-28) - [Deprecations](#deprecations-5) - - [Contributors](#contributors-27) + - [Contributors](#contributors-28) - [v22.1](#v221) - [v22.0](#v220) - [GDB Debug Stub Support](#gdb-debug-stub-support) @@ -222,13 +231,13 @@ - [PMU Support for AArch64](#pmu-support-for-aarch64) - [Documentation Under CC-BY-4.0 License](#documentation-under-cc-by-40-license) - [Deprecation of "Classic" `virtiofsd`](#deprecation-of-classic-virtiofsd) - - [Notable Bug Fixes](#notable-bug-fixes-28) - - [Contributors](#contributors-28) + - [Notable Bug Fixes](#notable-bug-fixes-29) + - [Contributors](#contributors-29) - [v21.0](#v210) - [Efficient Local Live Migration (for Live Upgrade)](#efficient-local-live-migration-for-live-upgrade) - [Recommended Kernel is Now 5.15](#recommended-kernel-is-now-515) - - [Notable Bug fixes](#notable-bug-fixes-29) - - [Contributors](#contributors-29) + - [Notable Bug fixes](#notable-bug-fixes-30) + - [Contributors](#contributors-30) - [v20.2](#v202) - [v20.1](#v201) - [v20.0](#v200) @@ -237,8 +246,8 @@ - [Improved VFIO support](#improved-vfio-support) - [Safer code](#safer-code) - [Extended documentation](#extended-documentation) - - [Notable bug fixes](#notable-bug-fixes-30) - - [Contributors](#contributors-30) + - [Notable bug fixes](#notable-bug-fixes-31) + - [Contributors](#contributors-31) - [v19.0](#v190) - [Improved PTY handling for serial and `virtio-console`](#improved-pty-handling-for-serial-and-virtio-console) - [PCI boot time optimisations](#pci-boot-time-optimisations) @@ -246,8 +255,8 @@ - [Live migration enhancements](#live-migration-enhancements) - [`virtio-mem` support with `vfio-user`](#virtio-mem-support-with-vfio-user) - [AArch64 for `virtio-iommu`](#aarch64-for-virtio-iommu) - - [Notable bug fixes](#notable-bug-fixes-31) - - [Contributors](#contributors-31) + - [Notable bug fixes](#notable-bug-fixes-32) + - [Contributors](#contributors-32) - [v18.0](#v180) - [Experimental User Device (`vfio-user`) support](#experimental-user-device-vfio-user-support) - [Migration support for `vhost-user` devices](#migration-support-for-vhost-user-devices) @@ -257,23 +266,23 @@ - [Live migration on MSHV hypervisor](#live-migration-on-mshv-hypervisor) - [AArch64 CPU topology support](#aarch64-cpu-topology-support) - [Power button support on AArch64](#power-button-support-on-aarch64) - - [Notable bug fixes](#notable-bug-fixes-32) - - [Contributors](#contributors-32) + - [Notable bug fixes](#notable-bug-fixes-33) + - [Contributors](#contributors-33) - [v17.0](#v170) - [ARM64 NUMA support using ACPI](#arm64-numa-support-using-acpi) - [`Seccomp` support for MSHV backend](#seccomp-support-for-mshv-backend) - [Hotplug of `macvtap` devices](#hotplug-of-macvtap-devices) - [Improved SGX support](#improved-sgx-support) - [Inflight tracking for `vhost-user` devices](#inflight-tracking-for-vhost-user-devices) - - [Notable bug fixes](#notable-bug-fixes-33) - - [Contributors](#contributors-33) + - [Notable bug fixes](#notable-bug-fixes-34) + - [Contributors](#contributors-34) - [v16.0](#v160) - [Improved live migration support](#improved-live-migration-support) - [Improved `vhost-user` support](#improved-vhost-user-support) - [ARM64 ACPI and UEFI support](#arm64-acpi-and-uefi-support) - - [Notable bug fixes](#notable-bug-fixes-34) + - [Notable bug fixes](#notable-bug-fixes-35) - [Removed functionality](#removed-functionality) - - [Contributors](#contributors-34) + - [Contributors](#contributors-35) - [v15.0](#v150) - [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees) - [Network device rate limiting](#network-device-rate-limiting) @@ -281,7 +290,7 @@ - [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter) - [Bug fixes](#bug-fixes) - [Deprecations](#deprecations-6) - - [Contributors](#contributors-35) + - [Contributors](#contributors-36) - [v0.14.1](#v0141) - [v0.14.0](#v0140) - [Structured event monitoring](#structured-event-monitoring) @@ -291,7 +300,7 @@ - [PTY control for serial and `virtio-console`](#pty-control-for-serial-and-virtio-console) - [Block device rate limiting](#block-device-rate-limiting) - [Deprecations](#deprecations-7) - - [Contributors](#contributors-36) + - [Contributors](#contributors-37) - [v0.13.0](#v0130) - [Wider VFIO device support](#wider-vfio-device-support) - [Improved huge page support](#improved-huge-page-support) @@ -299,13 +308,13 @@ - [VHD disk image support](#vhd-disk-image-support) - [Improved Virtio device threading](#improved-virtio-device-threading) - [Clean shutdown support via synthetic power button](#clean-shutdown-support-via-synthetic-power-button) - - [Contributors](#contributors-37) + - [Contributors](#contributors-38) - [v0.12.0](#v0120) - [ARM64 enhancements](#arm64-enhancements) - [Removal of `vhost-user-net` and `vhost-user-block` self spawning](#removal-of-vhost-user-net-and-vhost-user-block-self-spawning) - [Migration of `vhost-user-fs` backend](#migration-of-vhost-user-fs-backend) - [Enhanced "info" API](#enhanced-info-api) - - [Contributors](#contributors-38) + - [Contributors](#contributors-39) - [v0.11.0](#v0110) - [`io_uring` support by default for `virtio-block`](#io_uring-support-by-default-for-virtio-block) - [Windows Guest Support](#windows-guest-support) @@ -317,15 +326,15 @@ - [Default Log Level Changed](#default-log-level-changed) - [New `--balloon` Parameter Added](#new---balloon-parameter-added) - [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support) - - [Notable Bug Fixes](#notable-bug-fixes-35) - - [Contributors](#contributors-39) + - [Notable Bug Fixes](#notable-bug-fixes-36) + - [Contributors](#contributors-40) - [v0.10.0](#v0100) - [`virtio-block` Support for Multiple Descriptors](#virtio-block-support-for-multiple-descriptors) - [Memory Zones](#memory-zones) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements) - [Preliminary KVM HyperV Emulation Control](#preliminary-kvm-hyperv-emulation-control) - - [Notable Bug Fixes](#notable-bug-fixes-36) - - [Contributors](#contributors-40) + - [Notable Bug Fixes](#notable-bug-fixes-37) + - [Contributors](#contributors-41) - [v0.9.0](#v090) - [`io_uring` Based Block Device Support](#io_uring-based-block-device-support) - [Block and Network Device Statistics](#block-and-network-device-statistics) @@ -338,17 +347,17 @@ - [Enhancements to ARM64 Support](#enhancements-to-arm64-support) - [Intel SGX Support](#intel-sgx-support) - [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1) - - [Notable Bug Fixes](#notable-bug-fixes-37) - - [Contributors](#contributors-41) + - [Notable Bug Fixes](#notable-bug-fixes-38) + - [Contributors](#contributors-42) - [v0.8.0](#v080) - [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support) - [Experimental ARM64 Support](#experimental-arm64-support) - [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests) - [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices) - [`vhost_user_fs` Improvements](#vhost_user_fs-improvements) - - [Notable Bug Fixes](#notable-bug-fixes-38) + - [Notable Bug Fixes](#notable-bug-fixes-39) - [Command Line and API Changes](#command-line-and-api-changes) - - [Contributors](#contributors-42) + - [Contributors](#contributors-43) - [v0.7.0](#v070) - [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug) - [Alternative `libc` Support](#alternative-libc-support) @@ -358,14 +367,14 @@ - [`Seccomp` Sandboxing](#seccomp-sandboxing) - [Updated Distribution Support](#updated-distribution-support) - [Command Line and API Changes](#command-line-and-api-changes-1) - - [Contributors](#contributors-43) + - [Contributors](#contributors-44) - [v0.6.0](#v060) - [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug) - [Shared Filesystem Improvements](#shared-filesystem-improvements) - [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading) - [Command Line Interface](#command-line-interface) - [PVH Boot](#pvh-boot) - - [Contributors](#contributors-44) + - [Contributors](#contributors-45) - [v0.5.1](#v051) - [v0.5.0](#v050) - [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing) @@ -373,7 +382,7 @@ - [New Interrupt Management Framework](#new-interrupt-management-framework) - [Development Tools](#development-tools) - [Kata Containers Integration](#kata-containers-integration) - - [Contributors](#contributors-45) + - [Contributors](#contributors-46) - [v0.4.0](#v040) - [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition) - [Programmatic firmware tables generation](#programmatic-firmware-tables-generation) @@ -382,7 +391,7 @@ - [Userspace IOAPIC by default](#userspace-ioapic-by-default) - [PCI BAR reprogramming](#pci-bar-reprogramming) - [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization) - - [Contributors](#contributors-46) + - [Contributors](#contributors-47) - [v0.3.0](#v030) - [Block device offloading](#block-device-offloading) - [Network device backend](#network-device-backend) @@ -409,6 +418,131 @@ - [Unit testing](#unit-testing) - [Integration tests parallelization](#integration-tests-parallelization) +# v51.0 + +This release has been tracked in [v51.0 +group](https://github.com/orgs/cloud-hypervisor/projects/6/views/6?filterQuery=release%3A%22Release+51%22) +of our [roadmap project](https://github.com/orgs/cloud-hypervisor/projects/6/). + +### Security Fixes + +This release fixes a security vulnerability in disk image handling. +Details can be found in +[GHSA-jmr4-g2hv-mjj6](https://github.com/cloud-hypervisor/cloud-hypervisor/security/advisories/GHSA-jmr4-g2hv-mjj6). + +* A new `backing_files=on|off` option has been added to `--disk` to + explicitly control whether QCOW2 backing files are permitted. This + defaults to `off` to prevent the loading of backing files entirely. + (#7685) +* Explicit image type specification via the user interface, removing + reliance on format autodetection (#7728). +* Prevent sector-zero writes for autodetected raw images (#7728). + +### Significant QCOW2 v3 Improvements + +A large number of QCOW2 v3 specification features have been implemented: + +* RAW backing file support for QCOW2 overlays (#7570) +* Zero bit in L2 entries (#7627) +* Incompatible feature bit validation (#7612) +* Dirty bit support (#7636) +* Variable refcount widths (1 to 64-bit) (#7633) +* Corrupt bit detection and marking (#7639) +* Autoclear feature bits handling (#7648) +* Thread safety fix for multiple virtio queues (`num_queues > 1`) + (#7661) +* Correct zero-fill for reads beyond backing file size (#7678) +* Live disk resize support (#7687) + +### ACPI Generic Initiator Support + +ACPI Generic Initiator Affinity (SRAT Type 5) support has been added +to associate VFIO-PCI devices with dedicated memory/CPU-less NUMA +nodes. This enables the guest OS to make NUMA-aware memory allocation +decisions for device workloads. A new `device_id` parameter has been +added to `--numa` for specifying VFIO devices. (#7626) + +### Block Device DISCARD and WRITE_ZEROES Support + +The `virtio-blk` device now supports `DISCARD` and `WRITE_ZEROES` +operations for QCOW2 and RAW image formats. This enables thin +provisioning and efficient space reclamation when guests trim +filesystems. A new `sparse=on|off` option has been added to `--disk` to +control disk space management: `sparse=on` (default) enables thin +provisioning with space reclamation, while `sparse=off` provides thick +provisioning with consistent I/O latency. (#7666) + +### Notable Performance Improvements + +* Transparent Huge Pages (THP) support has been extended to cover + anonymous shared memory (`shared=on`) via `madvise`. Previously, THP + was only used for non-shared memory. (#7646) +* The `vhost-user-net` device now uses the default set of vhost-user + virtio features, including `VIRTIO_F_RING_INDIRECT_DESC`, which + provides a performance improvement. (#7653) + +### MSHV Support Improvements + +* Optimize CPU state update after emulation by only updating special + registers when changed (#7603) +* Enable SMT for guests with `threads_per_core > 1` (#7668) +* Stub `save_data_tables()` to unblock VM pause/resume (#7692) +* Handle `GHCB_INFO_SPECIAL_DBGPRINT` VMG exit in SEV-SNP guest exit + handler (#7703) +* Fix CVM boot failure on MSHV (#7548) +* Fix CPU topology detection for multithreaded configurations (#7576) + +### Notable Bug Fixes + +* Fix VFIO device hot-remove leaving group and container file + descriptors open, preventing re-add (#7676) +* Fix snapshot restore when backing file is on read-only storage with + `shared=false` (#7674) +* Enforce `VIRTIO_BLK_F_RO` even if guest does not negotiate it + (#7705) +* Fix read-only block device FLUSH requests from OVMF preventing VMs + from booting (#7706) +* Fix vhost-user device not properly dropping unowned file descriptors + (#7679) +* Fix `vhost-user-block` `get_config` interoperability (#7617) +* Fix vsock TOCTOU race condition by copying packet header from guest + memory before processing (#7530) +* Fix vsock handling of large TX packets spanning multiple data + descriptors (#7680) +* Add `gettid()` to all seccomp filters (#7596) +* Fix MAC address parsing that wrongly allowed `+` instead of hex + characters (#7579) +* Improve UUID parse error message and `--net` fd help text (#7702) +* Fix various inconsistencies in our OpenAPI specification file + (#7716, #7726) +* Various documentation fixes (#7602, #7606) + +### Contributors + +Many thanks to everyone who has contributed to our release: + +* Aastha Rawat +* Alyssa Ross +* Anatol Belski +* Anirudh Rayabharam +* Bo Chen +* Champ-Goblem +* Changyuan Lyu +* Damian Barabonkov +* Demi Marie Obenour +* Leander Kohler +* Muminul Islam +* Philipp Schuster +* Rob Bradford +* Rowen-Ye +* Saravanan D +* Stanislav Kinsburskii +* Thomas Leroy +* Wei Liu +* Yi Wang +* Zhibin Li +* stevenhorsman + # v50.0 This release has been tracked in [v50.0 From 52b2ebb2b80d958793c144c5850286a071dd6ad2 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 20 Feb 2026 23:51:42 +0000 Subject: [PATCH 0585/1893] vmm: api: Fix image_type in OpenAPI definition Signed-off-by: Wei Liu --- vmm/src/api/openapi/cloud-hypervisor.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 359a7d9af3..629a6800d1 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -948,7 +948,8 @@ components: type: boolean default: true image_type: - type: enum ["FixedVhd", "Qcow2", "Raw", "Vhdx"] + type: string + enum: [FixedVhd, Qcow2, Raw, Vhdx, Unknown] NetConfig: From 8aaf3734aadbf550ad7df06328f5c7964fc38220 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Sat, 21 Feb 2026 01:32:02 +0000 Subject: [PATCH 0586/1893] build: Temporarily remove the vfio CI worker This runner machine is out for maintenance. Signed-off-by: Bo Chen --- .github/workflows/integration-vfio.yaml | 33 ------------------------- 1 file changed, 33 deletions(-) delete mode 100644 .github/workflows/integration-vfio.yaml diff --git a/.github/workflows/integration-vfio.yaml b/.github/workflows/integration-vfio.yaml deleted file mode 100644 index 218e897270..0000000000 --- a/.github/workflows/integration-vfio.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Cloud Hypervisor Tests (VFIO) -on: [merge_group, pull_request] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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" From ec5374cd9988beb6e21d913efc64d1e4f90c5a3e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 22 Feb 2026 00:22:49 +0100 Subject: [PATCH 0587/1893] 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 --- performance-metrics/src/performance_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index b959fce5c3..7eb07f368a 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -441,7 +441,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { let mut test_disk_arg = format!("path={test_file},queue_size={queue_size},num_queues={num_queues}"); if test_file == OVERLAY_WITH_QCOW2_BACKING || test_file == OVERLAY_WITH_RAW_BACKING { - test_disk_arg.push_str(",backing_files=on"); + test_disk_arg.push_str(",image_type=qcow2,backing_files=on"); } let mut child = GuestCommand::new(&guest) From 600e74f0af86a2897c397fea537fb20424dc51fe Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 23 Feb 2026 12:06:31 +0100 Subject: [PATCH 0588/1893] 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 --- scripts/dev_cli.sh | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index e537c499c2..0e190a0072 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -706,10 +706,21 @@ cmd_shell() { ensure_build_dir ensure_latest_ctr process_volumes_args - say_warn "Starting a privileged shell prompt as root ..." - say_warn "WARNING: Your $CLH_ROOT_DIR folder will be bind-mounted in the container under $CTR_CLH_ROOT_DIR" + + # Remaining args after -- are passed as a command to bash -c. + # With no args, an interactive shell is started. + tty_args="-ti" + shell_args=() + if [ $# -gt 0 ]; then + tty_args="" + shell_args+=("-c" "$*") + else + say_warn "Starting a privileged shell prompt as root ..." + say_warn "WARNING: Your $CLH_ROOT_DIR folder will be bind-mounted in the container under $CTR_CLH_ROOT_DIR" + fi + $DOCKER_RUNTIME run \ - -ti \ + $tty_args \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --privileged \ @@ -723,7 +734,8 @@ cmd_shell() { --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --entrypoint bash \ - "$CTR_IMAGE" + "$CTR_IMAGE" \ + "${shell_args[@]}" fix_dir_perms $? } From 184a229ca4a50c8292863fbe70e1b776fbd04772 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 23 Feb 2026 18:02:01 +0100 Subject: [PATCH 0589/1893] tests: vhdx: Enable VHDX fstrim integration test Remove the #[ignore] attribute from test_virtio_block_fstrim_unsupported_vhdx. Signed-off-by: Anatol Belski --- cloud-hypervisor/tests/integration.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 1aad1a372c..e9d2e66c1b 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7793,11 +7793,7 @@ mod common_parallel { _test_virtio_block_fstrim("vhd", "vpc", &["-o", "subformat=fixed"], false, false); } - // VHDX backend has a multiqueue bug causing filesystem corruption. - // The _test_virtio_block_fstrim helper uses num_queues>1 which triggers the bug. - // Ref: #7665 #[test] - #[ignore] fn test_virtio_block_fstrim_unsupported_vhdx() { _test_virtio_block_fstrim("vhdx", "vhdx", &[], false, false); } From 6f19d0071db4d447058d05a5f64a6426ea6d0117 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 23 Feb 2026 18:03:08 +0100 Subject: [PATCH 0590/1893] block: vhdx: Fix multiqueue data corruption Wrap the Vhdx instance in Arc> 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 --- block/src/vhdx_sync.rs | 46 +++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index fc236c15df..0a0dc47bc2 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -5,6 +5,7 @@ use std::collections::VecDeque; use std::fs::File; use std::os::fd::AsRawFd; +use std::sync::{Arc, Mutex}; use vmm_sys_util::eventfd::EventFd; @@ -15,24 +16,32 @@ use crate::vhdx::{Result as VhdxResult, Vhdx}; use crate::{AsyncAdaptor, BlockBackend, Error}; pub struct VhdxDiskSync { - vhdx_file: Vhdx, + // 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>, } impl VhdxDiskSync { pub fn new(f: File) -> VhdxResult { Ok(VhdxDiskSync { - vhdx_file: Vhdx::new(f)?, + vhdx_file: Arc::new(Mutex::new(Vhdx::new(f)?)), }) } } impl DiskFile for VhdxDiskSync { fn logical_size(&mut self) -> DiskFileResult { - Ok(self.vhdx_file.virtual_disk_size()) + Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) } fn physical_size(&mut self) -> DiskFileResult { - self.vhdx_file.physical_size().map_err(|e| { + self.vhdx_file.lock().unwrap().physical_size().map_err(|e| { let io_inner = match e { Error::GetFileMetadata(e) => e, _ => unreachable!(), @@ -42,30 +51,28 @@ impl DiskFile for VhdxDiskSync { } fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { - Ok( - Box::new(VhdxSync::new(self.vhdx_file.clone()).map_err(DiskFileError::NewAsyncIo)?) - as Box, - ) + Ok(Box::new(VhdxSync::new(Arc::clone(&self.vhdx_file))) as Box) } fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.vhdx_file.as_raw_fd()) + BorrowedDiskFd::new(self.vhdx_file.lock().unwrap().as_raw_fd()) } } pub struct VhdxSync { - vhdx_file: Vhdx, + vhdx_file: Arc>, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, } impl VhdxSync { - pub fn new(vhdx_file: Vhdx) -> std::io::Result { - Ok(VhdxSync { + pub fn new(vhdx_file: Arc>) -> Self { + VhdxSync { vhdx_file, - eventfd: EventFd::new(libc::EFD_NONBLOCK)?, + eventfd: EventFd::new(libc::EFD_NONBLOCK) + .expect("Failed creating EventFd for VhdxSync"), completion_list: VecDeque::new(), - }) + } } } @@ -82,7 +89,7 @@ impl AsyncIo for VhdxSync { iovecs: &[libc::iovec], user_data: u64, ) -> AsyncIoResult<()> { - self.vhdx_file.read_vectored_sync( + self.vhdx_file.lock().unwrap().read_vectored_sync( offset, iovecs, user_data, @@ -97,7 +104,7 @@ impl AsyncIo for VhdxSync { iovecs: &[libc::iovec], user_data: u64, ) -> AsyncIoResult<()> { - self.vhdx_file.write_vectored_sync( + self.vhdx_file.lock().unwrap().write_vectored_sync( offset, iovecs, user_data, @@ -107,8 +114,11 @@ impl AsyncIo for VhdxSync { } fn fsync(&mut self, user_data: Option) -> AsyncIoResult<()> { - self.vhdx_file - .fsync_sync(user_data, &self.eventfd, &mut self.completion_list) + self.vhdx_file.lock().unwrap().fsync_sync( + user_data, + &self.eventfd, + &mut self.completion_list, + ) } fn next_completed_request(&mut self) -> Option<(u64, i32)> { From 8a09b3870c911682ff5b68bb647a52725a7a843a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 00:49:51 +0000 Subject: [PATCH 0591/1893] 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] --- Cargo.lock | 64 ++++++++++++++++++------------------- Cargo.toml | 4 +-- cloud-hypervisor/Cargo.toml | 2 +- fuzz/Cargo.lock | 20 ++++++------ vmm/Cargo.toml | 2 +- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12304950bd..ea880ebc97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -82,14 +82,14 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "api_client" @@ -358,9 +358,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -399,18 +399,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -618,7 +618,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -700,7 +700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -1136,9 +1136,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" +checksum = "b3e3d65f018c6ae946ab16e80944b97096ed73c35b221d1c478a6c81d8f57940" dependencies = [ "jiff-static", "log", @@ -1149,9 +1149,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" +checksum = "a17c2b211d863c7fde02cbea8a3c1a439b98e109286554f2860bdded7ff83818" dependencies = [ "proc-macro2", "quote", @@ -1277,9 +1277,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "lock_api" @@ -1920,15 +1920,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -2117,9 +2117,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -2136,7 +2136,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -2966,9 +2966,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.13.2" +version = "5.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" +checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" dependencies = [ "async-broadcast", "async-executor", @@ -3001,9 +3001,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.13.2" +version = "5.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" +checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3081,9 +3081,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.9.2" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" +checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" dependencies = [ "endi", "enumflags2", @@ -3095,9 +3095,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.9.2" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" +checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 53537895d7..e34941f358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,11 +73,11 @@ serde_json = "1.0.149" serde_with = { version = "3.16.1", default-features = false } # other crates -anyhow = "1.0.101" +anyhow = "1.0.102" bitflags = "2.11.0" byteorder = "1.5.0" cfg-if = "1.0.4" -clap = "4.5.59" +clap = "4.5.60" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" diff --git a/cloud-hypervisor/Cargo.toml b/cloud-hypervisor/Cargo.toml index 542d1859d8..d69773e743 100644 --- a/cloud-hypervisor/Cargo.toml +++ b/cloud-hypervisor/Cargo.toml @@ -38,7 +38,7 @@ tracer = { path = "../tracer" } vm-memory = { workspace = true } vmm = { path = "../vmm" } vmm-sys-util = { workspace = true } -zbus = { version = "5.13.2", optional = true } +zbus = { version = "5.14.0", optional = true } [dev-dependencies] block = { path = "../block" } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 37e0f2aa2d..52cf0e2286 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -69,9 +69,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbitrary" @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -191,18 +191,18 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -1170,9 +1170,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 4cfa4ed3a1..43b1de14b4 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -91,7 +91,7 @@ vm-memory = { workspace = true, features = [ vm-migration = { path = "../vm-migration" } vm-virtio = { path = "../vm-virtio" } vmm-sys-util = { workspace = true, features = ["with-serde"] } -zbus = { version = "5.13.2", optional = true } +zbus = { version = "5.14.0", optional = true } zerocopy = { workspace = true, features = ["alloc", "derive"] } [lints] From 8c618ff5e018e8c19b9c1cc6fb465b1e765feea8 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 4 Feb 2026 09:24:03 -0500 Subject: [PATCH 0592/1893] 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 --- virtio-devices/src/seccomp_filters.rs | 16 + .../src/vhost_user/generic_vhost_user.rs | 437 ++++++++++++++++++ virtio-devices/src/vhost_user/mod.rs | 2 + 3 files changed, 455 insertions(+) create mode 100644 virtio-devices/src/vhost_user/generic_vhost_user.rs diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs index 5afd056a6b..63d01a5d8d 100644 --- a/virtio-devices/src/seccomp_filters.rs +++ b/virtio-devices/src/seccomp_filters.rs @@ -24,6 +24,7 @@ pub enum Thread { VirtioRng, VirtioVhostBlock, VirtioVhostFs, + VirtioGenericVhostUser, VirtioVhostNet, VirtioVhostNetCtl, VirtioVsock, @@ -192,6 +193,20 @@ fn virtio_vhost_fs_thread_rules() -> Vec<(i64, Vec)> { ] } +fn virtio_generic_vhost_user_thread_rules() -> Vec<(i64, Vec)> { + vec![ + (libc::SYS_clock_nanosleep, vec![]), + (libc::SYS_connect, vec![]), + (libc::SYS_nanosleep, vec![]), + (libc::SYS_pread64, vec![]), + (libc::SYS_pwrite64, vec![]), + (libc::SYS_recvmsg, vec![]), + (libc::SYS_sendmsg, vec![]), + (libc::SYS_sendto, vec![]), + (libc::SYS_socket, vec![]), + ] +} + fn virtio_vhost_net_ctl_thread_rules() -> Vec<(i64, Vec)> { vec![] } @@ -271,6 +286,7 @@ fn get_seccomp_rules(thread_type: Thread) -> Vec<(i64, Vec)> { Thread::VirtioRng => virtio_rng_thread_rules(), Thread::VirtioVhostBlock => virtio_vhost_block_thread_rules(), Thread::VirtioVhostFs => virtio_vhost_fs_thread_rules(), + Thread::VirtioGenericVhostUser => virtio_generic_vhost_user_thread_rules(), Thread::VirtioVhostNet => virtio_vhost_net_thread_rules(), Thread::VirtioVhostNetCtl => virtio_vhost_net_ctl_thread_rules(), Thread::VirtioVsock => virtio_vsock_thread_rules(), diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs new file mode 100644 index 0000000000..5774af928a --- /dev/null +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -0,0 +1,437 @@ +// Copyright 2019 Intel Corporation. All Rights Reserved. +// Copyright 2025 Demi Marie Obenour. +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Barrier, Mutex}; +use std::{result, thread}; + +use event_monitor::event; +use log::{error, info, warn}; +use seccompiler::SeccompAction; +use serde::{Deserialize, Serialize}; +use vhost::vhost_user::message::{ + VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures, +}; +use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; +use virtio_queue::Queue; +use vm_device::UserspaceMapping; +use vm_memory::GuestMemoryAtomic; +use vm_migration::protocol::MemoryRangeTable; +use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; +use vmm_sys_util::eventfd::EventFd; + +use super::vu_common_ctrl::VhostUserHandle; +use super::{Error, Result}; +use crate::seccomp_filters::Thread; +use crate::thread_helper::spawn_virtio_thread; +use crate::vhost_user::VhostUserCommon; +use crate::{ + ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_IOMMU_PLATFORM, + VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioSharedMemoryList, +}; + +#[derive(Serialize, Deserialize)] +pub struct State { + pub avail_features: u64, + pub acked_features: u64, + pub acked_protocol_features: u64, + pub vu_num_queues: usize, + pub backend_req_support: bool, +} + +struct BackendReqHandler {} +impl VhostUserFrontendReqHandler for BackendReqHandler {} +pub struct GenericVhostUser { + common: VirtioCommon, + vu_common: VhostUserCommon, + id: String, + // Hold ownership of the memory that is allocated for the device + // which will be automatically dropped when the device is dropped + cache: Option<(VirtioSharedMemoryList, MmapRegion)>, + seccomp_action: SeccompAction, + guest_memory: Option>, + epoll_thread: Option>, + exit_evt: EventFd, + iommu: bool, + cfg_warning: AtomicBool, +} + +impl GenericVhostUser { + /// Create a new generic vhost-user device. + #[allow(clippy::too_many_arguments)] + pub fn new( + id: String, + path: &str, + request_queue_sizes: Vec, + device_type: u32, + cache: Option<(VirtioSharedMemoryList, MmapRegion)>, + seccomp_action: SeccompAction, + exit_evt: EventFd, + iommu: bool, + state: Option, + ) -> Result { + // Calculate the actual number of queues needed. + let num_queues = request_queue_sizes.len(); + + // Connect to the vhost-user socket. + let mut vu = VhostUserHandle::connect_vhost_user(false, path, num_queues as u64, false)?; + + let (avail_features, acked_features, acked_protocol_features, vu_num_queues, paused) = + if let Some(state) = state { + info!("Restoring generic vhost-user {id}"); + vu.set_protocol_features_vhost_user( + state.acked_features, + state.acked_protocol_features, + )?; + + ( + state.avail_features, + state.acked_features, + state.acked_protocol_features, + state.vu_num_queues, + true, + ) + } else { + let avail_protocol_features = VhostUserProtocolFeatures::CONFIG + | VhostUserProtocolFeatures::MQ + | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS + | VhostUserProtocolFeatures::REPLY_ACK + | VhostUserProtocolFeatures::INFLIGHT_SHMFD + | VhostUserProtocolFeatures::LOG_SHMFD; + + let avail_features = super::DEFAULT_VIRTIO_FEATURES; + + let (acked_features, acked_protocol_features) = + vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; + + let backend_num_queues = + if acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0 { + vu.socket_handle() + .get_queue_num() + .map_err(Error::VhostUserGetQueueMaxNum)? + as usize + } else { + num_queues + }; + + if num_queues > backend_num_queues { + error!( + "generic vhost-user requested too many queues ({num_queues}) \ +since the backend only supports {backend_num_queues}\n", + ); + return Err(Error::BadQueueNum); + } + // Create virtio-vhost-user device configuration. + ( + acked_features, + // If part of the available features that have been acked, the + // PROTOCOL_FEATURES bit must be already set through the VIRTIO + // acked features as we know the guest would never ack it, thus + // the feature would be lost. + acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(), + acked_protocol_features, + num_queues, + false, + ) + }; + + Ok(GenericVhostUser { + common: VirtioCommon { + device_type, + avail_features, + acked_features, + queue_sizes: request_queue_sizes, + paused_sync: Some(Arc::new(Barrier::new(2))), + min_queues: 1, + paused: Arc::new(AtomicBool::new(paused)), + ..Default::default() + }, + vu_common: VhostUserCommon { + vu: Some(Arc::new(Mutex::new(vu))), + acked_protocol_features, + socket_path: path.to_string(), + vu_num_queues, + ..Default::default() + }, + id, + cache, + seccomp_action, + guest_memory: None, + epoll_thread: None, + exit_evt, + iommu, + cfg_warning: AtomicBool::new(false), + }) + } + + fn state(&self) -> State { + State { + avail_features: self.common.avail_features, + acked_features: self.common.acked_features, + acked_protocol_features: self.vu_common.acked_protocol_features, + vu_num_queues: self.vu_common.vu_num_queues, + backend_req_support: false, + } + } + + #[cold] + #[inline(never)] + fn warn_no_config_access(&self) { + if self + .cfg_warning + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + warn!( + "Attempt to read config space, but backend does not support config \ +space access. Reads will return 0xFF and writes will be ignored." + ); + } + } +} + +impl Drop for GenericVhostUser { + fn drop(&mut self) { + if let Some(kill_evt) = self.common.kill_evt.take() { + // Ignore the result because there is nothing we can do about it. + let _ = kill_evt.write(1); + } + self.common.wait_for_epoll_threads(); + if let Some(thread) = self.epoll_thread.take() + && let Err(e) = thread.join() + { + error!("Error joining thread: {e:?}"); + } + } +} + +impl VirtioDevice for GenericVhostUser { + fn device_type(&self) -> u32 { + self.common.device_type + } + + fn queue_max_sizes(&self) -> &[u16] { + &self.common.queue_sizes + } + + fn features(&self) -> u64 { + let mut features = self.common.avail_features; + if self.iommu { + features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + } + features + } + + fn ack_features(&mut self, value: u64) { + self.common.ack_features(value); + } + + fn read_config(&self, offset: u64, data: &mut [u8]) { + if (VhostUserProtocolFeatures::CONFIG.bits() & self.state().acked_protocol_features) == 0 { + self.warn_no_config_access(); + + data.fill(0xFF); + return; + } + if let Err(e) = self + .vu_common + .vu + .as_ref() + .unwrap() + .lock() + .unwrap() + .socket_handle() + .get_config( + offset.try_into().unwrap(), + data.len().try_into().unwrap(), + VhostUserConfigFlags::empty(), + data, + ) + .map(|(_, config)| data.copy_from_slice(&config)) + { + panic!("Failed getting generic vhost-user configuration: {e}"); + } + } + + fn write_config(&mut self, offset: u64, data: &[u8]) { + if (VhostUserProtocolFeatures::CONFIG.bits() & self.state().acked_protocol_features) == 0 { + self.warn_no_config_access(); + return; + } + if let Err(e) = self + .vu_common + .vu + .as_ref() + .unwrap() + .lock() + .unwrap() + .socket_handle() + .set_config( + offset.try_into().unwrap(), + VhostUserConfigFlags::WRITABLE, + data, + ) + { + panic!("Failed setting generic vhost-user configuration: {e}"); + } + } + + fn activate( + &mut self, + mem: GuestMemoryAtomic, + interrupt_cb: Arc, + queues: Vec<(usize, Queue, EventFd)>, + ) -> ActivateResult { + self.common.activate(&queues, interrupt_cb.clone())?; + self.guest_memory = Some(mem.clone()); + + let backend_req_handler: Option> = None; + // Run a dedicated thread for handling potential reconnections with + // the backend. + let (kill_evt, pause_evt) = self.common.dup_eventfds(); + + let mut handler = self.vu_common.activate( + mem, + &queues, + interrupt_cb, + self.common.acked_features, + backend_req_handler, + kill_evt, + pause_evt, + )?; + + let paused = self.common.paused.clone(); + let paused_sync = self.common.paused_sync.clone(); + + let mut epoll_threads = Vec::new(); + spawn_virtio_thread( + &self.id, + &self.seccomp_action, + Thread::VirtioGenericVhostUser, + &mut epoll_threads, + &self.exit_evt, + move || handler.run(&paused, paused_sync.as_ref().unwrap()), + )?; + self.epoll_thread = Some(epoll_threads.remove(0)); + + event!("virtio-device", "activated", "id", &self.id); + Ok(()) + } + + fn reset(&mut self) -> Option> { + // We first must resume the virtio thread if it was paused. + if self.common.pause_evt.take().is_some() { + self.common.resume().ok()?; + } + + if let Some(vu) = &self.vu_common.vu + && let Err(e) = vu.lock().unwrap().reset_vhost_user() + { + error!("Failed to reset vhost-user daemon: {e:?}"); + return None; + } + + if let Some(kill_evt) = self.common.kill_evt.take() { + // Ignore the result because there is nothing we can do about it. + let _ = kill_evt.write(1); + } + + event!("virtio-device", "reset", "id", &self.id); + + // Return the interrupt + Some(self.common.interrupt_cb.take().unwrap()) + } + + fn shutdown(&mut self) { + self.vu_common.shutdown(); + } + + fn get_shm_regions(&self) -> Option { + self.cache.as_ref().map(|cache| cache.0.clone()) + } + + fn set_shm_regions( + &mut self, + shm_regions: VirtioSharedMemoryList, + ) -> std::result::Result<(), crate::Error> { + if let Some(cache) = self.cache.as_mut() { + cache.0 = shm_regions; + Ok(()) + } else { + Err(crate::Error::SetShmRegionsNotSupported) + } + } + + fn add_memory_region( + &mut self, + region: &Arc, + ) -> std::result::Result<(), crate::Error> { + self.vu_common.add_memory_region(&self.guest_memory, region) + } + + fn userspace_mappings(&self) -> Vec { + let mut mappings = Vec::new(); + if let Some(cache) = self.cache.as_ref() { + mappings.push(UserspaceMapping { + mem_slot: cache.0.mem_slot, + addr: cache.0.addr, + mapping: cache.0.mapping.clone(), + mergeable: false, + }); + } + + mappings + } +} + +impl Pausable for GenericVhostUser { + fn pause(&mut self) -> result::Result<(), MigratableError> { + self.vu_common.pause()?; + self.common.pause() + } + + fn resume(&mut self) -> result::Result<(), MigratableError> { + self.common.resume()?; + + if let Some(epoll_thread) = &self.epoll_thread { + epoll_thread.thread().unpark(); + } + + self.vu_common.resume() + } +} + +impl Snapshottable for GenericVhostUser { + fn id(&self) -> String { + self.id.clone() + } + + fn snapshot(&mut self) -> std::result::Result { + self.vu_common.snapshot(&self.state()) + } +} +impl Transportable for GenericVhostUser {} + +impl Migratable for GenericVhostUser { + fn start_dirty_log(&mut self) -> std::result::Result<(), MigratableError> { + self.vu_common.start_dirty_log(&self.guest_memory) + } + + fn stop_dirty_log(&mut self) -> std::result::Result<(), MigratableError> { + self.vu_common.stop_dirty_log() + } + + fn dirty_log(&mut self) -> std::result::Result { + self.vu_common.dirty_log(&self.guest_memory) + } + + fn start_migration(&mut self) -> std::result::Result<(), MigratableError> { + self.vu_common.start_migration() + } + + fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> { + self.vu_common + .complete_migration(self.common.kill_evt.take()) + } +} diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 7e2c162cb9..158da3d800 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -33,11 +33,13 @@ use crate::{ pub mod blk; pub mod fs; +pub mod generic_vhost_user; pub mod net; pub mod vu_common_ctrl; pub use self::blk::Blk; pub use self::fs::*; +pub use self::generic_vhost_user::GenericVhostUser; pub use self::net::Net; pub use self::vu_common_ctrl::VhostUserConfig; From 085a7a49fab7783ee760a746830a506409a28919 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 4 Feb 2026 09:52:42 -0500 Subject: [PATCH 0593/1893] vmm: generic vhost-user: add support Add VMM support for generic vhost-user devices. Signed-off-by: Demi Marie Obenour --- cloud-hypervisor/src/main.rs | 1 + option_parser/src/lib.rs | 16 +++ vmm/src/api/mod.rs | 50 +++++++- vmm/src/config.rs | 217 +++++++++++++++++++++++++++++++++++ vmm/src/device_manager.rs | 92 ++++++++++++++- vmm/src/lib.rs | 91 ++++++++++++++- vmm/src/vm.rs | 31 ++++- vmm/src/vm_config.rs | 25 ++++ 8 files changed, 515 insertions(+), 8 deletions(-) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 1d78b400bc..351dd73b78 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -998,6 +998,7 @@ mod unit_tests { }, balloon: None, fs: None, + generic_vhost_user: None, pmem: None, serial: ConsoleConfig { file: None, diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index 1722da39f2..699a26252c 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -4,6 +4,7 @@ // use std::collections::HashMap; +use std::fmt::{Display, Write}; use std::num::ParseIntError; use std::str::FromStr; @@ -240,6 +241,21 @@ impl FromStr for ByteSized { pub struct IntegerList(pub Vec); +impl Display for IntegerList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_char('[')?; + let mut iter = self.0.iter(); + if let Some(first) = iter.next() { + first.fmt(f)?; + for i in iter { + f.write_char(',')?; + i.fmt(f)?; + } + } + f.write_char(']') + } +} + #[derive(Error, Debug)] pub enum IntegerListParseError { #[error("invalid value: {0}")] diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 12ca6b9877..17ba0011b0 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -51,8 +51,8 @@ use crate::config::RestoreConfig; use crate::device_tree::DeviceTree; use crate::vm::{Error as VmError, VmState}; use crate::vm_config::{ - DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, - VmConfig, VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig, + UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; /// API errors are sent back from the VMM API server through the ApiResponse. @@ -170,6 +170,10 @@ pub enum ApiError { #[error("The fs could not be added to the VM")] VmAddFs(#[source] VmError), + /// The generic vhost-user device could not be added to the VM. + #[error("The generic vhost-user device could not be added to the VM")] + VmAddGenericVhostUser(#[source] VmError), + /// The pmem device could not be added to the VM. #[error("The pmem device could not be added to the VM")] VmAddPmem(#[source] VmError), @@ -340,6 +344,11 @@ pub trait RequestHandler { fn vm_add_fs(&mut self, fs_cfg: FsConfig) -> Result>, VmError>; + fn vm_add_generic_vhost_user( + &mut self, + fs_cfg: GenericVhostUserConfig, + ) -> Result>, VmError>; + fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> Result>, VmError>; fn vm_add_net(&mut self, net_cfg: NetConfig) -> Result>, VmError>; @@ -539,6 +548,43 @@ impl ApiAction for VmAddFs { } } +pub struct VmAddGenericVhostUser; + +impl ApiAction for VmAddGenericVhostUser { + type RequestBody = GenericVhostUserConfig; + type ResponseBody = Option; + + fn request( + &self, + config: Self::RequestBody, + response_sender: Sender, + ) -> ApiRequest { + Box::new(move |vmm| { + info!("API request event: VmAddGenericVhostUser {config:?}"); + + let response = vmm + .vm_add_generic_vhost_user(config) + .map_err(ApiError::VmAddGenericVhostUser) + .map(ApiResponsePayload::VmAction); + + response_sender + .send(response) + .map_err(VmmError::ApiResponseSend)?; + + Ok(false) + }) + } + + fn send( + &self, + api_evt: EventFd, + api_sender: Sender, + data: Self::RequestBody, + ) -> ApiResult { + get_response_body(self, api_evt, api_sender, data) + } +} + pub struct VmAddPmem; impl ApiAction for VmAddPmem { diff --git a/vmm/src/config.rs b/vmm/src/config.rs index a4339c27b8..25c0d96ae6 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -46,6 +46,24 @@ pub enum Error { /// Filesystem socket is missing #[error("Error parsing --fs: socket missing")] ParseFsSockMissing, + /// Generic vhost-user socket is missing + #[error("Error parsing --generic-vhost-user: socket missing")] + ParseGenericVhostUserSockMissing, + /// Generic vhost-user number of queues is missing + #[error("Error parsing --generic-vhost-user: number of queues missing")] + ParseGenericVhostUserNumResponseQueuesMissing, + /// Generic vhost-user virtio ID is missing + #[error("Error parsing --generic-vhost-user: virtio ID missing")] + ParseGenericVhostUserVirtioIdMissing, + /// Generic vhost-user available features is missing + #[error("Error parsing --generic-vhost-user: available features missing")] + ParseGenericVhostUserAvailFeaturesMissing, + /// Generic vhost-user queue size is too large + #[error("Error parsing --generic-vhost-user: queue size {0} is {1}, but limit is 65535")] + ParseGenericVhostUserQueueSizeTooLarge(usize, u64), + /// Generic vhost-user queue size missing + #[error("Error parsing --generic-vhost-user: queue size missing")] + ParseGenericVhostUserQueueSizeMissing, /// Missing persistent memory file parameter. #[error("Error parsing --pmem: file missing")] ParsePmemFileMissing, @@ -94,6 +112,9 @@ pub enum Error { /// Error parsing persistent memory parameters #[error("Error parsing --pmem")] ParsePersistentMemory(#[source] OptionParserError), + /// Error parsing generic vhost-user parameters + #[error("Error parsing --generic-vhost-user")] + ParseGenericVhostUser(#[source] OptionParserError), /// Failed parsing console #[error("Error parsing --console")] ParseConsole(#[source] OptionParserError), @@ -394,6 +415,7 @@ pub struct VmParams<'a> { pub rng: &'a str, pub balloon: Option<&'a str>, pub fs: Option>, + pub generic_vhost_user: Option>, pub pmem: Option>, pub serial: &'a str, pub console: &'a str, @@ -455,6 +477,9 @@ impl<'a> VmParams<'a> { let fs: Option> = args .get_many::("fs") .map(|x| x.map(|y| y as &str).collect()); + let generic_vhost_user: Option> = args + .get_many::("generic-vhost-user") + .map(|x| x.map(|y| y as &str).collect()); let pmem: Option> = args .get_many::("pmem") .map(|x| x.map(|y| y as &str).collect()); @@ -509,6 +534,7 @@ impl<'a> VmParams<'a> { rng, balloon, fs, + generic_vhost_user, pmem, serial, console, @@ -1642,6 +1668,82 @@ impl BalloonConfig { } } +impl GenericVhostUserConfig { + pub const SYNTAX: &'static str = "generic vhost-user parameters \ + \"virtio_id=,\ + socket=,\ + queue_sizes=,\ + id=,pci_segment=\""; + + pub fn parse(vhost_user: &str) -> Result { + let mut parser = OptionParser::new(); + parser + .add("virtio_id") + .add("queue_sizes") + .add("socket") + .add("id") + .add("pci_segment"); + parser + .parse(vhost_user) + .map_err(Error::ParseGenericVhostUser)?; + + let socket = parser + .get("socket") + .ok_or(Error::ParseGenericVhostUserSockMissing)?; + + let IntegerList(queue_sizes) = parser + .convert("queue_sizes") + .map_err(Error::ParseGenericVhostUser)? + .ok_or(Error::ParseGenericVhostUserQueueSizeMissing)?; + let device_type = parser + .convert("virtio_id") + .map_err(Error::ParseGenericVhostUser)? + .ok_or(Error::ParseGenericVhostUserVirtioIdMissing)?; + let id = parser.get("id"); + let pci_segment = parser + .convert("pci_segment") + .map_err(Error::ParseGenericVhostUser)? + .unwrap_or_default(); + let mut converted_queue_sizes: Vec = Vec::new(); + for (offset, &queue_size) in queue_sizes.iter().enumerate() { + match queue_size.try_into() { + Err(_) => { + return Err(Error::ParseGenericVhostUserQueueSizeTooLarge( + offset, queue_size, + )); + } + Ok(queue_size) => converted_queue_sizes.push(queue_size), + } + } + + Ok(GenericVhostUserConfig { + socket: socket.into(), + device_type, + id, + pci_segment, + queue_sizes: converted_queue_sizes, + }) + } + + pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { + if let Some(platform_config) = vm_config.platform.as_ref() { + if self.pci_segment >= platform_config.num_pci_segments { + return Err(ValidationError::InvalidPciSegment(self.pci_segment)); + } + + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + { + return Err(ValidationError::IommuNotSupportedOnSegment( + self.pci_segment, + )); + } + } + + Ok(()) + } +} + impl FsConfig { pub const SYNTAX: &'static str = "virtio-fs parameters \ \"tag=,socket=,num_queues=,\ @@ -2738,6 +2840,17 @@ impl VmConfig { } } + if let Some(generic_vhost_user_devices) = &self.generic_vhost_user { + if !generic_vhost_user_devices.is_empty() && !self.backed_by_shared_memory() { + return Err(ValidationError::VhostUserRequiresSharedMemory); + } + for generic_vhost_user_device in generic_vhost_user_devices { + generic_vhost_user_device.validate(self)?; + + Self::validate_identifier(&mut id_list, &generic_vhost_user_device.id)?; + } + } + if let Some(pmems) = &self.pmem { for pmem in pmems { pmem.validate(self)?; @@ -2991,6 +3104,15 @@ impl VmConfig { fs = Some(fs_config_list); } + let mut generic_vhost_user: Option> = None; + if let Some(generic_vhost_user_list) = &vm_params.generic_vhost_user { + let mut generic_vhost_user_config_list = Vec::new(); + for item in generic_vhost_user_list.iter() { + generic_vhost_user_config_list.push(GenericVhostUserConfig::parse(item)?); + } + generic_vhost_user = Some(generic_vhost_user_config_list); + } + let mut pmem: Option> = None; if let Some(pmem_list) = &vm_params.pmem { let mut pmem_config_list = Vec::new(); @@ -3126,6 +3248,7 @@ impl VmConfig { net, rng, balloon, + generic_vhost_user, fs, pmem, serial, @@ -3188,6 +3311,13 @@ impl VmConfig { removed |= fs.len() != len; } + // Remove if generic vhost-user device + if let Some(generic_vhost_user) = self.generic_vhost_user.as_mut() { + let len = generic_vhost_user.len(); + generic_vhost_user.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + removed |= generic_vhost_user.len() != len; + } + // Remove if net device if let Some(net) = self.net.as_mut() { let len = net.len(); @@ -3260,6 +3390,7 @@ impl Clone for VmConfig { #[cfg(feature = "pvmemcontrol")] pvmemcontrol: self.pvmemcontrol.clone(), fs: self.fs.clone(), + generic_vhost_user: self.generic_vhost_user.clone(), pmem: self.pmem.clone(), serial: self.serial.clone(), console: self.console.clone(), @@ -3784,6 +3915,90 @@ mod unit_tests { Ok(()) } + #[track_caller] + #[allow(clippy::too_many_arguments)] + fn make_vhost_user_config( + socket: &str, + virtio_id: u64, + id: &str, + pci_segment: u64, + queue_sizes: &IntegerList, + ) { + assert!(!socket.contains(",[]\n\r\0\"")); + assert!(!id.contains(",[]\n\r\0\"")); + let config = GenericVhostUserConfig::parse(&format!( + "virtio_id={virtio_id},socket=\"{socket}\",\ +id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" + )); + if pci_segment <= u16::MAX.into() + && virtio_id <= u32::MAX.into() + && queue_sizes.0.iter().all(|&f| f <= u16::MAX.into()) + { + assert_eq!( + config.unwrap(), + GenericVhostUserConfig { + socket: socket.into(), + id: Some(id.to_owned()), + device_type: u32::try_from(virtio_id).unwrap(), + pci_segment: u16::try_from(pci_segment).unwrap(), + queue_sizes: queue_sizes + .0 + .iter() + .map(|&f| u16::try_from(f).unwrap()) + .collect(), + } + ); + } else { + config.unwrap_err(); + } + } + + #[test] + fn test_parse_vhost_user() -> Result<()> { + // all parameters must be supplied, except pci_segment + GenericVhostUserConfig::parse("").unwrap_err(); + GenericVhostUserConfig::parse("virtio_id=1").unwrap_err(); + GenericVhostUserConfig::parse("queue_size=1").unwrap_err(); + GenericVhostUserConfig::parse("socket=/tmp/sock").unwrap_err(); + GenericVhostUserConfig::parse("id=1").unwrap_err(); + make_vhost_user_config( + "/dev/null/doesnotexist", + 100, + "Something", + 10, + &IntegerList(vec![u16::MAX.into(), 20u16.into()]), + ); + make_vhost_user_config( + "/dev/null/doesnotexist", + 100, + "Something", + 10, + &IntegerList(vec![u16::MAX.into()]), + ); + make_vhost_user_config( + "/dev/null/doesnotexist", + u64::from(u32::MAX) + 1, + "Something", + 10, + &IntegerList(vec![20u64]), + ); + make_vhost_user_config( + "/dev/null/doesnotexist", + u64::from(u32::MAX) + 1, + "Something", + 10, + &IntegerList(vec![20u64]), + ); + make_vhost_user_config( + "/dev/null/doesnotexist", + u64::from(u32::MAX) + 1, + "Something", + 10, + &IntegerList(vec![20u64]), + ); + Ok(()) + } + fn pmem_fixture() -> PmemConfig { PmemConfig { file: PathBuf::from("/tmp/pmem"), @@ -4168,6 +4383,7 @@ mod unit_tests { rate_limit_groups: None, disks: None, rng: RngConfig::default(), + generic_vhost_user: None, balloon: None, fs: None, pmem: None, @@ -4373,6 +4589,7 @@ mod unit_tests { }, balloon: None, fs: None, + generic_vhost_user: None, pmem: None, serial: ConsoleConfig { file: None, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index a7d3254c3f..f22696c7fe 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -127,8 +127,8 @@ use crate::serial_manager::{Error as SerialManagerError, SerialManager}; use crate::vm_config::IvshmemConfig; use crate::vm_config::{ ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT, - DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, - VhostMode, VmConfig, VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig, + UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig, }; use crate::{DEVICE_MANAGER_SNAPSHOT_ID, GuestRegionMmap, PciDeviceInfo, device_node}; @@ -158,6 +158,7 @@ const IVSHMEM_DEVICE_NAME: &str = "__ivshmem"; const DISK_DEVICE_NAME_PREFIX: &str = "_disk"; const FS_DEVICE_NAME_PREFIX: &str = "_fs"; const NET_DEVICE_NAME_PREFIX: &str = "_net"; +const GENERIC_VHOST_USER_DEVICE_NAME_PREFIX: &str = "_generic_vhost_user"; const PMEM_DEVICE_NAME_PREFIX: &str = "_pmem"; const VDPA_DEVICE_NAME_PREFIX: &str = "_vdpa"; const VSOCK_DEVICE_NAME_PREFIX: &str = "_vsock"; @@ -197,6 +198,10 @@ pub enum DeviceManagerError { #[error("Cannot create virtio-rng device")] CreateVirtioRng(#[source] io::Error), + /// Cannot create generic vhost-user device + #[error("Cannot create generic vhost-user device")] + CreateGenericVhostUser(#[source] virtio_devices::vhost_user::Error), + /// Cannot create virtio-fs device #[error("Cannot create virtio-fs device")] CreateVirtioFs(#[source] virtio_devices::vhost_user::Error), @@ -205,6 +210,10 @@ pub enum DeviceManagerError { #[error("Virtio-fs device was created without a socket")] NoVirtioFsSock, + /// Generic vhost-user device was created without a socket. + #[error("Generic vhost-user device was created without a socket")] + NoGenericVhostUserSock, + /// Cannot create vhost-user-blk device #[error("Cannot create vhost-user-blk device")] CreateVhostUserBlk(#[source] virtio_devices::vhost_user::Error), @@ -2554,6 +2563,9 @@ impl DeviceManager { self.make_virtio_net_devices()?; self.make_virtio_rng_devices()?; + // Add generic vhost-user if required + self.make_generic_vhost_user_devices()?; + // Add virtio-fs if required self.make_virtio_fs_devices()?; @@ -3122,6 +3134,72 @@ impl DeviceManager { Ok(()) } + fn make_generic_vhost_user_device( + &mut self, + generic_vhost_user_cfg: &mut GenericVhostUserConfig, + ) -> DeviceManagerResult { + let id = if let Some(id) = &generic_vhost_user_cfg.id { + id.clone() + } else { + let id = self.next_device_name(GENERIC_VHOST_USER_DEVICE_NAME_PREFIX)?; + generic_vhost_user_cfg.id = Some(id.clone()); + id + }; + + info!("Creating generic vhost-user device: {generic_vhost_user_cfg:?}"); + + let mut node = device_node!(id); + + if let Some(generic_vhost_user_socket) = generic_vhost_user_cfg.socket.to_str() { + let generic_vhost_user_device = Arc::new(Mutex::new( + virtio_devices::vhost_user::GenericVhostUser::new( + id.clone(), + generic_vhost_user_socket, + generic_vhost_user_cfg.queue_sizes.clone(), + generic_vhost_user_cfg.device_type, + None, + self.seccomp_action.clone(), + self.exit_evt + .try_clone() + .map_err(DeviceManagerError::EventFd)?, + self.force_iommu, + state_from_id(self.snapshot.as_ref(), id.as_str()) + .map_err(DeviceManagerError::RestoreGetState)?, + ) + .map_err(DeviceManagerError::CreateGenericVhostUser)?, + )); + + // Update the device tree with the migratable device. + node.migratable = + Some(Arc::clone(&generic_vhost_user_device) as Arc>); + self.device_tree.lock().unwrap().insert(id.clone(), node); + + Ok(MetaVirtioDevice { + virtio_device: Arc::clone(&generic_vhost_user_device) + as Arc>, + iommu: false, + id, + pci_segment: generic_vhost_user_cfg.pci_segment, + dma_handler: None, + }) + } else { + Err(DeviceManagerError::NoGenericVhostUserSock) + } + } + + fn make_generic_vhost_user_devices(&mut self) -> DeviceManagerResult<()> { + let mut generic_vhost_user_devices = self.config.lock().unwrap().generic_vhost_user.clone(); + if let Some(generic_vhost_user_list_cfg) = &mut generic_vhost_user_devices { + for generic_vhost_user_cfg in generic_vhost_user_list_cfg.iter_mut() { + let device = self.make_generic_vhost_user_device(generic_vhost_user_cfg)?; + self.virtio_devices.push(device); + } + } + self.config.lock().unwrap().generic_vhost_user = generic_vhost_user_devices; + + Ok(()) + } + fn make_virtio_fs_device( &mut self, fs_cfg: &mut FsConfig, @@ -4918,6 +4996,16 @@ impl DeviceManager { self.hotplug_virtio_pci_device(device) } + pub fn add_generic_vhost_user( + &mut self, + generic_vhost_user_cfg: &mut GenericVhostUserConfig, + ) -> DeviceManagerResult { + self.validate_identifier(&generic_vhost_user_cfg.id)?; + + let device = self.make_generic_vhost_user_device(generic_vhost_user_cfg)?; + self.hotplug_virtio_pci_device(device) + } + pub fn add_pmem(&mut self, pmem_cfg: &mut PmemConfig) -> DeviceManagerResult { self.validate_identifier(&pmem_cfg.id)?; diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index dcf6614b24..627b13d5d7 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -59,8 +59,8 @@ use crate::migration::{recv_vm_config, recv_vm_state}; use crate::seccomp_filters::{Thread, get_seccomp_filter}; use crate::vm::{Error as VmError, Vm, VmState}; use crate::vm_config::{ - DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, - VmConfig, VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig, + UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; mod acpi; @@ -2125,6 +2125,39 @@ impl RequestHandler for Vmm { } } + fn vm_add_generic_vhost_user( + &mut self, + generic_vhost_user_cfg: GenericVhostUserConfig, + ) -> result::Result>, VmError> { + self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; + + { + // Validate the configuration change in a cloned configuration + let mut config = self.vm_config.as_ref().unwrap().lock().unwrap().clone(); + add_to_config( + &mut config.generic_vhost_user, + generic_vhost_user_cfg.clone(), + ); + config.validate().map_err(VmError::ConfigValidation)?; + } + + if let Some(ref mut vm) = self.vm { + let info = vm + .add_generic_vhost_user(generic_vhost_user_cfg) + .inspect_err(|e| { + error!("Error when adding new generic vhost-user device to the VM: {e:?}"); + })?; + serde_json::to_vec(&info) + .map(Some) + .map_err(VmError::SerializeJson) + } else { + // Update VmConfig by adding the new device. + let mut config = self.vm_config.as_ref().unwrap().lock().unwrap(); + add_to_config(&mut config.generic_vhost_user, generic_vhost_user_cfg); + Ok(None) + } + } + fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> result::Result>, VmError> { self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; @@ -2443,6 +2476,7 @@ mod unit_tests { }, balloon: None, fs: None, + generic_vhost_user: None, pmem: None, serial: ConsoleConfig { file: None, @@ -2679,6 +2713,59 @@ mod unit_tests { ); } + #[test] + fn test_vmm_vm_cold_add_generic_vhost_user() { + let mut vmm = create_dummy_vmm(); + let generic_vhost_user_config = + GenericVhostUserConfig::parse("virtio_id=26,socket=/tmp/sock,queue_sizes=[1024]") + .unwrap(); + + assert!(matches!( + vmm.vm_add_generic_vhost_user(generic_vhost_user_config.clone()), + Err(VmError::VmNotCreated) + )); + + let _ = vmm.vm_create(create_dummy_vm_config()); + assert!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .generic_vhost_user + .is_none() + ); + + assert!( + vmm.vm_add_generic_vhost_user(generic_vhost_user_config.clone()) + .unwrap() + .is_none() + ); + assert_eq!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .generic_vhost_user + .clone() + .unwrap() + .len(), + 1 + ); + assert_eq!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .generic_vhost_user + .clone() + .unwrap()[0], + generic_vhost_user_config + ); + } + #[test] fn test_vmm_vm_cold_add_pmem() { let mut vmm = create_dummy_vmm(); diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index db29072bed..9c37457c19 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -100,8 +100,8 @@ use crate::migration::{SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE, url_to_path}; #[cfg(feature = "fw_cfg")] use crate::vm_config::FwCfgConfig; use crate::vm_config::{ - DeviceConfig, DiskConfig, FsConfig, HotplugMethod, NetConfig, NumaConfig, PayloadConfig, - PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, HotplugMethod, NetConfig, + NumaConfig, PayloadConfig, PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; use crate::{ CPU_MANAGER_SNAPSHOT_ID, DEVICE_MANAGER_SNAPSHOT_ID, GuestMemoryMmap, @@ -2136,6 +2136,33 @@ impl Vm { Ok(pci_device_info) } + pub fn add_generic_vhost_user( + &mut self, + mut generic_vhost_user_cfg: GenericVhostUserConfig, + ) -> Result { + let pci_device_info = self + .device_manager + .lock() + .unwrap() + .add_generic_vhost_user(&mut generic_vhost_user_cfg) + .map_err(Error::DeviceManager)?; + + // Update VmConfig by adding the new device. This is important to + // ensure the device would be created in case of a reboot. + { + let mut config = self.config.lock().unwrap(); + add_to_config(&mut config.generic_vhost_user, generic_vhost_user_cfg); + } + + self.device_manager + .lock() + .unwrap() + .notify_hotplug(AcpiNotificationFlags::PCI_DEVICES_CHANGED) + .map_err(Error::DeviceManager)?; + + Ok(pci_device_info) + } + pub fn add_pmem(&mut self, mut pmem_cfg: PmemConfig) -> Result { let pci_device_info = self .device_manager diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 5de8c31452..b9e67f7bba 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -472,6 +472,24 @@ impl ApplyLandlock for FsConfig { } } +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct GenericVhostUserConfig { + pub socket: PathBuf, + pub queue_sizes: Vec, + #[serde(default)] + pub id: Option, + #[serde(default)] + pub pci_segment: u16, + pub device_type: u32, +} + +impl ApplyLandlock for GenericVhostUserConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + landlock.add_rule_with_access(&self.socket, "rw")?; + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct PmemConfig { pub file: PathBuf, @@ -924,6 +942,7 @@ pub struct VmConfig { #[serde(default)] pub rng: RngConfig, pub balloon: Option, + pub generic_vhost_user: Option>, pub fs: Option>, pub pmem: Option>, #[serde(default = "default_serial")] @@ -1000,6 +1019,12 @@ impl VmConfig { } } + if let Some(generic_vhost_user_configs) = &self.generic_vhost_user { + for generic_vhost_user_config in generic_vhost_user_configs.iter() { + generic_vhost_user_config.apply_landlock(&mut landlock)?; + } + } + if let Some(pmem_configs) = &self.pmem { for pmem_config in pmem_configs.iter() { pmem_config.apply_landlock(&mut landlock)?; From d510f11a5000d86a956b457381c50ece9e4b32dd Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 4 Feb 2026 09:29:34 -0500 Subject: [PATCH 0594/1893] 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 --- cloud-hypervisor/src/main.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 351dd73b78..b2b184248b 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -32,9 +32,9 @@ use vmm::vm_config::FwCfgConfig; #[cfg(feature = "ivshmem")] use vmm::vm_config::IvshmemConfig; use vmm::vm_config::{ - BalloonConfig, DeviceConfig, DiskConfig, FsConfig, LandlockConfig, NetConfig, NumaConfig, - PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, TpmConfig, UserDeviceConfig, VdpaConfig, - VmConfig, VsockConfig, + BalloonConfig, DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, LandlockConfig, + NetConfig, NumaConfig, PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, TpmConfig, + UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::signal::block_signal; @@ -280,6 +280,11 @@ fn get_cli_options_sorted( .help("GDB socket (UNIX domain socket): path=") .num_args(1) .group("vmm-config"), + Arg::new("generic-vhost-user") + .long("generic-vhost-user") + .help(GenericVhostUserConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), #[cfg(feature = "igvm")] Arg::new("igvm") .long("igvm") From df86b2864bac621575f97ed547fbcda2f2761833 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 4 Feb 2026 09:34:50 -0500 Subject: [PATCH 0595/1893] vmm: add HTTP API endpoints for generic vhost-user This includes OpenAPI schemas. Signed-off-by: Demi Marie Obenour --- fuzz/fuzz_targets/http_api.rs | 8 ++++ vmm/src/api/http/http_endpoint.rs | 10 +++-- vmm/src/api/http/mod.rs | 12 ++++-- vmm/src/api/openapi/cloud-hypervisor.yaml | 46 +++++++++++++++++++++++ 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index ee9dd62f18..b7f38994fb 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -168,6 +168,7 @@ impl RequestHandler for StubApiRequestHandler { }, balloon: None, fs: None, + generic_vhost_user: None, pmem: None, serial: ConsoleConfig { file: None, @@ -254,6 +255,13 @@ impl RequestHandler for StubApiRequestHandler { Ok(None) } + fn vm_add_generic_vhost_user( + &mut self, + _: GenericVhostUserConfig, + ) -> Result>, VmError> { + Ok(None) + } + fn vm_add_pmem(&mut self, _: PmemConfig) -> Result>, VmError> { Ok(None) } diff --git a/vmm/src/api/http/http_endpoint.rs b/vmm/src/api/http/http_endpoint.rs index e463a20819..15ebfd2f9e 100644 --- a/vmm/src/api/http/http_endpoint.rs +++ b/vmm/src/api/http/http_endpoint.rs @@ -45,10 +45,11 @@ use crate::api::VmCoredump; use crate::api::http::http_endpoint::fds_helper::{attach_fds_to_cfg, attach_fds_to_cfgs}; use crate::api::http::{EndpointHandler, HttpError, error_response}; use crate::api::{ - AddDisk, ApiAction, ApiError, ApiRequest, NetConfig, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, - VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmConfig, VmCounters, VmDelete, VmNmi, VmPause, - VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk, - VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot, + AddDisk, ApiAction, ApiError, ApiRequest, NetConfig, VmAddDevice, VmAddFs, + VmAddGenericVhostUser, VmAddNet, VmAddPmem, VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, + VmConfig, VmCounters, VmDelete, VmNmi, VmPause, VmPowerButton, VmReboot, VmReceiveMigration, + VmRemoveDevice, VmResize, VmResizeDisk, VmResizeZone, VmRestore, VmResume, VmSendMigration, + VmShutdown, VmSnapshot, }; use crate::config::RestoreConfig; use crate::cpu::Error as CpuError; @@ -419,6 +420,7 @@ vm_action_put_handler!(VmNmi); vm_action_put_handler_body!(VmAddDevice); vm_action_put_handler_body!(AddDisk); vm_action_put_handler_body!(VmAddFs); +vm_action_put_handler_body!(VmAddGenericVhostUser); vm_action_put_handler_body!(VmAddPmem); vm_action_put_handler_body!(VmAddVdpa); vm_action_put_handler_body!(VmAddVsock); diff --git a/vmm/src/api/http/mod.rs b/vmm/src/api/http/mod.rs index 2aa52e8e37..3461c08af9 100644 --- a/vmm/src/api/http/mod.rs +++ b/vmm/src/api/http/mod.rs @@ -28,10 +28,10 @@ use self::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmPing, VmmShutdow #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::api::VmCoredump; use crate::api::{ - AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice, - VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmNmi, VmPause, VmPowerButton, VmReboot, - VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk, VmResizeZone, VmRestore, VmResume, - VmSendMigration, VmShutdown, VmSnapshot, + AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddGenericVhostUser, VmAddNet, + VmAddPmem, VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmNmi, + VmPause, VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk, + VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot, }; use crate::landlock::Landlock; use crate::seccomp_filters::{Thread, get_seccomp_filter}; @@ -196,6 +196,10 @@ pub static HTTP_ROUTES: LazyLock = LazyLock::new(|| { endpoint!("/vm.add-fs"), Box::new(VmActionHandler::new(&VmAddFs)), ); + r.routes.insert( + endpoint!("/vm.add-generic-vhost-user"), + Box::new(VmActionHandler::new(&VmAddGenericVhostUser)), + ); r.routes.insert( endpoint!("/vm.add-net"), Box::new(VmActionHandler::new(&VmAddNet)), diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 629a6800d1..499218c7a5 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -277,6 +277,28 @@ paths: 500: description: The new device could not be added to the VM instance. + /vm.add-generic-vhost-user: + put: + summary: Add a new generic vhost-user device to the VM + requestBody: + description: The details of the new generic vhost-user device + content: + application/json: + schema: + $ref: "#/components/schemas/GenericVhostUserConfig" + required: true + responses: + 200: + description: The new device was successfully added to the VM instance. + content: + application/json: + schema: + $ref: "#/components/schemas/PciDeviceInfo" + 204: + description: The new device was successfully (cold) added to the VM instance. + 500: + description: The new device could not be added to the VM instance. + /vm.add-pmem: put: summary: Add a new pmem device to the VM @@ -603,6 +625,10 @@ components: type: array items: $ref: "#/components/schemas/FsConfig" + generic-vhost-user: + type: array + items: + $ref: "#/components/schemas/GenericVhostUserConfig" pmem: type: array items: @@ -1057,6 +1083,26 @@ components: id: type: string + GenericVhostUserConfig: + required: + - queue_sizes + - socket + - tag + - virtio_id + type: object + properties: + socket: + type: string + queue_size: + type: array + items: + type: uint16 + pci_segment: + type: integer + format: int16 + virtio_id: + type: uint32 + PmemConfig: required: - file From cfb69c68d2b00ab781fffb1002c3198b28c49546 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 4 Feb 2026 09:45:36 -0500 Subject: [PATCH 0596/1893] 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 --- vmm/src/api/dbus/mod.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/vmm/src/api/dbus/mod.rs b/vmm/src/api/dbus/mod.rs index 6f75fb5cda..ae39feb7d7 100644 --- a/vmm/src/api/dbus/mod.rs +++ b/vmm/src/api/dbus/mod.rs @@ -22,10 +22,10 @@ use super::{ApiAction, ApiRequest}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::api::VmCoredump; use crate::api::{ - AddDisk, Body, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice, VmAddVdpa, - VmAddVsock, VmBoot, VmCounters, VmCreate, VmDelete, VmInfo, VmPause, VmPowerButton, VmReboot, - VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume, - VmSendMigration, VmShutdown, VmSnapshot, VmmPing, VmmShutdown, + AddDisk, Body, VmAddDevice, VmAddFs, VmAddGenericVhostUser, VmAddNet, VmAddPmem, + VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmCreate, VmDelete, VmInfo, + VmPause, VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, + VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot, VmmPing, VmmShutdown, }; use crate::seccomp_filters::{Thread, get_seccomp_filter}; use crate::{Error as VmmError, NetConfig, Result as VmmResult, VmConfig}; @@ -144,6 +144,16 @@ impl DBusApi { self.vm_action(&VmAddFs, fs_config).await } + async fn vm_add_generic_vhost_user( + &self, + generic_vhost_user_config: String, + ) -> Result> { + let generic_vhost_user_config = + serde_json::from_str(&generic_vhost_user_config).map_err(api_error)?; + self.vm_action(&VmAddGenericVhostUser, generic_vhost_user_config) + .await + } + async fn vm_add_net(&self, net_config: String) -> Result> { let mut net_config: NetConfig = serde_json::from_str(&net_config).map_err(api_error)?; if net_config.fds.is_some() { From 36371283c672a22240a623cef78541d94ee81b51 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 4 Feb 2026 09:43:23 -0500 Subject: [PATCH 0597/1893] ch-remote: add generic vhost-user support Support adding generic vhost-user devices via the ch-remote CLI. Signed-off-by: Demi Marie Obenour --- cloud-hypervisor/src/bin/ch-remote.rs | 55 ++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/src/bin/ch-remote.rs b/cloud-hypervisor/src/bin/ch-remote.rs index fd48ffab1f..6939eaa385 100644 --- a/cloud-hypervisor/src/bin/ch-remote.rs +++ b/cloud-hypervisor/src/bin/ch-remote.rs @@ -22,8 +22,8 @@ use option_parser::{ByteSized, ByteSizedParseError}; use thiserror::Error; use vmm::config::RestoreConfig; use vmm::vm_config::{ - DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, - VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig, + UserDeviceConfig, VdpaConfig, VsockConfig, }; #[cfg(feature = "dbus_api")] use zbus::{proxy, zvariant::Optional}; @@ -49,6 +49,8 @@ enum Error { AddDiskConfig(#[source] vmm::config::Error), #[error("Error parsing filesystem syntax")] AddFsConfig(#[source] vmm::config::Error), + #[error("Error parsing generic vhost-user syntax")] + AddGenericVhostUserConfig(#[source] vmm::config::Error), #[error("Error parsing persistent memory syntax")] AddPmemConfig(#[source] vmm::config::Error), #[error("Error parsing network syntax")] @@ -83,6 +85,10 @@ trait DBusApi1 { fn vm_add_device(&self, device_config: &str) -> zbus::Result>; fn vm_add_disk(&self, disk_config: &str) -> zbus::Result>; fn vm_add_fs(&self, fs_config: &str) -> zbus::Result>; + fn vm_add_generic_vhost_user( + &self, + generic_vhost_user_config: &str, + ) -> zbus::Result>; fn vm_add_net(&self, net_config: &str) -> zbus::Result>; fn vm_add_pmem(&self, pmem_config: &str) -> zbus::Result>; fn vm_add_user_device(&self, vm_add_user_device: &str) -> zbus::Result>; @@ -155,6 +161,10 @@ impl<'a> DBusApi1ProxyBlocking<'a> { self.print_response(self.vm_add_fs(fs_config)) } + fn api_vm_add_generic_vhost_user(&self, generic_vhost_user_config: &str) -> ApiResult { + self.print_response(self.vm_add_generic_vhost_user(generic_vhost_user_config)) + } + fn api_vm_add_net(&self, net_config: &str) -> ApiResult { self.print_response(self.vm_add_net(net_config)) } @@ -398,6 +408,22 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu simple_api_command(socket, "PUT", "add-fs", Some(&fs_config)) .map_err(Error::HttpApiClient) } + Some("add-generic-vhost-user") => { + let device_config = add_generic_vhost_user_config( + matches + .subcommand_matches("add-generic-vhost-user") + .unwrap() + .get_one::("generic_vhost_user_config") + .unwrap(), + )?; + simple_api_command( + socket, + "PUT", + "add-generic-vhost-user", + Some(&device_config), + ) + .map_err(Error::HttpApiClient) + } Some("add-pmem") => { let pmem_config = add_pmem_config( matches @@ -620,6 +646,16 @@ fn dbus_api_do_command(matches: &ArgMatches, proxy: &DBusApi1ProxyBlocking<'_>) )?; proxy.api_vm_add_fs(&fs_config) } + Some("add-generic-vhost-user") => { + let generic_vhost_user_config = add_generic_vhost_user_config( + matches + .subcommand_matches("add-generic-vhost-user") + .unwrap() + .get_one::("generic_vhost_user_config") + .unwrap(), + )?; + proxy.api_vm_add_generic_vhost_user(&generic_vhost_user_config) + } Some("add-pmem") => { let pmem_config = add_pmem_config( matches @@ -835,6 +871,14 @@ fn add_fs_config(config: &str) -> Result { Ok(fs_config) } +fn add_generic_vhost_user_config(config: &str) -> Result { + let generic_vhost_user_config = + GenericVhostUserConfig::parse(config).map_err(Error::AddGenericVhostUserConfig)?; + let generic_vhost_user_config = serde_json::to_string(&generic_vhost_user_config).unwrap(); + + Ok(generic_vhost_user_config) +} + fn add_pmem_config(config: &str) -> Result { let pmem_config = PmemConfig::parse(config).map_err(Error::AddPmemConfig)?; let pmem_config = serde_json::to_string(&pmem_config).unwrap(); @@ -981,6 +1025,13 @@ fn get_cli_commands_sorted() -> Box<[Command]> { .index(1) .help(vmm::vm_config::FsConfig::SYNTAX), ), + Command::new("add-generic-vhost-user") + .about("Add generic vhost-user device") + .arg( + Arg::new("generic_vhost_user_config") + .index(1) + .help(vmm::vm_config::GenericVhostUserConfig::SYNTAX), + ), Command::new("add-net") .about("Add network device") .arg(Arg::new("net_config").index(1).help(NetConfig::SYNTAX)), From d6b80d9845a9e94087d0a4d3283fa1284511fc88 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 4 Feb 2026 09:44:04 -0500 Subject: [PATCH 0598/1893] tests: generic vhost-user: add support Include integeration tests for generic vhost-user devices. Signed-off-by: Demi Marie Obenour --- cloud-hypervisor/tests/integration.rs | 62 +++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index e9d2e66c1b..221aadfbaf 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -470,6 +470,7 @@ fn prepare_virtiofsd(tmp_dir: &TempDir, shared_dir: &str) -> (std::process::Chil .args(["--shared-dir", shared_dir]) .args(["--socket-path", virtiofsd_socket_path.as_str()]) .args(["--cache", "never"]) + .args(["--tag", "myfs"]) .spawn() .unwrap(); @@ -1621,6 +1622,7 @@ fn test_boot_from_vhost_user_blk( fn _test_virtio_fs( prepare_daemon: &dyn Fn(&TempDir, &str) -> (std::process::Child, String), hotplug: bool, + use_generic_vhost_user: bool, pci_segment: Option, ) { #[cfg(target_arch = "aarch64")] @@ -1670,8 +1672,13 @@ fn _test_virtio_fs( } let fs_params = format!( - "id=myfs0,tag=myfs,socket={},num_queues=1,queue_size=1024{}", + "socket={},id=myfs0,{}{}", virtiofsd_socket_path, + if use_generic_vhost_user { + "queue_sizes=[1024,1024],virtio_id=26" + } else { + "tag=myfs,num_queues=1,queue_size=1024" + }, if let Some(pci_segment) = pci_segment { format!(",pci_segment={pci_segment}") } else { @@ -1680,10 +1687,22 @@ fn _test_virtio_fs( ); if !hotplug { - guest_command.args(["--fs", fs_params.as_str()]); + guest_command.args([ + if use_generic_vhost_user { + "--generic-vhost-user" + } else { + "--fs" + }, + fs_params.as_str(), + ]); } let mut child = guest_command.capture_output().spawn().unwrap(); + let add_arg = if use_generic_vhost_user { + "add-generic-vhost-user" + } else { + "add-fs" + }; let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); @@ -1691,7 +1710,7 @@ fn _test_virtio_fs( if hotplug { // Add fs to the VM let (cmd_success, cmd_output) = - remote_command_w_output(&api_socket, "add-fs", Some(&fs_params)); + remote_command_w_output(&api_socket, add_arg, Some(&fs_params)); assert!(cmd_success); if let Some(pci_segment) = pci_segment { @@ -1764,8 +1783,13 @@ fn _test_virtio_fs( let r = std::panic::catch_unwind(|| { thread::sleep(std::time::Duration::new(10, 0)); let fs_params = format!( - "id=myfs0,tag=myfs,socket={},num_queues=1,queue_size=1024{}", + "id=myfs0,socket={},{}{}", virtiofsd_socket_path, + if use_generic_vhost_user { + "queue_sizes=[1024,1024],virtio_id=26" + } else { + "tag=myfs,num_queues=1,queue_size=1024" + }, if let Some(pci_segment) = pci_segment { format!(",pci_segment={pci_segment}") } else { @@ -1775,7 +1799,7 @@ fn _test_virtio_fs( // Add back and check it works let (cmd_success, cmd_output) = - remote_command_w_output(&api_socket, "add-fs", Some(&fs_params)); + remote_command_w_output(&api_socket, add_arg, Some(&fs_params)); assert!(cmd_success); if let Some(pci_segment) = pci_segment { assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( @@ -5152,22 +5176,42 @@ mod common_parallel { #[test] fn test_virtio_fs() { - _test_virtio_fs(&prepare_virtiofsd, false, None); + _test_virtio_fs(&prepare_virtiofsd, false, false, None); } #[test] fn test_virtio_fs_hotplug() { - _test_virtio_fs(&prepare_virtiofsd, true, None); + _test_virtio_fs(&prepare_virtiofsd, true, false, None); } #[test] fn test_virtio_fs_multi_segment_hotplug() { - _test_virtio_fs(&prepare_virtiofsd, true, Some(15)); + _test_virtio_fs(&prepare_virtiofsd, true, false, Some(15)); } #[test] fn test_virtio_fs_multi_segment() { - _test_virtio_fs(&prepare_virtiofsd, false, Some(15)); + _test_virtio_fs(&prepare_virtiofsd, false, false, Some(15)); + } + + #[test] + fn test_generic_vhost_user() { + _test_virtio_fs(&prepare_virtiofsd, false, true, None); + } + + #[test] + fn test_generic_vhost_user_hotplug() { + _test_virtio_fs(&prepare_virtiofsd, true, true, None); + } + + #[test] + fn test_generic_vhost_user_multi_segment_hotplug() { + _test_virtio_fs(&prepare_virtiofsd, true, true, Some(15)); + } + + #[test] + fn test_generic_vhost_user_multi_segment() { + _test_virtio_fs(&prepare_virtiofsd, false, true, Some(15)); } #[test] From 042d1abd6734652cf5ab3591d8c1a9b400e713bb Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 4 Feb 2026 09:47:38 -0500 Subject: [PATCH 0599/1893] docs: generic vhost-user: document Include documentation for the generic vhost-user device. Signed-off-by: Demi Marie Obenour --- docs/api.md | 61 +++++++++++++++--------------- docs/device_model.md | 18 +++++++++ docs/generic-vhost-user.md | 76 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 docs/generic-vhost-user.md diff --git a/docs/api.md b/docs/api.md index 8f7a10642d..cea3f31812 100644 --- a/docs/api.md +++ b/docs/api.md @@ -72,36 +72,37 @@ The Cloud Hypervisor API exposes the following actions through its endpoints: ##### Virtual Machine (VM) Actions | Action | Endpoint | Request Body | Response Body | Prerequisites | -| ---------------------------------- | ----------------------- | ------------------------------- | ------------------------ | ------------------------------------------------------ | -| Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet | -| Delete the VM | `/vm.delete` | N/A | N/A | N/A | -| Boot the VM | `/vm.boot` | N/A | N/A | The VM is created but not booted | -| Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted | -| Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted | -| Trigger power button of the VM | `/vm.power-button` | N/A | N/A | The VM is booted | -| Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted | -| Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused | -| Take a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused | -| Perform a coredump of the VM* | `/vm.coredump` | `/schemas/VmCoredumpData` | N/A | The VM is paused | -| Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted | -| Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted | -| Add/remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted | -| Resize a disk attached to the VM | `/vm.resize-disk` | `/schemas/VmResizeDisk` | N/A | The VM is created | -| Add/remove memory from a zone | `/vm.resize-zone` | `/schemas/VmResizeZone` | N/A | The VM is booted | -| Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created | -| Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | `/schemas/PciDeviceInfo` | The VM is booted | -| Add disk device to the VM | `/vm.add-disk` | `/schemas/DiskConfig` | `/schemas/PciDeviceInfo` | The VM is booted | -| Add fs device to the VM | `/vm.add-fs` | `/schemas/FsConfig` | `/schemas/PciDeviceInfo` | The VM is booted | -| Add pmem device to the VM | `/vm.add-pmem` | `/schemas/PmemConfig` | `/schemas/PciDeviceInfo` | The VM is booted | -| Add network device to the VM | `/vm.add-net` | `/schemas/NetConfig` | `/schemas/PciDeviceInfo` | The VM is booted | -| Add userspace PCI device to the VM | `/vm.add-user-device` | `/schemas/VmAddUserDevice` | `/schemas/PciDeviceInfo` | The VM is booted | -| Add vdpa device to the VM | `/vm.add-vdpa` | `/schemas/VdpaConfig` | `/schemas/PciDeviceInfo` | The VM is booted | -| Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | `/schemas/PciDeviceInfo` | The VM is booted | -| Remove device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted | -| Dump the VM counters | `/vm.counters` | N/A | `/schemas/VmCounters` | The VM is booted | -| Inject an NMI | `/vm.nmi` | N/A | N/A | The VM is booted | -| Prepare to receive a migration | `/vm.receive-migration` | `/schemas/ReceiveMigrationData` | N/A | N/A | -| Start to send migration to target | `/vm.send-migration` | `/schemas/SendMigrationData` | N/A | The VM is booted and (shared mem or hugepages enabled) | +| --------------------------------------- | ---------------------------- | --------------------------------- | ------------------------ | ------------------------------------------------------ | +| Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet | +| Delete the VM | `/vm.delete` | N/A | N/A | N/A | +| Boot the VM | `/vm.boot` | N/A | N/A | The VM is created but not booted | +| Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted | +| Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted | +| Trigger power button of the VM | `/vm.power-button` | N/A | N/A | The VM is booted | +| Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted | +| Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused | +| Take a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused | +| Perform a coredump of the VM* | `/vm.coredump` | `/schemas/VmCoredumpData` | N/A | The VM is paused | +| Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted | +| Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted | +| Add/remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted | +| Resize a disk attached to the VM | `/vm.resize-disk` | `/schemas/VmResizeDisk` | N/A | The VM is created | +| Add/remove memory from a zone | `/vm.resize-zone` | `/schemas/VmResizeZone` | N/A | The VM is booted | +| Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created | +| Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | `/schemas/PciDeviceInfo` | The VM is booted | +| Add disk device to the VM | `/vm.add-disk` | `/schemas/DiskConfig` | `/schemas/PciDeviceInfo` | The VM is booted | +| Add fs device to the VM | `/vm.add-fs` | `/schemas/FsConfig` | `/schemas/PciDeviceInfo` | The VM is booted | +| Add generic vhost-user device to the VM | `/vm.add-generic-vhost-user` | `/schemas/GenericVhostUserConfig` | `/schemas/PciDeviceInfo` | The VM is booted | +| Add pmem device to the VM | `/vm.add-pmem` | `/schemas/PmemConfig` | `/schemas/PciDeviceInfo` | The VM is booted | +| Add network device to the VM | `/vm.add-net` | `/schemas/NetConfig` | `/schemas/PciDeviceInfo` | The VM is booted | +| Add userspace PCI device to the VM | `/vm.add-user-device` | `/schemas/VmAddUserDevice` | `/schemas/PciDeviceInfo` | The VM is booted | +| Add vdpa device to the VM | `/vm.add-vdpa` | `/schemas/VdpaConfig` | `/schemas/PciDeviceInfo` | The VM is booted | +| Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | `/schemas/PciDeviceInfo` | The VM is booted | +| Remove device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted | +| Dump the VM counters | `/vm.counters` | N/A | `/schemas/VmCounters` | The VM is booted | +| Inject an NMI | `/vm.nmi` | N/A | N/A | The VM is booted | +| Prepare to receive a migration | `/vm.receive-migration` | `/schemas/ReceiveMigrationData` | N/A | N/A | +| Start to send migration to target | `/vm.send-migration` | `/schemas/SendMigrationData` | N/A | The VM is booted and (shared mem or hugepages enabled) | * The `vmcoredump` action is available exclusively for the `x86_64` architecture and can be executed only when the `guest_debug` feature is diff --git a/docs/device_model.md b/docs/device_model.md index c072dc2eb6..ed4577a2cd 100644 --- a/docs/device_model.md +++ b/docs/device_model.md @@ -201,6 +201,24 @@ networking device (e.g. DPDK) into the VMM as their virtio network backend. This device is always built-in, and it is enabled when `vhost_user=true` and `socket` are provided to the `--net` parameter. +### vhost-user-generic + +This is a generic vhost-user device. The main use case is to provide a +vhost-user device that Cloud Hypervisor doesn't support natively. However, +there is nothing preventing its use for devices that Cloud Hypervisor does +support. For instance, the tag of a virtio-fs device can be set on the +virtiofsd command line, whereas the built-in virtio-fs support +requires the tag to be set in Cloud Hypervisor's command line. + +If the backend negotiates the `VHOST_USER_PROTOCOL_F_CONFIG` feature, +all configuration space access will be handled by it. Otherwise, +writes will be ignored and reads will return 0xFF. Cloud Hypervisor +warns if this happens. + +This device is always built-in, and it is enabled when the +`--generic-vhost-user` flag is passed. +See [the generic vhost-user documentation](generic-vhost-user.md) for more details. + ## VFIO VFIO (Virtual Function I/O) is a kernel framework that exposes direct device diff --git a/docs/generic-vhost-user.md b/docs/generic-vhost-user.md new file mode 100644 index 0000000000..6af813e28c --- /dev/null +++ b/docs/generic-vhost-user.md @@ -0,0 +1,76 @@ +# How to use generic vhost-user devices + +## What is a generic vhost-user device? + +Cloud Hypervisor deliberately does not have support for all types of virtio devices. +For instance, it does not natively support sound or media. + +However, the vhost-user protocol does not require the frontend to have separate +code for each type of vhost-user device. This allows writing a *generic* frontend +that supports almost all of them. + +Any vhost-user device that only uses supported protocol messages is +expected to work. It can (and often will) be of a type that Cloud +Hypervisor does not know about. It can even be of a type that is +not standardized. + +Virtio-GPU is known to *not* work. The version implemented in QEMU +requires `VHOST_USER_GPU_SET_SOCKET`, which is standard but will +never be implemented by Cloud Hypervisor. Other versions require +messages that have not been standardized. In the future, these +versions might be supported. + +## Examples + +virtiofsd meets these requirements if the `--tag` argument is passed. +Therefore, generic vhost-user can be used as an alternative to the built-in +virtio-fs support. See [fs.md](fs.md) for how to build the virtiofs daemon. + +To use generic vhost-user with virtiofsd, use a command line argument +similar to this: + +```bash +/path/to/virtiofsd \ + --tag=myfs \ + --log-level=debug \ + "--socket-path=$path_to_virtiofsd_socket" \ + "--shared-dir=$path_to_shared_directory" \ + "${other_virtiofsd_options[@]}" & + +/path/to/cloud-hypervisor \ + --cpus boot=1 \ + --memory size=1G,shared=on \ + --disk path=your-linux-image.iso \ + --kernel vmlinux \ + --cmdline "console=hvc0 root=/dev/vda1 rw" \ + --generic-vhost-user "socket=\"${path_to_virtiofsd_socket//\"/\"\"}\",virtio_id=26,queue_sizes=[512,512]" \ + "${other_cloud_hypervisor_options[@]}" +``` + +26 is the ID for a virtio-fs device. The IDs for other devices are defined +by the VIRTIO specification. The odd-looking variable expansion escapes +any double quotes in the socket path. It is also possible to provide +the name that is defined by the virtio specification, so `virtio_id=fs` +will also work. + +Inside the guest, you can mount the virtio-fs device with + +```bash +mkdir mount_dir +mount -t virtiofs -- myfs mount_dir/ +``` + +## Limitations + +Cloud Hypervisor does not save, restore, or migrate the PCI configuration +space of a generic vhost-user device. The backend can do it itself, but if +it does not these features will not work. + +Cloud Hypervisor cannot validate the number or size of the queues. Some +guest drivers do not validate these and will crash if they are wrong. +Notably, at least some versions of Linux will crash if one creates a +virtio-fs device (id 26) with only one queue. + +If any access to configuration space fails, Cloud Hypervisor will panic +instead of injecting an exception into the guest. It is unclear what +correct behavior is in this case. From 7ea1fa07a2b1aa9de5b16fdf5a0a257bf81f0a91 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 13 Feb 2026 16:44:14 -0500 Subject: [PATCH 0600/1893] 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 --- vmm/src/config.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 25c0d96ae6..8c8b5cbda9 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -19,6 +19,7 @@ use option_parser::{ use serde::{Deserialize, Serialize}; use thiserror::Error; use virtio_bindings::virtio_blk::VIRTIO_BLK_ID_BYTES; +use virtio_bindings::virtio_ids::*; use virtio_devices::block::MINIMUM_BLOCK_QUEUE_SIZE; use virtio_devices::vhost_user::VIRTIO_FS_TAG_LEN; use virtio_devices::{RateLimiterConfig, TokenBucketConfig}; @@ -46,6 +47,11 @@ pub enum Error { /// Filesystem socket is missing #[error("Error parsing --fs: socket missing")] ParseFsSockMissing, + /// Generic vhost-user virtio ID is invalid + #[error( + "Error parsing --generic-vhost-user: virtio ID {0:?} invalid (leading zeros or unknown string)" + )] + ParseGenericVhostUserVirtioIdInvalid(String), /// Generic vhost-user socket is missing #[error("Error parsing --generic-vhost-user: socket missing")] ParseGenericVhostUserSockMissing, @@ -1670,7 +1676,7 @@ impl BalloonConfig { impl GenericVhostUserConfig { pub const SYNTAX: &'static str = "generic vhost-user parameters \ - \"virtio_id=,\ + \"virtio_id=,\ socket=,\ queue_sizes=,\ id=,pci_segment=\""; @@ -1695,10 +1701,64 @@ impl GenericVhostUserConfig { .convert("queue_sizes") .map_err(Error::ParseGenericVhostUser)? .ok_or(Error::ParseGenericVhostUserQueueSizeMissing)?; - let device_type = parser - .convert("virtio_id") + let device_type_str = parser + .convert::("virtio_id") .map_err(Error::ParseGenericVhostUser)? .ok_or(Error::ParseGenericVhostUserVirtioIdMissing)?; + let device_type = match device_type_str.as_bytes() { + b"net" => VIRTIO_ID_NET, + b"block" => VIRTIO_ID_BLOCK, + b"console" => VIRTIO_ID_CONSOLE, + b"rng" => VIRTIO_ID_RNG, + b"balloon" => VIRTIO_ID_BALLOON, + b"iomem" => VIRTIO_ID_IOMEM, + b"rpmsg" => VIRTIO_ID_RPMSG, + b"scsi" => VIRTIO_ID_SCSI, + b"9p" => VIRTIO_ID_9P, + b"mac80211_wlan" => VIRTIO_ID_MAC80211_WLAN, + b"rproc_serial" => VIRTIO_ID_RPROC_SERIAL, + b"caif" => VIRTIO_ID_CAIF, + b"memory_balloon" => VIRTIO_ID_MEMORY_BALLOON, + b"gpu" => VIRTIO_ID_GPU, + b"clock" => VIRTIO_ID_CLOCK, + b"input" => VIRTIO_ID_INPUT, + b"vsock" => VIRTIO_ID_VSOCK, + b"crypto" => VIRTIO_ID_CRYPTO, + b"signal_dist" => VIRTIO_ID_SIGNAL_DIST, + b"pstore" => VIRTIO_ID_PSTORE, + b"iommu" => VIRTIO_ID_IOMMU, + b"mem" => VIRTIO_ID_MEM, + b"sound" => VIRTIO_ID_SOUND, + b"fs" => VIRTIO_ID_FS, + b"pmem" => VIRTIO_ID_PMEM, + b"rpmb" => VIRTIO_ID_RPMB, + b"mac80211_hwsim" => VIRTIO_ID_MAC80211_HWSIM, + b"video_encoder" => VIRTIO_ID_VIDEO_ENCODER, + b"video_decoder" => VIRTIO_ID_VIDEO_DECODER, + b"scmi" => VIRTIO_ID_SCMI, + b"nitro_sec_mod" => VIRTIO_ID_NITRO_SEC_MOD, + b"i2c" => VIRTIO_ID_I2C_ADAPTER, + b"watchdog" => VIRTIO_ID_WATCHDOG, + b"can" => VIRTIO_ID_CAN, + b"dmabuf" => VIRTIO_ID_DMABUF, + b"param_serv" => VIRTIO_ID_PARAM_SERV, + b"audio_policy" => VIRTIO_ID_AUDIO_POLICY, + b"bt" => VIRTIO_ID_BT, + b"gpio" => VIRTIO_ID_GPIO, + b"rdma" => 42, + b"camera" => 43, + b"ism" => 44, + b"spi" => 45, + b"tee" => 46, + b"cpu_balloon" => 47, + b"media" => 48, + b"usb" => 49, + [b'1'..=b'9', ..] => match device_type_str.parse() { + Ok(id) => id, + Err(_) => return Err(Error::ParseGenericVhostUserVirtioIdInvalid(device_type_str)), + }, + _ => return Err(Error::ParseGenericVhostUserVirtioIdInvalid(device_type_str)), + }; let id = parser.get("id"); let pci_segment = parser .convert("pci_segment") From 989f3a323317878dd98c6cb97ddd02a7cb73a238 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 13 Feb 2026 16:45:10 -0500 Subject: [PATCH 0601/1893] 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 --- vmm/src/config.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 8c8b5cbda9..3e65f6a6d9 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -52,6 +52,11 @@ pub enum Error { "Error parsing --generic-vhost-user: virtio ID {0:?} invalid (leading zeros or unknown string)" )] ParseGenericVhostUserVirtioIdInvalid(String), + /// Generic vhost-user virtio ID is unsupported + #[error( + "Error parsing --generic-vhost-user: device with virtio ID {0:?} cannot be implemented via vhost-user" + )] + ParseGenericVhostUserVirtioIdUnsupported(String), /// Generic vhost-user socket is missing #[error("Error parsing --generic-vhost-user: socket missing")] ParseGenericVhostUserSockMissing, @@ -1759,6 +1764,18 @@ impl GenericVhostUserConfig { }, _ => return Err(Error::ParseGenericVhostUserVirtioIdInvalid(device_type_str)), }; + match device_type { + // vhost-user devices of these types definitely cannot work. + // Cloud Hypervisor needs to know if an IOMMU exists so that it + // can perform address translation, and a vhost-user device has + // no supported way to reset the guest. + VIRTIO_ID_WATCHDOG | VIRTIO_ID_IOMMU => { + return Err(Error::ParseGenericVhostUserVirtioIdUnsupported( + device_type_str, + )); + } + _ => {} + } let id = parser.get("id"); let pci_segment = parser .convert("pci_segment") @@ -3992,6 +4009,9 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" )); if pci_segment <= u16::MAX.into() && virtio_id <= u32::MAX.into() + && virtio_id != u64::from(VIRTIO_ID_BALLOON) + && virtio_id != u64::from(VIRTIO_ID_WATCHDOG) + && virtio_id != u64::from(VIRTIO_ID_IOMMU) && queue_sizes.0.iter().all(|&f| f <= u16::MAX.into()) { assert_eq!( From 25a63c8b879ea8311a2c47026abc11b909decefb Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Feb 2026 13:57:09 +0100 Subject: [PATCH 0602/1893] 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 128M'. Signed-off-by: Anatol Belski --- cloud-hypervisor/tests/integration.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 221aadfbaf..5ac8d91597 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -3570,17 +3570,23 @@ mod common_parallel { _test_virtio_block(FOCAL_IMAGE_NAME, true, true, false, false, ImageType::Raw); } - fn run_qemu_img(path: &std::path::Path, args: &[&str]) -> std::process::Output { - std::process::Command::new("qemu-img") - .arg(args[0]) + fn run_qemu_img( + path: &std::path::Path, + args: &[&str], + trailing_args: Option<&[&str]>, + ) -> std::process::Output { + let mut cmd = std::process::Command::new("qemu-img"); + cmd.arg(args[0]) .args(&args[1..]) - .arg(path.to_str().unwrap()) - .output() - .unwrap() + .arg(path.to_str().unwrap()); + if let Some(extra) = trailing_args { + cmd.args(extra); + } + cmd.output().unwrap() } fn get_image_info(path: &std::path::Path) -> Option { - let output = run_qemu_img(path, &["info", "-U", "--output=json"]); + let output = run_qemu_img(path, &["info", "-U", "--output=json"], None); output.status.success().then(|| ())?; serde_json::from_slice(&output.stdout).ok() @@ -3759,7 +3765,7 @@ mod common_parallel { initial_backing_checksum: Option<(std::path::PathBuf, String, u32)>, ) { let path = resolve_disk_path(path_or_image_name); - let output = run_qemu_img(&path, &["check"]); + let output = run_qemu_img(&path, &["check"], None); assert!( output.status.success(), @@ -3777,7 +3783,7 @@ mod common_parallel { if let Some((backing_path, format, initial_checksum)) = initial_backing_checksum { if format.parse::().ok() != Some(block::qcow::ImageType::Raw) { - let output = run_qemu_img(&backing_path, &["check"]); + let output = run_qemu_img(&backing_path, &["check"], None); assert!( output.status.success(), From bc374c537c3d25e90eac663d3c15ffa92e27db58 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Feb 2026 14:29:12 +0100 Subject: [PATCH 0603/1893] 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 --- cloud-hypervisor/tests/integration.rs | 103 ++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5ac8d91597..bbff720057 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7646,6 +7646,109 @@ mod common_parallel { _test_virtio_block_discard("vhdx", "vhdx", &[], false, false); } + #[test] + fn test_virtio_block_discard_loop_device() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let test_disk_path = guest.tmp_dir.as_path().join("loop_discard_test.raw"); + let res = run_qemu_img(&test_disk_path, &["create", "-f", "raw"], Some(&["128M"])); + assert!( + res.status.success(), + "Failed to create raw backing image: {}", + String::from_utf8_lossy(&res.stderr) + ); + + let loop_dev = create_loop_device(test_disk_path.to_str().unwrap(), 4096, 5); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={},image_type=raw", &loop_dev).as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + assert_eq!( + guest + .ssh_command("lsblk -t | grep vdc | awk '{print $6}'") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4096 + ); + + let discard_max = guest + .ssh_command("cat /sys/block/vdc/queue/discard_max_bytes") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(); + assert!( + discard_max > 0, + "discard_max_bytes={discard_max}, VIRTIO_BLK_F_DISCARD not negotiated" + ); + + guest + .ssh_command("sudo dd if=/dev/urandom of=/dev/vdc bs=4096 count=1024 oflag=direct") + .unwrap(); + guest.ssh_command("sync").unwrap(); + + let result = guest + .ssh_command("sudo blkdiscard -v -o 0 -l 4194304 /dev/vdc 2>&1 || true") + .unwrap(); + assert!( + !result.contains("Operation not supported") + && !result.contains("BLKDISCARD ioctl failed"), + "blkdiscard failed on loop device: {result}" + ); + + guest.ssh_command("sync").unwrap(); + + assert_guest_disk_region_is_zero(&guest, "/dev/vdc", 0, 4194304); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + Command::new("losetup") + .args(["-d", &loop_dev]) + .output() + .expect("loop device not found"); + } + fn _test_virtio_block_fstrim( format_name: &str, qemu_img_format: &str, From 8a165aef3b6995a5f9db393e75eb4aa52352b585 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Feb 2026 15:04:14 +0100 Subject: [PATCH 0604/1893] 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 --- cloud-hypervisor/tests/integration.rs | 183 ++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index bbff720057..2037ad0083 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7749,6 +7749,189 @@ mod common_parallel { .expect("loop device not found"); } + #[test] + fn test_virtio_block_discard_dm_snapshot() { + // 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 guest must handle this + // gracefully even under repeated attempts. + // + // DM topology follows the same pattern used by WindowsDiskConfig. + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let origin_path = guest.tmp_dir.as_path().join("dm_origin.raw"); + let cow_path = guest.tmp_dir.as_path().join("dm_cow.raw"); + + let res = run_qemu_img(&origin_path, &["create", "-f", "raw"], Some(&["128M"])); + assert!( + res.status.success(), + "Failed to create origin image: {}", + String::from_utf8_lossy(&res.stderr) + ); + + let cow_size: u64 = 128 << 20; + let cow_sectors = cow_size / 512; + let cow_file = File::create(&cow_path).expect("Expect creating COW image to succeed"); + cow_file + .set_len(cow_size) + .expect("Expect truncating COW image to succeed"); + + let origin_sectors: u64 = 128 * 1024 * 1024 / 512; + let origin_loop = create_loop_device(origin_path.to_str().unwrap(), 4096, 5); + let cow_loop = create_loop_device(cow_path.to_str().unwrap(), 512, 5); + + let unique = format!( + "ch-test-{}", + guest + .tmp_dir + .as_path() + .file_name() + .unwrap() + .to_str() + .unwrap() + ); + let cow_dm_name = format!("{unique}-cow"); + let snap_dm_name = format!("{unique}-snap"); + + let output = Command::new("dmsetup") + .args([ + "create", + &cow_dm_name, + "--table", + &format!("0 {cow_sectors} linear {cow_loop} 0"), + ]) + .output() + .expect("Failed to run dmsetup"); + assert!( + output.status.success(), + "dmsetup create (cow linear) failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + Command::new("dmsetup") + .arg("mknodes") + .output() + .expect("dmsetup mknodes failed"); + + // dm-snapshot: origin + COW, non-persistent, chunk size 8 sectors. + let output = Command::new("dmsetup") + .args([ + "create", + &snap_dm_name, + "--table", + &format!("0 {origin_sectors} snapshot {origin_loop} /dev/mapper/{cow_dm_name} N 8"), + ]) + .output() + .expect("Failed to run dmsetup"); + assert!( + output.status.success(), + "dmsetup create (snapshot) failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + Command::new("dmsetup") + .arg("mknodes") + .output() + .expect("dmsetup mknodes failed"); + + let dm_dev = format!("/dev/mapper/{snap_dm_name}"); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={},image_type=raw", &dm_dev).as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + let discard_max = guest + .ssh_command("cat /sys/block/vdc/queue/discard_max_bytes") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(); + assert!( + discard_max > 0, + "discard_max_bytes={discard_max}, VIRTIO_BLK_F_DISCARD not negotiated" + ); + + guest + .ssh_command("sudo dd if=/dev/urandom of=/dev/vdc bs=4096 count=1024 oflag=direct") + .unwrap(); + guest.ssh_command("sync").unwrap(); + + // Discard is expected to fail on DM snapshot because the + // snapshot target does not support BLKDISCARD. + for attempt in 1..=3 { + let result = guest + .ssh_command("sudo blkdiscard -o 0 -l 4194304 /dev/vdc 2>&1; echo rc=$?") + .unwrap(); + println!("blkdiscard attempt {attempt}: {result}"); + + let uptime = guest.ssh_command("uptime").unwrap(); + assert!( + !uptime.is_empty(), + "Guest unresponsive after blkdiscard attempt {attempt}" + ); + } + + guest + .ssh_command("sudo dd if=/dev/urandom of=/dev/vdc bs=4096 count=256 oflag=direct") + .unwrap(); + let readback = guest + .ssh_command("sudo dd if=/dev/vdc bs=4096 count=1 iflag=direct 2>/dev/null | od -A n -t x1 | head -1") + .unwrap(); + assert!( + !readback.trim().is_empty(), + "Failed to read back from device after discard errors" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + let _ = Command::new("dmsetup") + .args(["remove", &snap_dm_name]) + .output(); + let _ = Command::new("dmsetup") + .args(["remove", &cow_dm_name]) + .output(); + let _ = Command::new("losetup").args(["-d", &origin_loop]).output(); + let _ = Command::new("losetup").args(["-d", &cow_loop]).output(); + } + fn _test_virtio_block_fstrim( format_name: &str, qemu_img_format: &str, From e067c768802bb7ba4f40154176ab88f1e4ba9868 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Feb 2026 15:18:43 +0100 Subject: [PATCH 0605/1893] 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 --- cloud-hypervisor/tests/integration.rs | 18 +++++------ test_infra/src/lib.rs | 46 ++++++++++++--------------- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 2037ad0083..99a22f2730 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -11537,7 +11537,7 @@ mod windows { .args(["--kernel", edk2_path().to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks() + .default_disks_sparse_off() .default_net() .capture_output() .spawn() @@ -11586,7 +11586,7 @@ mod windows { .args([ "--disk", format!( - "path={},num_queues=4", + "path={},num_queues=4,sparse=off", windows_guest .guest() .disk_config @@ -11654,7 +11654,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks() + .default_disks_sparse_off() .default_net() .capture_output() .spawn() @@ -11743,7 +11743,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks() + .default_disks_sparse_off() .default_net() .capture_output() .spawn() @@ -11818,7 +11818,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks() + .default_disks_sparse_off() .default_net() .capture_output() .spawn() @@ -11892,7 +11892,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks() + .default_disks_sparse_off() .default_net() .capture_output() .spawn() @@ -11966,7 +11966,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks() + .default_disks_sparse_off() .default_net() .capture_output() .spawn() @@ -12062,7 +12062,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks() + .default_disks_sparse_off() .default_net() .capture_output() .spawn() @@ -12194,7 +12194,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks() + .default_disks_sparse_off() // The multi net dev config is borrowed from test_multiple_network_interfaces .args([ "--net", diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 3ab550870e..2848a130e5 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1448,35 +1448,31 @@ impl<'a> GuestCommand<'a> { } pub fn default_disks(&mut self) -> &mut Self { - if self.guest.disk_config.disk(DiskType::CloudInit).is_some() { + self.default_disks_inner(true) + } + + pub fn default_disks_sparse_off(&mut self) -> &mut Self { + self.default_disks_inner(false) + } + + fn default_disks_inner(&mut self, sparse: bool) -> &mut Self { + let sparse_opt = if sparse { "" } else { ",sparse=off" }; + let os_disk = format!( + "path={}{}", + self.guest + .disk_config + .disk(DiskType::OperatingSystem) + .unwrap(), + sparse_opt + ); + if let Some(cloud_init) = self.guest.disk_config.disk(DiskType::CloudInit) { self.args([ "--disk", - format!( - "path={}", - self.guest - .disk_config - .disk(DiskType::OperatingSystem) - .unwrap() - ) - .as_str(), - format!( - "path={}", - self.guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), + os_disk.as_str(), + format!("path={cloud_init}").as_str(), ]) } else { - self.args([ - "--disk", - format!( - "path={}", - self.guest - .disk_config - .disk(DiskType::OperatingSystem) - .unwrap() - ) - .as_str(), - ]) + self.args(["--disk", os_disk.as_str()]) } } From 6404d2d513e4799ac485168bce2a801ecfd69d1d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Feb 2026 22:13:46 +0100 Subject: [PATCH 0606/1893] 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 --- block/src/lib.rs | 41 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 9f78cefd9e..3d45473a9a 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -846,34 +846,19 @@ fn probe_file_sparse_support(fd: libc::c_int) -> bool { supported } -/// Probe sparse support for a block device using ioctls. -fn probe_block_device_sparse_support(fd: libc::c_int) -> bool { - ioctl_io_nr!(BLKDISCARD, 0x12, 119); - ioctl_io_nr!(BLKZEROOUT, 0x12, 127); - - let range: [u64; 2] = [0, 0]; - - // SAFETY: FFI call with valid fd and valid range buffer - let punch_hole = unsafe { ioctl(fd, BLKDISCARD() as _, &range) } == 0; - - if !punch_hole { - let err = io::Error::last_os_error(); - debug!("Block device BLKDISCARD probe returned: {err}"); - } - - // SAFETY: FFI call with valid fd and valid range buffer - let zero_range = unsafe { ioctl(fd, BLKZEROOUT() as _, &range) } == 0; - - if !zero_range { - let err = io::Error::last_os_error(); - debug!("Block device BLKZEROOUT probe returned: {err}"); - } - - let supported = punch_hole || zero_range; - info!( - "Probed block device sparse support: punch_hole={punch_hole}, zero_range={zero_range} => {supported}" - ); - supported +/// Probe sparse support for a block device. +/// +/// Block devices always report sparse support. `BLKZEROOUT` is guaranteed to +/// succeed as the kernel provides a software fallback writing explicit zeros +/// when the hardware lacks a native write zeroes command. `BLKDISCARD` may fail +/// at runtime with `EOPNOTSUPP` on devices without trim or discard support, but +/// Linux guests handle this gracefully by ceasing discard requests. +/// +/// There is no non destructive read only ioctl to query block device discard +/// or write zeroes capabilities. +fn probe_block_device_sparse_support(_fd: libc::c_int) -> bool { + info!("Block device: assuming sparse support"); + true } /// Preallocate disk space for a disk image file. From c9cf3294ea83ab15cd3790ea71f1702e7bf85922 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Feb 2026 15:10:59 -0800 Subject: [PATCH 0607/1893] 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 --- vmm/src/vm.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 9c37457c19..3f793cd807 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -612,7 +612,6 @@ impl Vm { #[cfg(not(target_arch = "riscv64"))] timestamp, snapshot, - &config, )?; // Perform hypervisor-specific initialization @@ -798,10 +797,9 @@ impl Vm { boot_id_list: BTreeSet, #[cfg(not(target_arch = "riscv64"))] timestamp: Instant, snapshot: Option<&Snapshot>, - _vm_config: &Arc>, ) -> Result>> { #[cfg(feature = "tdx")] - let dynamic = !_vm_config.lock().unwrap().is_tdx_enabled(); + let dynamic = !config.lock().unwrap().is_tdx_enabled(); #[cfg(not(feature = "tdx"))] let dynamic = true; From 15d1f1d7fdd7b0698ace412c2398fbc3d515bcba Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Tue, 24 Feb 2026 14:39:45 +0100 Subject: [PATCH 0608/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com --- vmm/src/device_manager.rs | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index f22696c7fe..52e4cddfa0 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -743,15 +743,10 @@ impl DeviceRelocation for AddressManager { ) -> std::result::Result<(), std::io::Error> { match region_type { PciBarRegionType::IoRegion => { + let mut sys_allocator = self.allocator.lock().unwrap(); // Update system allocator - self.allocator - .lock() - .unwrap() - .free_io_addresses(GuestAddress(old_base), len as GuestUsize); - - self.allocator - .lock() - .unwrap() + sys_allocator.free_io_addresses(GuestAddress(old_base), len as GuestUsize); + sys_allocator .allocate_io_addresses(Some(GuestAddress(new_base)), len as GuestUsize, None) .ok_or_else(|| io::Error::other("failed allocating new IO range"))?; @@ -761,26 +756,22 @@ impl DeviceRelocation for AddressManager { .map_err(io::Error::other)?; } PciBarRegionType::Memory32BitRegion | PciBarRegionType::Memory64BitRegion => { - let allocators = if region_type == PciBarRegionType::Memory32BitRegion { + let pci_mmio_allocators = if region_type == PciBarRegionType::Memory32BitRegion { &self.pci_mmio32_allocators } else { &self.pci_mmio64_allocators }; - // Find the specific allocator that this BAR was allocated from and use it for new one - for allocator in allocators { - let allocator_base = allocator.lock().unwrap().base(); - let allocator_end = allocator.lock().unwrap().end(); + // Find the specific allocator that this BAR was allocated from and use it for a new one + for pci_mmio_allocator_mutex in pci_mmio_allocators { + let mut pci_mmio_allocator = pci_mmio_allocator_mutex.lock().unwrap(); - if old_base >= allocator_base.0 && old_base <= allocator_end.0 { - allocator - .lock() - .unwrap() - .free(GuestAddress(old_base), len as GuestUsize); + if old_base >= pci_mmio_allocator.base().0 + && old_base <= pci_mmio_allocator.end().0 + { + pci_mmio_allocator.free(GuestAddress(old_base), len as GuestUsize); - allocator - .lock() - .unwrap() + pci_mmio_allocator .allocate(Some(GuestAddress(new_base)), len as GuestUsize, Some(len)) .ok_or_else(|| io::Error::other("failed allocating new MMIO range"))?; From 3f800d2bb41f2ee92cda53eccb1588feb6930be3 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 23 Feb 2026 15:31:54 -0800 Subject: [PATCH 0609/1893] 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: INFO:vmm/src/cpu.rs:1247 -- vCPU 1: core scheduling cookie = 0x33e4c167 cloud-hypervisor: 0.243102s: INFO:vmm/src/cpu.rs:1247 -- vCPU 0: core scheduling cookie = 0x33e4c167 vCPU case: cloud-hypervisor: 0.089356s: INFO:vmm/src/cpu.rs:1247 -- vCPU 0: core scheduling cookie = 0x13993ad6 cloud-hypervisor: 0.089380s: INFO:vmm/src/cpu.rs:1247 -- vCPU 1: core scheduling cookie = 0xd48e86e Signed-off-by: Rob Bradford --- cloud-hypervisor/src/main.rs | 7 +- docs/cpu.md | 34 +++++- fuzz/fuzz_targets/http_api.rs | 1 + vmm/src/api/openapi/cloud-hypervisor.yaml | 4 + vmm/src/config.rs | 62 +++++++++- vmm/src/cpu.rs | 139 +++++++++++++++++++++- vmm/src/lib.rs | 5 +- vmm/src/vm_config.rs | 11 ++ 8 files changed, 254 insertions(+), 9 deletions(-) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index b2b184248b..b4d2bdf534 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -214,7 +214,7 @@ fn get_cli_options_sorted( kvm_hyperv=on|off,max_phys_bits=,\ affinity=,\ features=,\ - nested=on|off", + nested=on|off,core_scheduling=vm|vcpu|off", ) .default_value(default_vcpus) .group("vm-config"), @@ -916,8 +916,8 @@ mod unit_tests { #[cfg(target_arch = "x86_64")] use vmm::vm_config::DebugConsoleConfig; use vmm::vm_config::{ - ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, HotplugMethod, MemoryConfig, - PayloadConfig, RngConfig, VmConfig, + ConsoleConfig, ConsoleOutputMode, CoreScheduling, CpuFeatures, CpusConfig, HotplugMethod, + MemoryConfig, PayloadConfig, RngConfig, VmConfig, }; use crate::test_util::assert_args_sorted; @@ -968,6 +968,7 @@ mod unit_tests { affinity: None, features: CpuFeatures::default(), nested: true, + core_scheduling: CoreScheduling::Vm, }, memory: MemoryConfig { size: 536_870_912, diff --git a/docs/cpu.md b/docs/cpu.md index 6a55942691..8ed247c909 100644 --- a/docs/cpu.md +++ b/docs/cpu.md @@ -19,11 +19,12 @@ struct CpusConfig { affinity: Option>, features: CpuFeatures, nested: bool, + core_scheduling: CoreScheduling, } ``` ``` ---cpus boot=,max=,topology=:::,kvm_hyperv=on|off,max_phys_bits=,affinity=,features=,nested=on|off +--cpus boot=,max=,topology=:::,kvm_hyperv=on|off,max_phys_bits=,affinity=,features=,nested=on|off,core_scheduling=vm|vcpu|off ``` ### `boot` @@ -221,3 +222,34 @@ _Example_ ``` --cpus nested=on ``` + +### `core_scheduling` + +Core scheduling mode for vCPU threads. + +This option controls Linux core scheduling (`PR_SCHED_CORE`) for vCPU threads, +which prevents untrusted tasks from sharing SMT siblings. This mitigates +side-channel attacks (e.g. MDS, L1TF) between vCPU threads. + +Three modes are available: + +- `vm` (default): All vCPU threads share a single core scheduling cookie. + vCPUs may be co-scheduled on SMT siblings of the same core, providing + better performance while still isolating VM threads from host tasks. +- `vcpu`: Each vCPU thread gets its own unique cookie. No two vCPUs can + share SMT siblings, providing the strongest isolation between vCPUs at + the cost of performance. +- `off`: No core scheduling is applied. + +On kernels older than 5.14 (which lack `PR_SCHED_CORE` support), the +option silently has no effect. + +_Example_ + +``` +--cpus boot=2,core_scheduling=vm +``` + +In this example, both vCPUs will share the same core scheduling cookie, +allowing them to be co-scheduled on SMT siblings while preventing host +threads from sharing those siblings. diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index b7f38994fb..b7128a1678 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -137,6 +137,7 @@ impl RequestHandler for StubApiRequestHandler { affinity: None, features: CpuFeatures::default(), nested: true, + core_scheduling: CoreScheduling::default(), }, memory: MemoryConfig { size: 536_870_912, diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 499218c7a5..c4f4b6acf0 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -738,6 +738,10 @@ components: $ref: "#/components/schemas/CpuAffinity" features: $ref: "#/components/schemas/CpuFeatures" + core_scheduling: + type: string + enum: ["Vm", "Vcpu", "Off"] + default: "Vm" PciSegmentConfig: required: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 3e65f6a6d9..3b32463674 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -596,6 +596,23 @@ impl FromStr for HotplugMethod { } } +pub enum ParseCoreSchedulingError { + InvalidValue(String), +} + +impl FromStr for CoreScheduling { + type Err = ParseCoreSchedulingError; + + fn from_str(s: &str) -> std::result::Result { + match s.to_lowercase().as_str() { + "vm" => Ok(CoreScheduling::Vm), + "vcpu" => Ok(CoreScheduling::Vcpu), + "off" => Ok(CoreScheduling::Off), + _ => Err(ParseCoreSchedulingError::InvalidValue(s.to_owned())), + } + } +} + pub enum CpuTopologyParseError { InvalidValue(String), } @@ -640,7 +657,8 @@ impl CpusConfig { .add("max_phys_bits") .add("affinity") .add("features") - .add("nested"); + .add("nested") + .add("core_scheduling"); parser.parse(cpus).map_err(Error::ParseCpus)?; let boot_vcpus: u32 = parser @@ -707,6 +725,11 @@ impl CpusConfig { "nested=off is not supported on aarch64 and riscv64 architectures".to_string(), ))); } + let core_scheduling = parser + .convert("core_scheduling") + .map_err(Error::ParseCpus)? + .unwrap_or(CoreScheduling::Vm); + Ok(CpusConfig { boot_vcpus, max_vcpus, @@ -716,6 +739,7 @@ impl CpusConfig { affinity, features, nested, + core_scheduling, }) } } @@ -3579,6 +3603,42 @@ mod unit_tests { }, ); + // Test core_scheduling parsing + assert_eq!( + CpusConfig::parse("boot=1,core_scheduling=vm")?, + CpusConfig { + boot_vcpus: 1, + max_vcpus: 1, + core_scheduling: CoreScheduling::Vm, + ..Default::default() + } + ); + assert_eq!( + CpusConfig::parse("boot=1,core_scheduling=vcpu")?, + CpusConfig { + boot_vcpus: 1, + max_vcpus: 1, + core_scheduling: CoreScheduling::Vcpu, + ..Default::default() + } + ); + assert_eq!( + CpusConfig::parse("boot=1,core_scheduling=off")?, + CpusConfig { + boot_vcpus: 1, + max_vcpus: 1, + core_scheduling: CoreScheduling::Off, + ..Default::default() + } + ); + // Default (no core_scheduling specified) should be Vm + assert_eq!( + CpusConfig::parse("boot=1")?.core_scheduling, + CoreScheduling::Vm + ); + // Invalid value should error + CpusConfig::parse("boot=1,core_scheduling=invalid").unwrap_err(); + Ok(()) } diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index bba78e642c..78149c4b55 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -17,7 +17,7 @@ use std::io::Write; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use std::mem::size_of; use std::os::unix::thread::JoinHandleExt; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; use std::sync::{Arc, Barrier, Mutex}; use std::{cmp, io, result, thread}; @@ -92,7 +92,7 @@ use crate::gdb::{Debuggable, DebuggableError, get_raw_tid}; use crate::seccomp_filters::{Thread, get_seccomp_filter}; #[cfg(target_arch = "x86_64")] use crate::vm::physical_bits; -use crate::vm_config::CpusConfig; +use crate::vm_config::{CoreScheduling, CpusConfig}; use crate::{CPU_MANAGER_SNAPSHOT_ID, GuestMemoryMmap}; #[cfg(all(target_arch = "aarch64", feature = "guest_debug"))] @@ -220,9 +220,79 @@ pub enum Error { #[cfg(feature = "mshv")] #[error("Failed to set partition property")] SetPartitionProperty(#[source] anyhow::Error), + + #[error("Error enabling core scheduling")] + CoreScheduling(#[source] io::Error), } pub type Result = result::Result; +const PR_SCHED_CORE: libc::c_int = 62; +const PR_SCHED_CORE_GET: libc::c_int = 0; +const PR_SCHED_CORE_CREATE: libc::c_int = 1; +const PR_SCHED_CORE_SHARE_FROM: libc::c_int = 3; +const PIDTYPE_PID: libc::c_int = 0; + +/// Create a new unique core scheduling cookie for the current thread. +/// Silently succeeds on kernels that don't support PR_SCHED_CORE. +fn core_scheduling_create() -> Result<()> { + // SAFETY: prctl with PR_SCHED_CORE_CREATE on the current thread (pid=0). + // All arguments are valid constants. We check the return value. + let ret = unsafe { libc::prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, 0, PIDTYPE_PID, 0) }; + if ret == -1 { + let err = io::Error::last_os_error(); + // EINVAL: kernel < 5.14 where PR_SCHED_CORE is unknown. + // ENODEV: CONFIG_SCHED_CORE is enabled but SMT is not present/enabled, + // so core scheduling is not applicable. + // Both mean core scheduling is unavailable; silently ignore. + match err.raw_os_error() { + Some(libc::EINVAL) => { + warn!("Kernel lacks CONFIG_SCHED_CORE support - no SMT isolation"); + } + Some(libc::ENODEV) => {} + _ => return Err(Error::CoreScheduling(err)), + } + } + Ok(()) +} + +/// Copy the core scheduling cookie from the thread identified by `tid` +/// to the current thread, placing both in the same scheduling group. +/// Silently succeeds on kernels that don't support PR_SCHED_CORE. +fn core_scheduling_share_from(tid: i32) -> Result<()> { + // SAFETY: prctl with PR_SCHED_CORE_SHARE_FROM targeting tid. + // All arguments are valid. We check the return value. + let ret = unsafe { libc::prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, tid, PIDTYPE_PID, 0) }; + if ret == -1 { + let err = io::Error::last_os_error(); + match err.raw_os_error() { + Some(libc::EINVAL) | Some(libc::ENODEV) => {} + _ => return Err(Error::CoreScheduling(err)), + } + } + Ok(()) +} + +/// Read the core scheduling cookie of the current thread. +/// Returns 0 if no cookie is set or the kernel doesn't support PR_SCHED_CORE. +fn core_scheduling_cookie() -> u64 { + let mut cookie: u64 = 0; + // SAFETY: PR_SCHED_CORE_GET with pid=0 reads the current thread's cookie + // into the provided pointer. We pass a valid mutable reference. + let ret = unsafe { + libc::prctl( + PR_SCHED_CORE, + PR_SCHED_CORE_GET, + 0, + PIDTYPE_PID, + &mut cookie as *mut u64, + ) + }; + if ret == -1 { + return 0; + } + cookie +} + #[cfg(target_arch = "x86_64")] #[allow(dead_code)] #[repr(C, packed)] @@ -609,6 +679,9 @@ pub struct CpuManager { hypervisor: Arc, #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, + // TID of the first vCPU thread that created a core scheduling cookie (VM mode). + // 0 = no leader yet, -1 = leader creating cookie, >0 = leader TID (cookie ready). + core_scheduling_group_leader: Arc, } const CPU_ENABLE_FLAG: usize = 0; @@ -851,6 +924,7 @@ impl CpuManager { hypervisor, #[cfg(feature = "sev_snp")] sev_snp_enabled, + core_scheduling_group_leader: Arc::new(AtomicI32::new(0)), }))) } @@ -1079,6 +1153,9 @@ impl CpuManager { cpuset }); + let core_scheduling = self.config.core_scheduling; + let core_scheduling_group_leader = self.core_scheduling_group_leader.clone(); + // Retrieve seccomp filter for vcpu thread let vcpu_seccomp_filter = get_seccomp_filter( &self.seccomp_action, @@ -1117,6 +1194,64 @@ impl CpuManager { } } + // Set up core scheduling before seccomp locks down prctl. + match core_scheduling { + CoreScheduling::Vcpu => { + // Each vCPU gets its own unique cookie + if let Err(e) = core_scheduling_create() { + error!( + "Failed to enable core scheduling for vCPU {vcpu_id}: {e:?}" + ); + return; + } + } + CoreScheduling::Vm => { + // First vCPU creates a cookie; all others share from it. + // SAFETY: gettid() is always safe to call. + let my_tid = unsafe { libc::gettid() }; + if core_scheduling_group_leader + .compare_exchange(0, -1, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + { + // We are the group leader — create the cookie + if let Err(e) = core_scheduling_create() { + error!( + "Failed to create core scheduling cookie: {e:?}" + ); + return; + } + // Signal that the cookie is ready by storing real TID + core_scheduling_group_leader + .store(my_tid, Ordering::Release); + } else { + // Wait for the leader to finish creating the cookie + let mut leader_tid = + core_scheduling_group_leader.load(Ordering::Acquire); + while leader_tid <= 0 { + std::hint::spin_loop(); + leader_tid = + core_scheduling_group_leader.load(Ordering::Acquire); + } + // Copy the leader's cookie to this thread + if let Err(e) = core_scheduling_share_from(leader_tid) { + error!( + "Failed to share core scheduling cookie \ + to vCPU {vcpu_id}: {e:?}" + ); + return; + } + } + } + CoreScheduling::Off => {} + } + + if core_scheduling != CoreScheduling::Off { + info!( + "vCPU {vcpu_id}: core scheduling cookie = {:#x}", + core_scheduling_cookie() + ); + } + // Apply seccomp filter for vcpu thread. if !vcpu_seccomp_filter.is_empty() && let Err(e) = apply_filter(&vcpu_seccomp_filter).map_err(Error::ApplySeccompFilter) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 627b13d5d7..9ffd7fc0bc 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2411,8 +2411,8 @@ mod unit_tests { #[cfg(target_arch = "x86_64")] use crate::vm_config::DebugConsoleConfig; use crate::vm_config::{ - ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, HotplugMethod, MemoryConfig, - PayloadConfig, RngConfig, + ConsoleConfig, ConsoleOutputMode, CoreScheduling, CpuFeatures, CpusConfig, HotplugMethod, + MemoryConfig, PayloadConfig, RngConfig, }; fn create_dummy_vmm() -> Vmm { @@ -2441,6 +2441,7 @@ mod unit_tests { affinity: None, features: CpuFeatures::default(), nested: true, + core_scheduling: CoreScheduling::default(), }, memory: MemoryConfig { size: 536_870_912, diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index b9e67f7bba..33c2b23acd 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -39,6 +39,14 @@ pub struct CpuFeatures { pub amx: bool, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] +pub enum CoreScheduling { + #[default] + Vm, // All vCPUs have the same cookie so can share a core + Vcpu, // Each vCPU has a unique cookie so can't share a core + Off, +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CpuTopology { pub threads_per_core: u16, @@ -72,6 +80,8 @@ pub struct CpusConfig { pub features: CpuFeatures, #[serde(default = "default_cpusconfig_nested")] pub nested: bool, + #[serde(default)] + pub core_scheduling: CoreScheduling, } pub const DEFAULT_VCPUS: u32 = 1; @@ -87,6 +97,7 @@ impl Default for CpusConfig { affinity: None, features: CpuFeatures::default(), nested: true, + core_scheduling: CoreScheduling::default(), } } } From 272fa624ef827e31c7fb0d373801a3f213f61089 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Feb 2026 04:00:59 -0800 Subject: [PATCH 0610/1893] 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 --- .github/workflows/preview-riscv64-build.yaml | 30 -------------- .../workflows/preview-riscv64-modules.yaml | 39 ------------------- 2 files changed, 69 deletions(-) delete mode 100644 .github/workflows/preview-riscv64-build.yaml delete mode 100644 .github/workflows/preview-riscv64-modules.yaml diff --git a/.github/workflows/preview-riscv64-build.yaml b/.github/workflows/preview-riscv64-build.yaml deleted file mode 100644 index ad87232d3d..0000000000 --- a/.github/workflows/preview-riscv64-build.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: Cloud Hypervisor RISC-V 64-bit kvm build Preview -on: [pull_request, merge_group] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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)" diff --git a/.github/workflows/preview-riscv64-modules.yaml b/.github/workflows/preview-riscv64-modules.yaml deleted file mode 100644 index 1b7ac6ed16..0000000000 --- a/.github/workflows/preview-riscv64-modules.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Cloud Hypervisor RISC-V 64-bit Preview -on: [pull_request, merge_group] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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)" From 00c05f4761ae106f94269b5b6255dbd6ca734f9a Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Wed, 18 Feb 2026 14:23:03 -0800 Subject: [PATCH 0611/1893] 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 --- block/src/async_io.rs | 5 ++++- block/src/lib.rs | 14 ++++++++------ block/src/raw_async.rs | 18 +++++++++++++----- block/src/raw_async_aio.rs | 24 ++++++++++++++++++------ block/src/raw_sync.rs | 14 ++++++++++++-- 5 files changed, 55 insertions(+), 20 deletions(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index a1e8fa3e46..fe3349e6a3 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -8,7 +8,7 @@ use std::os::fd::{AsRawFd, OwnedFd, RawFd}; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; -use crate::{BatchRequest, DiskTopology}; +use crate::{BatchRequest, DiskTopology, SECTOR_SIZE}; #[derive(Error, Debug)] pub enum DiskFileError { @@ -145,4 +145,7 @@ pub trait AsyncIo: Send { fn submit_batch_requests(&mut self, _batch_request: &[BatchRequest]) -> AsyncIoResult<()> { Ok(()) } + fn alignment(&self) -> u64 { + SECTOR_SIZE + } } diff --git a/block/src/lib.rs b/block/src/lib.rs index 3d45473a9a..3ab8de9e41 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -436,6 +436,7 @@ impl Request { let sector = self.sector; let request_type = self.request_type; let offset = (sector << SECTOR_SHIFT) as libc::off_t; + let alignment = disk_image.alignment(); let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> = SmallVec::with_capacity(self.data_descriptors.len()); @@ -466,14 +467,15 @@ impl Request { assert!(origin_ptr.len() >= data_len); let origin_ptr = origin_ptr.ptr_guard(); - // Verify the buffer alignment. - // In case it's not properly aligned, an intermediate buffer is - // created with the correct alignment, and a copy from/to the - // origin buffer is performed, depending on the type of operation. - let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) { + // O_DIRECT requires buffer addresses to be aligned to the + // backend device's logical block size. In case it's not properly + // aligned, an intermediate buffer is created with the correct + // alignment, and a copy from/to the origin buffer is performed, + // depending on the type of operation. + let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(alignment) { origin_ptr.as_ptr() as *mut libc::c_void } else { - let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap(); + let layout = Layout::from_size_align(data_len, alignment as usize).unwrap(); // SAFETY: layout has non-zero size let aligned_ptr = unsafe { alloc_zeroed(layout) }; if aligned_ptr.is_null() { diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 539aaa9095..3a890d716f 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -13,7 +13,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::{BatchRequest, DiskTopology, RequestType, probe_sparse_support}; +use crate::{BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, probe_sparse_support}; pub struct RawFileDisk { file: File, @@ -40,10 +40,12 @@ impl DiskFile for RawFileDisk { } fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { - Ok(Box::new( - RawFileAsync::new(self.file.as_raw_fd(), ring_depth) - .map_err(DiskFileError::NewAsyncIo)?, - ) as Box) + let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth) + .map_err(DiskFileError::NewAsyncIo)?; + raw.alignment = DiskTopology::probe(&self.file) + .map(|t| t.logical_block_size) + .unwrap_or(SECTOR_SIZE); + Ok(Box::new(raw) as Box) } fn topology(&mut self) -> DiskTopology { @@ -72,6 +74,7 @@ pub struct RawFileAsync { fd: RawFd, io_uring: IoUring, eventfd: EventFd, + alignment: u64, } impl RawFileAsync { @@ -87,6 +90,7 @@ impl RawFileAsync { fd, io_uring, eventfd, + alignment: SECTOR_SIZE, }) } } @@ -96,6 +100,10 @@ impl AsyncIo for RawFileAsync { &self.eventfd } + fn alignment(&self) -> u64 { + self.alignment + } + fn read_vectored( &mut self, offset: libc::off_t, diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 6447a727d8..7266a3633a 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -16,7 +16,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::{DiskTopology, probe_sparse_support}; +use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support}; pub struct RawFileDiskAio { file: File, @@ -43,10 +43,12 @@ impl DiskFile for RawFileDiskAio { } fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { - Ok(Box::new( - RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth) - .map_err(DiskFileError::NewAsyncIo)?, - ) as Box) + let mut raw = RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth) + .map_err(DiskFileError::NewAsyncIo)?; + raw.alignment = DiskTopology::probe(&self.file) + .map(|t| t.logical_block_size) + .unwrap_or(SECTOR_SIZE); + Ok(Box::new(raw) as Box) } fn topology(&mut self) -> DiskTopology { @@ -71,6 +73,7 @@ pub struct RawFileAsyncAio { fd: RawFd, ctx: aio::IoContext, eventfd: EventFd, + alignment: u64, } impl RawFileAsyncAio { @@ -78,7 +81,12 @@ impl RawFileAsyncAio { let eventfd = EventFd::new(libc::EFD_NONBLOCK)?; let ctx = aio::IoContext::new(queue_depth)?; - Ok(RawFileAsyncAio { fd, ctx, eventfd }) + Ok(RawFileAsyncAio { + fd, + ctx, + eventfd, + alignment: SECTOR_SIZE, + }) } } @@ -87,6 +95,10 @@ impl AsyncIo for RawFileAsyncAio { &self.eventfd } + fn alignment(&self) -> u64 { + self.alignment + } + fn read_vectored( &mut self, offset: libc::off_t, diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index e1a5433b89..9c96863b69 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -13,7 +13,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::{DiskTopology, probe_sparse_support}; +use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support}; pub struct RawFileDiskSync { file: File, @@ -40,7 +40,11 @@ impl DiskFile for RawFileDiskSync { } fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { - Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box) + let mut raw = RawFileSync::new(self.file.as_raw_fd()); + raw.alignment = DiskTopology::probe(&self.file) + .map(|t| t.logical_block_size) + .unwrap_or(SECTOR_SIZE); + Ok(Box::new(raw) as Box) } fn topology(&mut self) -> DiskTopology { @@ -65,6 +69,7 @@ pub struct RawFileSync { fd: RawFd, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, + alignment: u64, } impl RawFileSync { @@ -73,6 +78,7 @@ impl RawFileSync { fd, eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"), completion_list: VecDeque::new(), + alignment: SECTOR_SIZE, } } } @@ -82,6 +88,10 @@ impl AsyncIo for RawFileSync { &self.eventfd } + fn alignment(&self) -> u64 { + self.alignment + } + fn read_vectored( &mut self, offset: libc::off_t, From 110192087ec5989d97f57ce3232366c135289ea2 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 26 Feb 2026 11:53:49 -0800 Subject: [PATCH 0612/1893] 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 --- vmm/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 3b32463674..46f3443136 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1414,7 +1414,7 @@ impl NetConfig { num_queues=,queue_size=,id=,\ vhost_user=,socket=,vhost_mode=client|server,\ bw_size=,bw_one_time_burst=,bw_refill_time=,\ - ops_size=,ops_one_time_burst=,ops_refill_time=,pci_segment=\ + ops_size=,ops_one_time_burst=,ops_refill_time=,pci_segment=,\ offload_tso=on|off,offload_ufo=on|off,offload_csum=on|off\""; pub fn parse(net: &str) -> Result { From 9fd9c244194f73644a590dc42160da121155bb3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 23:54:04 +0000 Subject: [PATCH 0613/1893] 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] --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6a96de491b..a6e8defba4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -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,7 +80,7 @@ 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 From fdc51d923f3a389202a625a288bccbb7cd3ded9b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 22 Feb 2026 22:06:51 -0800 Subject: [PATCH 0614/1893] 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 --- cloud-hypervisor/tests/integration.rs | 50 ++----------------------- test_infra/src/lib.rs | 53 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 99a22f2730..c836f631d4 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -889,28 +889,6 @@ fn fw_path(_fw_type: FwType) -> String { fw_path.to_str().unwrap().to_string() } -#[derive(Debug)] -struct MetaEvent { - event: String, - device_id: Option, -} - -impl MetaEvent { - pub fn match_with_json_event(&self, v: &serde_json::Value) -> bool { - let mut matched = false; - if v["event"].as_str().unwrap() == self.event { - if let Some(device_id) = &self.device_id { - if v["properties"]["id"].as_str().unwrap() == device_id { - matched = true; - } - } else { - matched = true; - } - } - matched - } -} - // Parse the event_monitor file based on the format that each event // is followed by a double newline fn parse_event_file(event_file: &str) -> Vec { @@ -2584,31 +2562,11 @@ fn _test_simple_launch(guest: &Guest) { assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); assert_eq!(guest.get_pci_bridge_class().unwrap_or_default(), "0x060000"); - - let expected_sequential_events = [ - &MetaEvent { - event: "starting".to_string(), - device_id: None, - }, - &MetaEvent { - event: "booting".to_string(), - device_id: None, - }, - &MetaEvent { - event: "booted".to_string(), - device_id: None, - }, - &MetaEvent { - event: "activated".to_string(), - device_id: Some("_disk0".to_string()), - }, - &MetaEvent { - event: "reset".to_string(), - device_id: Some("_disk0".to_string()), - }, - ]; assert!(check_sequential_events( - &expected_sequential_events, + &guest + .get_expected_seq_events_for_simple_launch() + .iter() + .collect::>(), &event_path )); diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 2848a130e5..cab4000828 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -883,6 +883,28 @@ pub fn kill_child(child: &mut Child) { } } +#[derive(Debug)] +pub struct MetaEvent { + pub event: String, + pub device_id: Option, +} + +impl MetaEvent { + pub fn match_with_json_event(&self, v: &serde_json::Value) -> bool { + let mut matched = false; + if v["event"].as_str().unwrap() == self.event { + if let Some(device_id) = &self.device_id { + if v["properties"]["id"].as_str().unwrap() == device_id { + matched = true; + } + } else { + matched = true; + } + } + matched + } +} + pub const PIPE_SIZE: i32 = 32 << 20; pub struct Guest { @@ -1320,6 +1342,37 @@ impl Guest { assert_eq!(self.ssh_command("sudo umount /mnt").unwrap(), ""); } } + + pub fn get_expected_seq_events_for_simple_launch(&self) -> Vec { + let mut out_evt = vec![ + MetaEvent { + event: "starting".to_string(), + device_id: None, + }, + MetaEvent { + event: "booting".to_string(), + device_id: None, + }, + MetaEvent { + event: "booted".to_string(), + device_id: None, + }, + MetaEvent { + event: "activated".to_string(), + device_id: Some("_disk0".to_string()), + }, + ]; + // For confidential VM, reset of the device does not trigger a VMM exit, or + // It is handled in the PSP + // so we won't receive the "reset" event for disk0. + if self.vm_type != GuestVmType::Confidential { + out_evt.push(MetaEvent { + event: "reset".to_string(), + device_id: Some("_disk0".to_string()), + }); + } + out_evt + } } #[derive(Default)] From 60c6242bde142ae2c8e88bfa9f14ebdbff9e78c4 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 22 Feb 2026 22:13:57 -0800 Subject: [PATCH 0615/1893] 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 --- cloud-hypervisor/tests/integration.rs | 2 +- test_infra/src/lib.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index c836f631d4..9838e14972 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2545,7 +2545,7 @@ fn _test_simple_launch(guest: &Guest) { let event_path = temp_event_monitor_path(&guest.tmp_dir); let mut child = GuestCommand::new(guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .default_kernel_cmdline() .default_disks() diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index cab4000828..f45ed3e700 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -916,6 +916,8 @@ pub struct Guest { pub kernel_path: Option, pub kernel_cmdline: Option, pub console_type: Option, + pub num_cpu: u32, + pub nested: bool, } // Return the next id that can be used for this guest. This is stored in a @@ -985,6 +987,8 @@ impl Guest { kernel_path: None, kernel_cmdline: None, console_type: None, + num_cpu: 1u32, + nested: true, } } @@ -1373,6 +1377,14 @@ impl Guest { } out_evt } + + pub fn default_cpus_string(&self) -> String { + format!( + "boot={}{}", + self.num_cpu, + if self.nested { "" } else { ",nested=off" } + ) + } } #[derive(Default)] @@ -1555,6 +1567,10 @@ impl<'a> GuestCommand<'a> { self } + + pub fn default_cpus(&mut self) -> &mut Self { + self.args(["--cpus", self.guest.default_cpus_string().as_str()]) + } } /// Returns the absolute path into the workspaces target directory to locate the desired From 45a5c7a04e38891211f3fd9467d888e0d42dc499 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 22 Feb 2026 22:17:48 -0800 Subject: [PATCH 0616/1893] 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 --- cloud-hypervisor/tests/integration.rs | 2 +- test_infra/src/lib.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 9838e14972..6b18591730 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2559,7 +2559,7 @@ fn _test_simple_launch(guest: &Guest) { let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); + guest.validate_cpu_count(None); assert!(guest.get_total_memory().unwrap_or_default() > 480_000); assert_eq!(guest.get_pci_bridge_class().unwrap_or_default(), "0x060000"); assert!(check_sequential_events( diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index f45ed3e700..d5349b43e0 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1385,6 +1385,14 @@ impl Guest { if self.nested { "" } else { ",nested=off" } ) } + + pub fn validate_cpu_count(&self, expected_cpu_count: Option) { + let cpu = match expected_cpu_count { + Some(count) => count, + None => self.num_cpu, + }; + assert_eq!(self.get_cpu_count().unwrap_or_default(), cpu); + } } #[derive(Default)] From 05aeef06e504dbc3fc063c5abc652329c194f014 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 22 Feb 2026 22:25:32 -0800 Subject: [PATCH 0617/1893] 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 --- cloud-hypervisor/tests/integration.rs | 2 +- test_infra/src/lib.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 6b18591730..7250e8d9f1 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2546,7 +2546,7 @@ fn _test_simple_launch(guest: &Guest) { let mut child = GuestCommand::new(guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .default_kernel_cmdline() .default_disks() .default_net() diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index d5349b43e0..0293249ff1 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -918,6 +918,7 @@ pub struct Guest { pub console_type: Option, pub num_cpu: u32, pub nested: bool, + pub mem_size_str: String, } // Return the next id that can be used for this guest. This is stored in a @@ -989,6 +990,7 @@ impl Guest { console_type: None, num_cpu: 1u32, nested: true, + mem_size_str: "512M".to_string(), } } @@ -1386,6 +1388,10 @@ impl Guest { ) } + pub fn default_memory_string(&self) -> String { + format!("size={}", self.mem_size_str) + } + pub fn validate_cpu_count(&self, expected_cpu_count: Option) { let cpu = match expected_cpu_count { Some(count) => count, @@ -1579,6 +1585,10 @@ impl<'a> GuestCommand<'a> { pub fn default_cpus(&mut self) -> &mut Self { self.args(["--cpus", self.guest.default_cpus_string().as_str()]) } + + pub fn default_memory(&mut self) -> &mut Self { + self.args(["--memory", self.guest.default_memory_string().as_str()]) + } } /// Returns the absolute path into the workspaces target directory to locate the desired From 32edcf39a648636ebec20dea82d347c05dbc4054 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 22 Feb 2026 22:39:10 -0800 Subject: [PATCH 0618/1893] 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 --- cloud-hypervisor/tests/integration.rs | 2 +- test_infra/src/lib.rs | 34 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 7250e8d9f1..804784507b 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2560,7 +2560,7 @@ fn _test_simple_launch(guest: &Guest) { guest.wait_vm_boot().unwrap(); guest.validate_cpu_count(None); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_memory(None); assert_eq!(guest.get_pci_bridge_class().unwrap_or_default(), "0x060000"); assert!(check_sequential_events( &guest diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 0293249ff1..60bf4ece03 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1399,6 +1399,40 @@ impl Guest { }; assert_eq!(self.get_cpu_count().unwrap_or_default(), cpu); } + + fn get_expected_memory(&self) -> Option { + // For confidential VMs, the memory available to the guest is less than + // the memory assigned to the VM, as some of it is reserved for the PSP + // and bounce buffers. + // So we return the expected available memory for confidential VMs here. + let memory = match self.mem_size_str.as_str() { + "512M" => { + if self.vm_type == GuestVmType::Confidential { + 407_000 + } else { + 480_000 + } + } + "1G" => { + if self.vm_type == GuestVmType::Confidential { + 920_000 + } else { + 960_000 + } + } + // More to be added if more memory sizes are used in the tests + _ => panic!("Unsupported memory size: {}", self.mem_size_str), + }; + Some(memory) + } + + pub fn validate_memory(&self, expected_memory: Option) { + let memory = expected_memory + .or_else(|| self.get_expected_memory()) + .unwrap_or_default(); + + assert!(self.get_total_memory().unwrap_or_default() > memory); + } } #[derive(Default)] From af764235a093ac237ced21be74807a36829095d3 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 22 Feb 2026 22:40:48 -0800 Subject: [PATCH 0619/1893] tests: nested not supported for CVM on MSHV Nested on MSHV confident VM not supported yet. Signed-off-by: Muminul Islam --- cloud-hypervisor/tests/integration.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 804784507b..c07ae25faa 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14441,6 +14441,7 @@ mod common_cvm { let mut guest = Guest::new(Box::new(disk_config)); guest.vm_type = GuestVmType::Confidential; guest.boot_timeout = DEFAULT_CVM_TCP_LISTENER_TIMEOUT; + guest.nested = false; _test_simple_launch(&guest) } } From c35bfbd79f5740a9d8f842633c67b931e61d6a84 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 22 Feb 2026 23:14:28 -0800 Subject: [PATCH 0620/1893] tests: use default_cpus() across integration tests Replace hard-coded --cpus boot= 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 --- cloud-hypervisor/tests/integration.rs | 112 +++++++++++++------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index c07ae25faa..e5349f3440 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1174,7 +1174,7 @@ fn _test_power_button(acpi: bool) { direct_kernel_boot_path() }; - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -1635,7 +1635,7 @@ fn _test_virtio_fs( let mut guest_command = GuestCommand::new(&guest); guest_command - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -1837,7 +1837,7 @@ fn test_virtio_pmem(discard_writes: bool, specify_size: bool) { .expect("Expect creating disk image to succeed"); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -1918,7 +1918,7 @@ fn _test_virtio_vsock(hotplug: bool) { let mut cmd = GuestCommand::new(&guest); cmd.args(["--api-socket", &api_socket]); - cmd.args(["--cpus", "boot=1"]); + cmd.default_cpus(); cmd.args(["--memory", "size=512M"]); cmd.args(["--kernel", kernel_path.to_str().unwrap()]); cmd.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]); @@ -1992,7 +1992,7 @@ fn test_memory_mergeable(mergeable: bool) { let disk_config1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest1 = Guest::new(Box::new(disk_config1)); let mut child1 = GuestCommand::new(&guest1) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", format!("size=512M,{memory_param}").as_str()]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -2018,7 +2018,7 @@ fn test_memory_mergeable(mergeable: bool) { let disk_config2 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest2 = Guest::new(Box::new(disk_config2)); let mut child2 = GuestCommand::new(&guest2) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", format!("size=512M,{memory_param}").as_str()]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -2281,7 +2281,7 @@ fn _test_virtio_iommu(acpi: bool) { }; let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -2865,7 +2865,7 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=128G"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -2902,7 +2902,7 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=0,hotplug_method=virtio-mem"]) .args([ "--memory-zone", @@ -2990,7 +2990,7 @@ mod common_parallel { let api_socket = temp_api_path(&guest.tmp_dir); let mut cmd = GuestCommand::new(&guest); - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--api-socket", &api_socket]) .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) @@ -3051,7 +3051,7 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -3088,7 +3088,7 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -3150,7 +3150,7 @@ mod common_parallel { assert!(exec_host_command_status(format!("mkfs.ext4 {test_disk_path}").as_str()).success()); let mut cmd = GuestCommand::new(&guest); - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -3321,7 +3321,7 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -3367,7 +3367,7 @@ mod common_parallel { kernel_path.push("bzImage-x86_64"); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -4456,7 +4456,7 @@ mod common_parallel { ); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -4519,7 +4519,7 @@ mod common_parallel { ); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -4586,7 +4586,7 @@ mod common_parallel { ); let child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -4636,7 +4636,7 @@ mod common_parallel { ); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -4779,7 +4779,7 @@ mod common_parallel { assert_eq!(vhdx_image_size(vhdx_path), EMPTY_VHDX_FILE_SIZE); let mut cloud_child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -4860,7 +4860,7 @@ mod common_parallel { .expect("copying of OS disk failed"); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args([ @@ -4970,7 +4970,7 @@ mod common_parallel { let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -5016,7 +5016,7 @@ mod common_parallel { let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -5052,7 +5052,7 @@ mod common_parallel { let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -5093,7 +5093,7 @@ mod common_parallel { let oem_strings = format!("oem_strings=[{s1},{s2}]"); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -5196,7 +5196,7 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args([ @@ -5251,7 +5251,7 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -5296,7 +5296,7 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -5332,7 +5332,7 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -5374,7 +5374,7 @@ mod common_parallel { #[cfg(target_arch = "aarch64")] let console_str: &str = "console=ttyAMA0"; - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args([ @@ -5430,7 +5430,7 @@ mod common_parallel { let console_str: &str = "console=ttyAMA0"; let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args([ @@ -5488,7 +5488,7 @@ mod common_parallel { let console_str: &str = "console=ttyAMA0"; let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args([ @@ -5557,7 +5557,7 @@ mod common_parallel { let cmdline = DIRECT_KERNEL_BOOT_CMDLINE.to_owned() + serial_option; let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", &cmdline]) @@ -5604,7 +5604,7 @@ mod common_parallel { let cmdline = DIRECT_KERNEL_BOOT_CMDLINE.to_owned() + serial_option; let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", &cmdline]) @@ -5668,7 +5668,7 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -5713,7 +5713,7 @@ mod common_parallel { let console_path = guest.tmp_dir.as_path().join("console-output"); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -6010,7 +6010,7 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args([ @@ -6107,7 +6107,7 @@ mod common_parallel { let kernel_path = edk2_path(); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -6519,7 +6519,7 @@ mod common_parallel { let guest_memory_size_kb = 512 * 1024; let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", format!("size={guest_memory_size_kb}K").as_str()]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -6561,7 +6561,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -6635,7 +6635,7 @@ mod common_parallel { } cmd.args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -6827,7 +6827,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -6942,7 +6942,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -7185,7 +7185,7 @@ mod common_parallel { let loop_dev = create_loop_device(test_disk_path.to_str().unwrap(), 4096, 5); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -8126,7 +8126,7 @@ mod common_parallel { ); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -8342,7 +8342,7 @@ mod common_parallel { //Let's start a 4G guest with balloon occupied 2G memory let mut child = GuestCommand::new(&guest) .args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=4G"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -8396,7 +8396,7 @@ mod common_parallel { //Let's start a 4G guest with balloon occupied 2G memory let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=4G"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -8480,7 +8480,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -8622,7 +8622,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -8840,7 +8840,7 @@ mod common_parallel { let api_socket = temp_api_path(&guest.tmp_dir); let mut cmd = GuestCommand::new(&guest); - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -8966,7 +8966,7 @@ mod common_parallel { let event_path = temp_event_monitor_path(&guest.tmp_dir); let mut cmd = GuestCommand::new(&guest); - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -9049,7 +9049,7 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); let mut cmd = GuestCommand::new(&guest); - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) @@ -9522,7 +9522,7 @@ mod common_parallel { let api_socket = temp_api_path(&guest.tmp_dir); let mut child = GuestCommand::new(&guest) .args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=1G,shared=on,hugepages=on"]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args(["--serial", "tty", "--console", "off"]) @@ -9816,7 +9816,7 @@ mod common_parallel { let mut guest_cmd = GuestCommand::new(&guest); guest_cmd - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=1G"]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args(["--tpm", &format!("socket={swtpm_socket_path}")]) @@ -9865,7 +9865,7 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); - cmd.args(["--cpus", "boot=1"]) + cmd.default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args([ @@ -14020,7 +14020,7 @@ mod aarch64_acpi { let guest = Guest::new(disk_config); let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=1"]) + .default_cpus() .args(["--memory", "size=512M"]) .args(["--kernel", edk2_path().to_str().unwrap()]) .default_disks() From a216cf164f845485a67d1c5a549dde3650f79ddd Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 22 Feb 2026 23:15:43 -0800 Subject: [PATCH 0621/1893] 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 --- cloud-hypervisor/tests/integration.rs | 120 +++++++++++++------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index e5349f3440..0b5b743714 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -995,7 +995,7 @@ fn test_cpu_topology(threads_per_core: u8, cores_per_package: u8, packages: u8, "boot={total_vcpus},topology={threads_per_core}:{cores_per_package}:1:{packages}" ), ]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -1175,7 +1175,7 @@ fn _test_power_button(acpi: bool) { }; cmd.default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .capture_output() @@ -1838,7 +1838,7 @@ fn test_virtio_pmem(discard_writes: bool, specify_size: bool) { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -1919,7 +1919,7 @@ fn _test_virtio_vsock(hotplug: bool) { let mut cmd = GuestCommand::new(&guest); cmd.args(["--api-socket", &api_socket]); cmd.default_cpus(); - cmd.args(["--memory", "size=512M"]); + cmd.default_memory(); cmd.args(["--kernel", kernel_path.to_str().unwrap()]); cmd.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]); cmd.default_disks(); @@ -2282,7 +2282,7 @@ fn _test_virtio_iommu(acpi: bool) { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -2639,7 +2639,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=2,max=4"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .capture_output() @@ -2694,7 +2694,7 @@ mod common_parallel { let max_phys_bits: u8 = 36; let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("max_phys_bits={max_phys_bits}")]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -2740,7 +2740,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=2,affinity=[0@[0,2],1@[1,3]]"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -2781,7 +2781,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -2992,7 +2992,7 @@ mod common_parallel { cmd.default_cpus() .args(["--api-socket", &api_socket]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -3052,7 +3052,7 @@ mod common_parallel { let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .capture_output() @@ -3089,7 +3089,7 @@ mod common_parallel { let guest = Guest::new(Box::new(disk_config)); let mut cmd = GuestCommand::new(&guest); cmd.default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args(["--net", guest.default_net_string_w_mtu(3000).as_str()]) @@ -3151,7 +3151,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -3322,7 +3322,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -3368,7 +3368,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -4457,7 +4457,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -4520,7 +4520,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -4587,7 +4587,7 @@ mod common_parallel { let child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -4637,7 +4637,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -4780,7 +4780,7 @@ mod common_parallel { let mut cloud_child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -4861,7 +4861,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args([ "--disk", @@ -4971,7 +4971,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -5017,7 +5017,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args(["--platform", "serial_number=a=b;c=d"]) @@ -5053,7 +5053,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args(["--platform", "uuid=1e8aa28a-435d-4027-87f4-40dceff1fa0a"]) @@ -5094,7 +5094,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args(["--platform", &oem_strings]) @@ -5197,7 +5197,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args([ "--disk", @@ -5252,7 +5252,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -5297,7 +5297,7 @@ mod common_parallel { let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -5333,7 +5333,7 @@ mod common_parallel { let guest = Guest::new(Box::new(disk_config)); let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -5375,7 +5375,7 @@ mod common_parallel { let console_str: &str = "console=ttyAMA0"; cmd.default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args([ "--cmdline", @@ -5431,7 +5431,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args([ "--cmdline", @@ -5489,7 +5489,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args([ "--cmdline", @@ -5558,7 +5558,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", &cmdline]) .default_disks() @@ -5605,7 +5605,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", &cmdline]) .default_disks() @@ -5669,7 +5669,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -5714,7 +5714,7 @@ mod common_parallel { let console_path = guest.tmp_dir.as_path().join("console-output"); let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -6011,7 +6011,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args([ "--cmdline", @@ -6108,7 +6108,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -6209,7 +6209,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=2,max=4"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args([ "--cmdline", @@ -6562,7 +6562,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--api-socket", &api_socket]) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args(["--landlock"]) @@ -6636,7 +6636,7 @@ mod common_parallel { cmd.args(["--api-socket", &api_socket]) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -6828,7 +6828,7 @@ mod common_parallel { cmd.args(["--api-socket", &api_socket]) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -6943,7 +6943,7 @@ mod common_parallel { cmd.args(["--api-socket", &api_socket]) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -7186,7 +7186,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -7398,7 +7398,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -7925,7 +7925,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -8127,7 +8127,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -8243,7 +8243,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -8481,7 +8481,7 @@ mod common_parallel { cmd.args(["--api-socket", &api_socket]) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -8623,7 +8623,7 @@ mod common_parallel { cmd.args(["--api-socket", &api_socket]) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_net() @@ -8841,7 +8841,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -8967,7 +8967,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -9050,7 +9050,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -9112,7 +9112,7 @@ mod common_parallel { let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("boot={num_queue_pairs}")]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -9237,7 +9237,7 @@ mod common_parallel { let mut guest_command = GuestCommand::new(&guest); guest_command .args(["--cpus", "boot=2"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -9866,7 +9866,7 @@ mod common_parallel { let kernel_path = direct_kernel_boot_path(); cmd.default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args([ "--cmdline", @@ -9915,7 +9915,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=4"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", cmd_line.as_str()]) .default_disks() @@ -10291,7 +10291,7 @@ mod ivshmem { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=2"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -14021,7 +14021,7 @@ mod aarch64_acpi { let mut child = GuestCommand::new(&guest) .default_cpus() - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", edk2_path().to_str().unwrap()]) .default_disks() .default_net() @@ -14395,7 +14395,7 @@ mod fw_cfg { std::fs::write(&test_file, "test-file-content").unwrap(); cmd.args(["--cpus", "boot=4"]) - .args(["--memory", "size=512M"]) + .default_memory() .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", cmd_line]) .default_disks() From b41927dbcc190ee731e302d59f884884f4bdcb35 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sun, 22 Feb 2026 23:23:53 -0800 Subject: [PATCH 0622/1893] 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 --- cloud-hypervisor/tests/integration.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 0b5b743714..5afc6da8e9 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14432,9 +14432,8 @@ mod fw_cfg { #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] mod common_cvm { - use vm_memory::GuestAddress; - use crate::*; + #[test] fn test_focal_simple_launch() { let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); From 5b5e5cb99912c46c5831facac37a279597a987fa Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 27 Feb 2026 01:32:32 -0800 Subject: [PATCH 0623/1893] 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 --- vmm/src/cpu.rs | 58 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 78149c4b55..852850b9fa 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -679,8 +679,7 @@ pub struct CpuManager { hypervisor: Arc, #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, - // TID of the first vCPU thread that created a core scheduling cookie (VM mode). - // 0 = no leader yet, -1 = leader creating cookie, >0 = leader TID (cookie ready). + // State of the core scheduling group leader election (VM mode). core_scheduling_group_leader: Arc, } @@ -692,6 +691,36 @@ const CPU_EJECT_FLAG: usize = 3; const CPU_STATUS_OFFSET: u64 = 4; const CPU_SELECTION_OFFSET: u64 = 0; +/// State of the core scheduling group leader election for VM-wide cookie +/// sharing. +/// +/// The value will be in an `AtomicI32`. Positive values represent a leader +/// TID (cookie ready). +#[repr(i32)] +enum CoreSchedulingLeader { + /// No leader elected yet. + Initial = 0, + /// A leader has been elected and is creating the cookie. + Elected = -1, + /// The leader failed to create the cookie. + Error = -2, +} + +impl TryFrom for CoreSchedulingLeader { + type Error = (); + /// Convert from the raw `i32` (from the `AtomicI32`) value. + /// Quirky: Returns `Ok(state)` for known sentinel values, or `Err(())` for + /// a positive TID (cookie ready). + fn try_from(value: i32) -> result::Result { + match value { + 0 => Ok(Self::Initial), + -1 => Ok(Self::Elected), + -2 => Ok(Self::Error), + _ => Err(()), + } + } +} + impl BusDevice for CpuManager { fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { // The Linux kernel, quite reasonably, doesn't zero the memory it gives us. @@ -924,7 +953,9 @@ impl CpuManager { hypervisor, #[cfg(feature = "sev_snp")] sev_snp_enabled, - core_scheduling_group_leader: Arc::new(AtomicI32::new(0)), + core_scheduling_group_leader: Arc::new(AtomicI32::new( + CoreSchedulingLeader::Initial as i32, + )), }))) } @@ -1210,7 +1241,7 @@ impl CpuManager { // SAFETY: gettid() is always safe to call. let my_tid = unsafe { libc::gettid() }; if core_scheduling_group_leader - .compare_exchange(0, -1, Ordering::AcqRel, Ordering::Acquire) + .compare_exchange(CoreSchedulingLeader::Initial as i32, CoreSchedulingLeader::Elected as i32, Ordering::AcqRel, Ordering::Acquire) .is_ok() { // We are the group leader — create the cookie @@ -1218,6 +1249,8 @@ impl CpuManager { error!( "Failed to create core scheduling cookie: {e:?}" ); + // This will force the loop in the other threads to break out + core_scheduling_group_leader.store(CoreSchedulingLeader::Error as i32, Ordering::Release); return; } // Signal that the cookie is ready by storing real TID @@ -1225,14 +1258,15 @@ impl CpuManager { .store(my_tid, Ordering::Release); } else { // Wait for the leader to finish creating the cookie - let mut leader_tid = - core_scheduling_group_leader.load(Ordering::Acquire); - while leader_tid <= 0 { - std::hint::spin_loop(); - leader_tid = - core_scheduling_group_leader.load(Ordering::Acquire); - } - // Copy the leader's cookie to this thread + let leader_tid = loop { + let v = core_scheduling_group_leader.load(Ordering::Acquire); + match CoreSchedulingLeader::try_from(v) { + Ok(CoreSchedulingLeader::Error) => return, + Ok(CoreSchedulingLeader::Initial | + CoreSchedulingLeader::Elected) => std::hint::spin_loop(), + Err(()) => break v, + } + }; if let Err(e) = core_scheduling_share_from(leader_tid) { error!( "Failed to share core scheduling cookie \ From d1c89a271d47f6ac5862071cc450ba34cb168c9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 23:53:55 +0000 Subject: [PATCH 0624/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 0427708458..81baf1e3af 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.43.5 + - uses: crate-ci/typos@v1.44.0 From a94fa554c32867dab78c00ce0faa75bf4c7cbe94 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 28 Feb 2026 23:51:13 +0100 Subject: [PATCH 0625/1893] 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 --- hypervisor/src/kvm/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 259009151e..75073ec8d5 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -90,11 +90,11 @@ pub use kvm_bindings::kvm_vcpu_events as VcpuEvents; #[cfg(target_arch = "x86_64")] use kvm_bindings::nested::KvmNestedStateBuffer; pub use kvm_bindings::{ - KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_IRQ_ROUTING_IRQCHIP, KVM_IRQ_ROUTING_MSI, - KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID, kvm_clock_data, - kvm_create_device, kvm_create_device as CreateDevice, kvm_device_attr as DeviceAttr, - kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_guest_debug, kvm_irq_routing, kvm_irq_routing_entry, - kvm_mp_state, kvm_run, kvm_userspace_memory_region, + self, KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_IRQ_ROUTING_IRQCHIP, + KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID, + kvm_clock_data, kvm_create_device, kvm_create_device as CreateDevice, + kvm_device_attr as DeviceAttr, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_guest_debug, + kvm_irq_routing, kvm_irq_routing_entry, kvm_mp_state, kvm_run, kvm_userspace_memory_region, }; #[cfg(target_arch = "aarch64")] use kvm_bindings::{ @@ -109,14 +109,13 @@ use kvm_bindings::{KVM_REG_RISCV_CORE, kvm_riscv_core}; use kvm_bindings::{KVM_X86_DEFAULT_VM, KVM_X86_SW_PROTECTED_VM, KVMIO, kvm_run__bindgen_ty_1}; #[cfg(target_arch = "x86_64")] use kvm_bindings::{Xsave as xsave2, kvm_xsave2}; -pub use kvm_ioctls::{Cap, Kvm, VcpuExit}; +pub use kvm_ioctls::{self, Cap, Kvm, VcpuExit}; use thiserror::Error; use vfio_ioctls::VfioDeviceFd; #[cfg(target_arch = "x86_64")] use vmm_sys_util::{fam::FamStruct, ioctl_io_nr}; #[cfg(feature = "tdx")] use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_iowr_nr}; -pub use {kvm_bindings, kvm_ioctls}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use crate::RegList; From 15ce890dd35fafe8154a5549a60c812a495d5c73 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Feb 2026 11:58:32 +0100 Subject: [PATCH 0626/1893] 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 --- block/src/lib.rs | 67 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 3ab8de9e41..3cbe9fe52c 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -38,7 +38,7 @@ use std::os::unix::io::AsRawFd; use std::path::Path; use std::str::FromStr; use std::time::Instant; -use std::{cmp, result}; +use std::{cmp, mem, result}; #[cfg(feature = "io_uring")] use io_uring::{IoUring, Probe, opcode}; @@ -1161,8 +1161,73 @@ impl DiskTopology { Ok(block_size) } + /// Query the O_DIRECT alignment requirement for a regular file. + /// + /// Uses `statx(STATX_DIOALIGN)` (Linux >= 6.1) to obtain the exact + /// memory and offset alignment the kernel requires for direct I/O on + /// this specific file. Unlike `fstatvfs().f_bsize`, which only returns + /// the filesystem's preferred I/O block size, `STATX_DIOALIGN` reports + /// the true per-file DIO constraints accounting for the filesystem, + /// underlying block device, and any stacking (loop, dm, etc.). + fn query_file_alignment(f: &File) -> u64 { + // The libc crate does not expose statx / STATX_DIOALIGN on all + // targets (e.g. musl), so define the constant and a minimal repr(C) + // struct locally and invoke the syscall directly. + const STATX_DIOALIGN: u32 = 0x2000; + + // Minimal statx layout, only the needed fields, + // everything else is padding. + #[repr(C)] + struct Statx { + stx_mask: u32, + _pad: [u8; 148], + stx_dio_mem_align: u32, + stx_dio_offset_align: u32, + _pad2: [u8; 96], + } + + let mut stx = mem::MaybeUninit::::zeroed(); + // SAFETY: FFI syscall with valid fd and correctly sized buffer. + let ret = unsafe { + libc::syscall( + libc::SYS_statx, + f.as_raw_fd(), + c"".as_ptr(), + libc::AT_EMPTY_PATH, + STATX_DIOALIGN, + stx.as_mut_ptr(), + ) + }; + if ret == 0 { + // SAFETY: statx succeeded, the struct is fully initialized. + let stx = unsafe { stx.assume_init() }; + if stx.stx_mask & STATX_DIOALIGN != 0 && stx.stx_dio_mem_align > 0 { + let align = cmp::max(stx.stx_dio_mem_align, stx.stx_dio_offset_align) as u64; + debug!("statx(STATX_DIOALIGN) returned alignment {align}"); + return align; + } + } + + debug!("O_DIRECT alignment query failed, falling back to default {SECTOR_SIZE}"); + SECTOR_SIZE + } + pub fn probe(f: &File) -> std::io::Result { if !Self::is_block_device(f)? { + // For regular files opened with O_DIRECT, the logical block size + // must reflect the filesystem DIO alignment so the guest issues + // correctly sized I/O. + // SAFETY: fcntl(F_GETFL) is always safe on a valid fd. + let flags = unsafe { libc::fcntl(f.as_raw_fd(), libc::F_GETFL) }; + if flags >= 0 && (flags & libc::O_DIRECT) != 0 { + let alignment = Self::query_file_alignment(f); + return Ok(DiskTopology { + logical_block_size: alignment, + physical_block_size: alignment, + minimum_io_size: alignment, + optimal_io_size: 0, + }); + } return Ok(DiskTopology::default()); } From 496c89c2892105fdc0ebf6c0cac8f9ccf9d632f6 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Feb 2026 12:20:15 +0100 Subject: [PATCH 0627/1893] 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 --- block/src/lib.rs | 126 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/block/src/lib.rs b/block/src/lib.rs index 3cbe9fe52c..f8d56cf102 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -1239,3 +1239,129 @@ impl DiskTopology { }) } } + +#[cfg(test)] +mod unit_tests { + use std::alloc::{Layout, alloc_zeroed, dealloc}; + use std::fs::OpenOptions; + use std::io::Write; + use std::os::unix::fs::OpenOptionsExt; + use std::{ptr, slice}; + + use vmm_sys_util::tempfile::TempFile; + + use super::*; + + #[test] + fn test_probe_regular_file_returns_valid_alignment() { + let temp_file = TempFile::new().unwrap(); + let mut f = temp_file.into_file(); + f.write_all(&[0u8; 4096]).unwrap(); + f.sync_all().unwrap(); + + let topo = DiskTopology::probe(&f).unwrap(); + + assert_eq!( + topo.logical_block_size, SECTOR_SIZE, + "probe() should return {SECTOR_SIZE} for regular files without O_DIRECT, got {}", + topo.logical_block_size + ); + } + + #[test] + fn test_probe_regular_file_with_direct_returns_dio_alignment() { + let temp_file = TempFile::new().unwrap(); + let path = temp_file.as_path().to_owned(); + { + let f = temp_file.as_file(); + f.set_len(1 << 20).unwrap(); // 1 MiB + f.sync_all().unwrap(); + } + + let f = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(libc::O_DIRECT) + .open(&path) + .unwrap(); + let topo = DiskTopology::probe(&f).unwrap(); + + assert!( + topo.logical_block_size.is_power_of_two(), + "logical_block_size {} is not a power of two", + topo.logical_block_size + ); + assert!( + topo.logical_block_size >= SECTOR_SIZE, + "logical_block_size {} is less than SECTOR_SIZE ({SECTOR_SIZE})", + topo.logical_block_size + ); + + let alignment = topo.logical_block_size as usize; + let layout = Layout::from_size_align(4096, alignment); + assert!( + layout.is_ok(), + "Layout::from_size_align(4096, {alignment}) failed: {:?}", + layout.err() + ); + } + + #[test] + fn test_dio_write_read_with_probed_alignment() { + let temp_file = TempFile::new().unwrap(); + let path = temp_file.as_path().to_owned(); + { + let f = temp_file.as_file(); + f.set_len(1 << 20).unwrap(); // 1 MiB + f.sync_all().unwrap(); + } + + let f = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(libc::O_DIRECT) + .open(&path) + .unwrap(); + let topo = DiskTopology::probe(&f).unwrap(); + let alignment = topo.logical_block_size as usize; + + let layout = Layout::from_size_align(alignment, alignment).unwrap(); + // SAFETY: layout is valid (non-zero, power-of-two alignment). + let buf = unsafe { alloc_zeroed(layout) }; + assert!(!buf.is_null()); + + // SAFETY: buf is valid for `alignment` bytes. + unsafe { ptr::write_bytes(buf, 0xAB, alignment) }; + + // SAFETY: buf is aligned and sized for O_DIRECT; fd is valid. + let written = + unsafe { libc::pwrite(f.as_raw_fd(), buf as *const libc::c_void, alignment, 0) }; + assert_eq!( + written as usize, + alignment, + "O_DIRECT pwrite failed: {}", + io::Error::last_os_error() + ); + + // SAFETY: buf is valid for `alignment` bytes. + unsafe { ptr::write_bytes(buf, 0x00, alignment) }; + // SAFETY: buf is aligned and sized for O_DIRECT; fd is valid. + let read = unsafe { libc::pread(f.as_raw_fd(), buf as *mut libc::c_void, alignment, 0) }; + assert_eq!( + read as usize, + alignment, + "O_DIRECT pread failed: {}", + io::Error::last_os_error() + ); + + // SAFETY: buf is valid for `alignment` bytes after successful pread. + let slice = unsafe { slice::from_raw_parts(buf, alignment) }; + assert!( + slice.iter().all(|&b| b == 0xAB), + "Data mismatch after O_DIRECT roundtrip" + ); + + // SAFETY: buf was allocated with this layout via alloc_zeroed. + unsafe { dealloc(buf, layout) }; + } +} From ba889a6ec26bf88d73b4dcc52f8f909f06ab7bed Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Feb 2026 23:29:22 +0100 Subject: [PATCH 0628/1893] 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 --- cloud-hypervisor/tests/integration.rs | 95 ++++++++++++++------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5afc6da8e9..05b25d3e9b 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -20,7 +20,7 @@ use std::process::{Child, Command, Stdio}; use std::string::String; use std::sync::mpsc::Receiver; use std::sync::{Mutex, mpsc}; -use std::time::Duration; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{fs, io, thread}; use net_util::MacAddr; @@ -7095,24 +7095,6 @@ mod common_parallel { .open(LOOP_CTL_PATH) .unwrap(); - // Request a free loop device - let loop_device_number = - unsafe { libc::ioctl(loop_ctl_file.as_raw_fd(), LOOP_CTL_GET_FREE as _) }; - - if loop_device_number < 0 { - panic!("Couldn't find a free loop device"); - } - - // Create loop device path - let loop_device_path = format!("{LOOP_DEVICE_PREFIX}{loop_device_number}"); - - // Open loop device - let loop_device_file = OpenOptions::new() - .read(true) - .write(true) - .open(&loop_device_path) - .unwrap(); - // Open backing file let backing_file = OpenOptions::new() .read(true) @@ -7120,13 +7102,34 @@ mod common_parallel { .open(backing_file_path) .unwrap(); - let loop_config = LoopConfig { - fd: backing_file.as_raw_fd() as u32, - block_size, - ..Default::default() - }; - + // Retry the whole get free -> open -> configure sequence so that a + // race with another parallel test claiming the same loop device + // is resolved by requesting a new free device on each attempt. + let mut loop_device_path = String::new(); for i in 0..num_retries { + // Request a free loop device + let loop_device_number = + unsafe { libc::ioctl(loop_ctl_file.as_raw_fd(), LOOP_CTL_GET_FREE as _) }; + + if loop_device_number < 0 { + panic!("Couldn't find a free loop device"); + } + + loop_device_path = format!("{LOOP_DEVICE_PREFIX}{loop_device_number}"); + + // Open loop device + let loop_device_file = OpenOptions::new() + .read(true) + .write(true) + .open(&loop_device_path) + .unwrap(); + + let loop_config = LoopConfig { + fd: backing_file.as_raw_fd() as u32, + block_size, + ..Default::default() + }; + let ret = unsafe { libc::ioctl( loop_device_file.as_raw_fd(), @@ -7134,28 +7137,32 @@ mod common_parallel { &loop_config, ) }; - if ret != 0 { - if i < num_retries - 1 { - println!( - "Iteration {}: Failed to configure the loop device {}: {}", - i, - loop_device_path, - std::io::Error::last_os_error() - ); - } else { - panic!( - "Failed {} times trying to configure the loop device {}: {}", - num_retries, - loop_device_path, - std::io::Error::last_os_error() - ); - } - } else { + if ret == 0 { break; } - // Wait for a bit before retrying - thread::sleep(std::time::Duration::new(5, 0)); + if i < num_retries - 1 { + println!( + "Iteration {}: Failed to configure loop device {}: {}", + i, + loop_device_path, + io::Error::last_os_error() + ); + let jitter_ms = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .subsec_nanos() + % 500 + + 100; + thread::sleep(Duration::from_millis(jitter_ms as u64)); + } else { + panic!( + "Failed {} times trying to configure the loop device {}: {}", + num_retries, + loop_device_path, + io::Error::last_os_error() + ); + } } loop_device_path From 6fe3f63d270a542e0adc3fa75b93f50574816676 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Feb 2026 23:37:23 +0100 Subject: [PATCH 0629/1893] 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 --- cloud-hypervisor/tests/integration.rs | 120 ++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 05b25d3e9b..988f9df566 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7261,6 +7261,126 @@ mod common_parallel { .expect("loop device not found"); } + #[test] + fn test_virtio_block_direct_io_file_backed_alignment_4k() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let mut workloads_path = dirs::home_dir().unwrap(); + workloads_path.push("workloads"); + let img_dir = TempDir::new_in(workloads_path.as_path()).unwrap(); + let fs_img_path = img_dir.as_path().join("fs_4ksec.img"); + + assert!( + exec_host_command_output(&format!( + "truncate -s 512M {}", + fs_img_path.to_str().unwrap() + )) + .status + .success(), + "truncate failed" + ); + + let loop_dev = exec_host_command_output(&format!( + "losetup --find --show --sector-size 4096 {}", + fs_img_path.to_str().unwrap() + )); + assert!(loop_dev.status.success(), "losetup failed"); + let loop_dev_path = String::from_utf8_lossy(&loop_dev.stdout).trim().to_string(); + + assert!( + exec_host_command_output(&format!("mkfs.ext4 -q {loop_dev_path}")) + .status + .success(), + "mkfs.ext4 failed" + ); + + let mnt_dir = img_dir.as_path().join("mnt"); + fs::create_dir_all(&mnt_dir).unwrap(); + assert!( + exec_host_command_output(&format!( + "mount {} {}", + &loop_dev_path, + mnt_dir.to_str().unwrap() + )) + .status + .success(), + "mount failed" + ); + + let test_disk_path = mnt_dir.join("dio_file_test.raw"); + assert!( + exec_host_command_output(&format!( + "truncate -s 64M {}", + test_disk_path.to_str().unwrap() + )) + .status + .success(), + "truncate test disk failed" + ); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!( + "path={},direct=on,image_type=raw", + test_disk_path.to_str().unwrap() + ) + .as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + let log_sec: u32 = guest + .ssh_command("lsblk -t | grep vdc | awk '{print $6}'") + .unwrap() + .trim() + .parse() + .unwrap_or_default(); + assert_eq!( + log_sec, 4096, + "expected 4096-byte logical sector for file on 4k-sector fs, got {log_sec}" + ); + + guest + .ssh_command( + "sudo dd if=/dev/urandom of=/tmp/pattern bs=4096 count=8 && \ + sudo dd if=/tmp/pattern of=/dev/vdc bs=4096 count=8 seek=1 oflag=direct && \ + sudo dd if=/dev/vdc of=/tmp/readback bs=4096 count=8 skip=1 iflag=direct && \ + cmp /tmp/pattern /tmp/readback", + ) + .unwrap(); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + + let _ = exec_host_command_output(&format!("umount {}", mnt_dir.to_str().unwrap())); + let _ = exec_host_command_output(&format!("losetup -d {loop_dev_path}")); + } + // Helper function to verify sparse file fn verify_sparse_file(test_disk_path: &str, expected_ratio: f64) { let res = exec_host_command_output(&format!("ls -s --block-size=1 {}", test_disk_path)); From aae7594a671af171f9963c2d9c98e5d9dd756155 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Feb 2026 23:38:08 +0100 Subject: [PATCH 0630/1893] 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 --- cloud-hypervisor/tests/integration.rs | 86 +++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 988f9df566..3cdf1ba8ea 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7261,6 +7261,92 @@ mod common_parallel { .expect("loop device not found"); } + #[test] + fn test_virtio_block_direct_io_block_device_alignment_4k() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + // The backing file for the loop device must live on a filesystem that + // supports O_DIRECT (e.g. ext4). guest.tmp_dir is on tmpfs inside + // Docker, and the loop driver forwards I/O to the backing file. + let mut workloads_path = dirs::home_dir().unwrap(); + workloads_path.push("workloads"); + let img_dir = TempDir::new_in(workloads_path.as_path()).unwrap(); + let test_disk_path = img_dir.as_path().join("directio_test.img"); + // Preallocate the backing file -- a sparse file can deadlock when + // O_DIRECT writes through a loop device trigger block allocation + // in the backing filesystem. + assert!( + exec_host_command_output(&format!( + "fallocate -l 64M {}", + test_disk_path.to_str().unwrap() + )) + .status + .success(), + "fallocate failed" + ); + + let loop_dev = create_loop_device(test_disk_path.to_str().unwrap(), 4096, 5); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=1"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={},direct=on,image_type=raw", &loop_dev).as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("lsblk -t | grep vdc | awk '{print $6}'") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4096 + ); + + guest + .ssh_command( + "sudo dd if=/dev/urandom of=/tmp/pattern bs=4096 count=1 && \ + sudo dd if=/tmp/pattern of=/dev/vdc bs=4096 count=1 seek=1 oflag=direct && \ + sudo dd if=/dev/vdc of=/tmp/readback bs=4096 count=1 skip=1 iflag=direct && \ + cmp /tmp/pattern /tmp/readback", + ) + .unwrap(); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + + Command::new("losetup") + .args(["-d", &loop_dev]) + .output() + .expect("loop device cleanup failed"); + } + #[test] fn test_virtio_block_direct_io_file_backed_alignment_4k() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); From 511e682909cf29ac8d4b390822b0ecafd0aa80d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 01:12:32 +0000 Subject: [PATCH 0631/1893] 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] --- Cargo.lock | 51 +++++++++++++++++++++++----------------------- Cargo.toml | 4 ++-- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 16 +++++++-------- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea880ebc97..83ef1dc8c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1136,9 +1136,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e3d65f018c6ae946ab16e80944b97096ed73c35b221d1c478a6c81d8f57940" +checksum = "819b44bc7c87d9117eb522f14d46e918add69ff12713c475946b0a29363ed1c2" dependencies = [ "jiff-static", "log", @@ -1149,9 +1149,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17c2b211d863c7fde02cbea8a3c1a439b98e109286554f2860bdded7ff83818" +checksum = "470252db18ecc35fd766c0891b1e3ec6cbbcd62507e85276c01bf75d8e94d4a1" dependencies = [ "proc-macro2", "quote", @@ -1232,11 +1232,10 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.11.0", "libc", ] @@ -1256,9 +1255,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" dependencies = [ "cc", "libc", @@ -1590,15 +1589,15 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand", @@ -1891,9 +1890,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "remain" @@ -2014,9 +2013,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" dependencies = [ "serde_core", "serde_with_macros", @@ -2024,9 +2023,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" dependencies = [ "darling", "proc-macro2", @@ -2128,12 +2127,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.60.2", @@ -3027,18 +3026,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e34941f358..a3e76a797e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ igvm_defs = "0.4.0" # serde crates serde = "1.0.228" serde_json = "1.0.149" -serde_with = { version = "3.16.1", default-features = false } +serde_with = { version = "3.17.0", default-features = false } # other crates anyhow = "1.0.102" @@ -90,7 +90,7 @@ signal-hook = "0.4.3" thiserror = "2.0.18" uuid = { version = "1.21.0" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.39", default-features = false } +zerocopy = { version = "0.8.40", default-features = false } [workspace.lints.clippy] # Any clippy lint (group) in alphabetical order: diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 0414f13d2a..d9ce839882 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.39", features = [ +zerocopy = { version = "0.8.40", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 52cf0e2286..6d98900f31 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1091,9 +1091,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" dependencies = [ "serde_core", "serde_with_macros", @@ -1101,9 +1101,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" dependencies = [ "darling", "proc-macro2", @@ -1778,18 +1778,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", From 838a4f86c465b733eeb48a24f4d52251f7bc8ef2 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Feb 2026 14:14:13 -0800 Subject: [PATCH 0632/1893] 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 --- cloud-hypervisor/tests/integration.rs | 88 --------------------------- test_infra/src/lib.rs | 88 +++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 88 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 3cdf1ba8ea..0866249e07 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -29,69 +29,6 @@ use vmm_sys_util::tempdir::TempDir; use vmm_sys_util::tempfile::TempFile; use wait_timeout::ChildExt; -// Constant taken from the VMM crate. -const MAX_NUM_PCI_SEGMENTS: u16 = 96; - -#[cfg(target_arch = "x86_64")] -mod x86_64 { - pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw"; - pub const JAMMY_VFIO_IMAGE_NAME: &str = - "jammy-server-cloudimg-amd64-custom-vfio-20241012-0.raw"; - pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-amd64-custom-20210609-0.vhd"; - pub const FOCAL_IMAGE_NAME_VHDX: &str = "focal-server-cloudimg-amd64-custom-20210609-0.vhdx"; - pub const JAMMY_IMAGE_NAME: &str = "jammy-server-cloudimg-amd64-custom-20241017-0.raw"; - pub const JAMMY_IMAGE_NAME_QCOW2: &str = "jammy-server-cloudimg-amd64-custom-20241017-0.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_ZLIB: &str = - "jammy-server-cloudimg-amd64-custom-20241017-0-zlib.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_ZSTD: &str = - "jammy-server-cloudimg-amd64-custom-20241017-0-zstd.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE: &str = - "jammy-server-cloudimg-amd64-custom-20241017-0-backing-zstd.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE: &str = - "jammy-server-cloudimg-amd64-custom-20241017-0-backing-uncompressed.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE: &str = - "jammy-server-cloudimg-amd64-custom-20241017-0-backing-raw.qcow2"; - pub const WINDOWS_IMAGE_NAME: &str = "windows-server-2022-amd64-2.raw"; - pub const OVMF_NAME: &str = "CLOUDHV.fd"; - pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'IO-APIC.*ttyS0' /proc/interrupts || true"; -} - -#[cfg(target_arch = "x86_64")] -use x86_64::*; - -#[cfg(target_arch = "aarch64")] -mod aarch64 { - pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-0.raw"; - pub const FOCAL_IMAGE_UPDATE_KERNEL_NAME: &str = - "focal-server-cloudimg-arm64-custom-20210929-0-update-kernel.raw"; - pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-arm64-custom-20210929-0.vhd"; - pub const FOCAL_IMAGE_NAME_VHDX: &str = "focal-server-cloudimg-arm64-custom-20210929-0.vhdx"; - pub const JAMMY_IMAGE_NAME: &str = "jammy-server-cloudimg-arm64-custom-20220329-0.raw"; - pub const JAMMY_IMAGE_NAME_QCOW2: &str = "jammy-server-cloudimg-arm64-custom-20220329-0.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_ZLIB: &str = - "jammy-server-cloudimg-arm64-custom-20220329-0-zlib.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_ZSTD: &str = - "jammy-server-cloudimg-arm64-custom-20220329-0-zstd.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE: &str = - "jammy-server-cloudimg-arm64-custom-20220329-0-backing-zstd.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE: &str = - "jammy-server-cloudimg-arm64-custom-20220329-0-backing-uncompressed.qcow2"; - pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE: &str = - "jammy-server-cloudimg-arm64-custom-20220329-0-backing-raw.qcow2"; - pub const WINDOWS_IMAGE_NAME: &str = "windows-11-iot-enterprise-aarch64.raw"; - pub const OVMF_NAME: &str = "CLOUDHV_EFI.fd"; - pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'GICv3.*uart-pl011' /proc/interrupts || true"; - pub const GREP_PMU_IRQ_CMD: &str = "grep -c 'GICv3.*arm-pmu' /proc/interrupts || true"; -} - -#[cfg(target_arch = "aarch64")] -use aarch64::*; - -const DIRECT_KERNEL_BOOT_CMDLINE: &str = - "root=/dev/vda1 console=hvc0 rw systemd.journald.forward_to_console=1"; - -const CONSOLE_TEST_STRING: &str = "Started OpenBSD Secure Shell server"; - // This enum exists to make it more convenient to // implement test for both D-Bus and REST APIs. enum TargetApi { @@ -541,31 +478,6 @@ fn temp_vmcore_file_path(tmp_dir: &TempDir) -> String { String::from(tmp_dir.as_path().join("vmcore").to_str().unwrap()) } -// Creates the path for direct kernel boot and return the path. -// For x86_64, this function returns the vmlinux kernel path. -// For AArch64, this function returns the PE kernel path. -fn direct_kernel_boot_path() -> PathBuf { - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - - let mut kernel_path = workload_path; - #[cfg(target_arch = "x86_64")] - kernel_path.push("vmlinux-x86_64"); - #[cfg(target_arch = "aarch64")] - kernel_path.push("Image-arm64"); - - kernel_path -} - -fn edk2_path() -> PathBuf { - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - let mut edk2_path = workload_path; - edk2_path.push(OVMF_NAME); - - edk2_path -} - fn cloud_hypervisor_release_path() -> String { let mut workload_path = dirs::home_dir().unwrap(); workload_path.push("workloads"); diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 60bf4ece03..f2b7bb9fbb 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -2050,3 +2050,91 @@ fn generate_host_data() -> String { rand::rng().fill_bytes(&mut bytes); bytes.iter().map(|b| format!("{b:02x}")).collect() } + +// Creates the path for direct kernel boot and return the path. +// For x86_64, this function returns the vmlinux kernel path. +// For AArch64, this function returns the PE kernel path. +pub fn direct_kernel_boot_path() -> PathBuf { + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + + let mut kernel_path = workload_path; + #[cfg(target_arch = "x86_64")] + kernel_path.push("vmlinux-x86_64"); + #[cfg(target_arch = "aarch64")] + kernel_path.push("Image-arm64"); + + kernel_path +} + +pub fn edk2_path() -> PathBuf { + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + let mut edk2_path = workload_path; + edk2_path.push(OVMF_NAME); + + edk2_path +} + +pub const DIRECT_KERNEL_BOOT_CMDLINE: &str = + "root=/dev/vda1 console=hvc0 rw systemd.journald.forward_to_console=1"; + +pub const CONSOLE_TEST_STRING: &str = "Started OpenBSD Secure Shell server"; + +// Constant taken from the VMM crate. +pub const MAX_NUM_PCI_SEGMENTS: u16 = 96; + +#[cfg(target_arch = "x86_64")] +pub mod x86_64 { + pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw"; + pub const JAMMY_VFIO_IMAGE_NAME: &str = + "jammy-server-cloudimg-amd64-custom-vfio-20241012-0.raw"; + pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-amd64-custom-20210609-0.vhd"; + pub const FOCAL_IMAGE_NAME_VHDX: &str = "focal-server-cloudimg-amd64-custom-20210609-0.vhdx"; + pub const JAMMY_IMAGE_NAME: &str = "jammy-server-cloudimg-amd64-custom-20241017-0.raw"; + pub const JAMMY_IMAGE_NAME_QCOW2: &str = "jammy-server-cloudimg-amd64-custom-20241017-0.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_ZLIB: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-zlib.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_ZSTD: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-zstd.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-backing-zstd.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-backing-uncompressed.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE: &str = + "jammy-server-cloudimg-amd64-custom-20241017-0-backing-raw.qcow2"; + pub const WINDOWS_IMAGE_NAME: &str = "windows-server-2022-amd64-2.raw"; + pub const OVMF_NAME: &str = "CLOUDHV.fd"; + pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'IO-APIC.*ttyS0' /proc/interrupts || true"; +} + +#[cfg(target_arch = "x86_64")] +pub use x86_64::*; + +#[cfg(target_arch = "aarch64")] +pub mod aarch64 { + pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-0.raw"; + pub const FOCAL_IMAGE_UPDATE_KERNEL_NAME: &str = + "focal-server-cloudimg-arm64-custom-20210929-0-update-kernel.raw"; + pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-arm64-custom-20210929-0.vhd"; + pub const FOCAL_IMAGE_NAME_VHDX: &str = "focal-server-cloudimg-arm64-custom-20210929-0.vhdx"; + pub const JAMMY_IMAGE_NAME: &str = "jammy-server-cloudimg-arm64-custom-20220329-0.raw"; + pub const JAMMY_IMAGE_NAME_QCOW2: &str = "jammy-server-cloudimg-arm64-custom-20220329-0.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_ZLIB: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-zlib.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_ZSTD: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-zstd.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-backing-zstd.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-backing-uncompressed.qcow2"; + pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE: &str = + "jammy-server-cloudimg-arm64-custom-20220329-0-backing-raw.qcow2"; + pub const WINDOWS_IMAGE_NAME: &str = "windows-11-iot-enterprise-aarch64.raw"; + pub const OVMF_NAME: &str = "CLOUDHV_EFI.fd"; + pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'GICv3.*uart-pl011' /proc/interrupts || true"; + pub const GREP_PMU_IRQ_CMD: &str = "grep -c 'GICv3.*arm-pmu' /proc/interrupts || true"; +} + +#[cfg(target_arch = "aarch64")] +pub use aarch64::*; From f2d3c17e1fa960e934692273fec1954f376b526c Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Feb 2026 14:33:05 -0800 Subject: [PATCH 0633/1893] 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 --- test_infra/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index f2b7bb9fbb..345aa3c250 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -985,8 +985,8 @@ impl Guest { network, vm_type: GuestVmType::Regular, boot_timeout: DEFAULT_TCP_LISTENER_TIMEOUT, - kernel_path: None, - kernel_cmdline: None, + kernel_path: direct_kernel_boot_path().to_str().map(String::from), + kernel_cmdline: Some(DIRECT_KERNEL_BOOT_CMDLINE.to_string()), console_type: None, num_cpu: 1u32, nested: true, From 85159c9255b35c990fb636e68cd4b7d4ca953b84 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Feb 2026 18:17:40 -0800 Subject: [PATCH 0634/1893] 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 --- test_infra/src/lib.rs | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 345aa3c250..1ba5ceccc0 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -998,6 +998,21 @@ impl Guest { Self::new_from_ip_range(disk_config, "192.168", next_guest_id()) } + pub fn with_cpu(mut self, count: u32) -> Self { + self.num_cpu = count; + self + } + + pub fn with_memory(mut self, mem_size: &str) -> Self { + self.mem_size_str = mem_size.to_string(); + self + } + + pub fn with_nested(mut self, nested: bool) -> Self { + self.nested = nested; + self + } + pub fn default_net_string(&self) -> String { format!( "tap=,mac={},ip={},mask=255.255.255.128", @@ -1435,6 +1450,45 @@ impl Guest { } } +// A factory for creating guests with different configurations. The factory is initialized +// with a GuestVmType, and created guests will have the same GuestVmType as the factory. +// This allows creation of guests with different configurations (e.g. regular vs confidential) +// without specifying the GuestVmType each time. +// Based on the VmType, the default timeout for waiting for the VM to boot is also set, +// which is used in the wait_vm_boot() method of the Guest struct. Additionally, nested +// virtualization is disabled by default for confidential VMs, as it is not supported. +pub struct GuestFactory { + vm_type: GuestVmType, + boot_timeout: u32, + nested: bool, +} + +impl GuestFactory { + pub fn new_regular_guest_factory() -> Self { + Self { + vm_type: GuestVmType::Regular, + boot_timeout: DEFAULT_TCP_LISTENER_TIMEOUT, + nested: true, + } + } + + pub fn new_confidential_guest_factory() -> Self { + Self { + vm_type: GuestVmType::Confidential, + boot_timeout: DEFAULT_CVM_TCP_LISTENER_TIMEOUT, + nested: false, + } + } + + pub fn create_guest(&self, disk_config: Box) -> Guest { + let mut guest = Guest::new(disk_config); + guest.vm_type = self.vm_type; + guest.boot_timeout = self.boot_timeout; + guest.nested = self.nested; + guest + } +} + #[derive(Default)] pub enum VerbosityLevel { #[default] From f443bae9d9a12c2cc7903c9c295ecf1bd721e3b7 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Feb 2026 16:02:59 -0800 Subject: [PATCH 0635/1893] 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 --- test_infra/src/lib.rs | 57 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 1ba5ceccc0..27b608c987 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1084,16 +1084,50 @@ impl Guest { } pub fn api_create_body(&self, cpu_count: u8, kernel_path: &str, kernel_cmd: &str) -> String { - format! {"{{\"cpus\":{{\"boot_vcpus\":{},\"max_vcpus\":{}}},\"payload\":{{\"kernel\":\"{}\",\"cmdline\": \"{}\"}},\"net\":[{{\"ip\":\"{}\", \"mask\":\"255.255.255.0\", \"mac\":\"{}\"}}], \"disks\":[{{\"path\":\"{}\"}}, {{\"path\":\"{}\"}}]}}", - cpu_count, - cpu_count, - kernel_path, - kernel_cmd, - self.network.host_ip0, - self.network.guest_mac0, - self.disk_config.disk(DiskType::OperatingSystem).unwrap().as_str(), - self.disk_config.disk(DiskType::CloudInit).unwrap().as_str(), + let mut body = serde_json::json!({ + "cpus": { + "boot_vcpus": cpu_count, + "max_vcpus": cpu_count, + }, + "net": [ + { + "ip": self.network.host_ip0, + "mask": "255.255.255.0", + "mac": self.network.guest_mac0, + } + ], + "disks": [ + { + "path": self.disk_config.disk(DiskType::OperatingSystem).unwrap(), + }, + { + "path": self.disk_config.disk(DiskType::CloudInit).unwrap(), + } + ] + }); + + if !self.nested { + body["cpus"]["nested"] = serde_json::json!(false); } + + if self.vm_type == GuestVmType::Confidential { + body["platform"] = serde_json::json!({"sev_snp": true}); + body["payload"] = serde_json::json!({ + "igvm": direct_igvm_boot_path(Some("hvc0")) + .unwrap() + .to_str() + .unwrap(), + "cmdline": kernel_cmd, + "host_data": generate_host_data(), + }); + } else { + body["payload"] = serde_json::json!({ + "kernel": kernel_path, + "cmdline": kernel_cmd, + }); + } + + body.to_string() } pub fn get_cpu_count(&self) -> Result { @@ -1656,7 +1690,10 @@ impl<'a> GuestCommand<'a> { }; let igvm = direct_igvm_boot_path(Some(console_str)) .expect("IGVM boot file not found for console type: {console_str}"); - self.command.args(["--igvm", igvm.to_str().unwrap()]); + self.command.args([ + "--igvm", + igvm.to_str().expect("IGVM path is not valid UTF-8"), + ]); self.command .args(["--host-data", generate_host_data().as_str()]); self.command.args(["--platform", "sev_snp=on"]); From 7a0019514f8d0d92178bc85a40faa22c969ea2ae Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Feb 2026 19:14:19 -0800 Subject: [PATCH 0636/1893] 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 --- cloud-hypervisor/tests/integration.rs | 105 ++++++++++++-------------- test_infra/src/lib.rs | 12 +-- 2 files changed, 55 insertions(+), 62 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 0866249e07..b3613067e2 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -109,12 +109,7 @@ fn _test_api_create_boot(target_api: &TargetApi, guest: &Guest) { assert!(target_api.remote_command("ping", None)); // Create the VM first - let cpu_count: u8 = 4; - let request_body = guest.api_create_body( - cpu_count, - direct_kernel_boot_path().to_str().unwrap(), - DIRECT_KERNEL_BOOT_CMDLINE, - ); + let request_body = guest.api_create_body(); let temp_config_path = guest.tmp_dir.as_path().join("config"); std::fs::write(&temp_config_path, request_body).unwrap(); @@ -124,12 +119,12 @@ fn _test_api_create_boot(target_api: &TargetApi, guest: &Guest) { // Then boot it assert!(target_api.remote_command("boot", None)); - thread::sleep(std::time::Duration::new(20, 0)); let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected - assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_cpu_count(None); + guest.validate_memory(None); }); kill_child(&mut child); @@ -154,12 +149,7 @@ fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { assert!(target_api.remote_command("ping", None)); // Create the VM first - let cpu_count: u8 = 4; - let request_body = guest.api_create_body( - cpu_count, - direct_kernel_boot_path().to_str().unwrap(), - DIRECT_KERNEL_BOOT_CMDLINE, - ); + let request_body = guest.api_create_body(); let temp_config_path = guest.tmp_dir.as_path().join("config"); std::fs::write(&temp_config_path, request_body).unwrap(); @@ -174,8 +164,8 @@ fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected - assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_cpu_count(None); + guest.validate_memory(None); // Sync and shutdown without powering off to prevent filesystem // corruption. @@ -194,8 +184,8 @@ fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected - assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_cpu_count(None); + guest.validate_memory(None); }); kill_child(&mut child); @@ -220,12 +210,8 @@ fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { assert!(target_api.remote_command("ping", None)); // Create the VM first - let cpu_count: u8 = 4; - let request_body = guest.api_create_body( - cpu_count, - direct_kernel_boot_path().to_str().unwrap(), - DIRECT_KERNEL_BOOT_CMDLINE, - ); + let request_body = guest.api_create_body(); + let temp_config_path = guest.tmp_dir.as_path().join("config"); std::fs::write(&temp_config_path, request_body).unwrap(); let create_config = temp_config_path.as_os_str().to_str().unwrap(); @@ -239,8 +225,8 @@ fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected - assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_cpu_count(None); + guest.validate_memory(None); // Sync and shutdown without powering off to prevent filesystem // corruption. @@ -261,8 +247,8 @@ fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected - assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_cpu_count(None); + guest.validate_memory(None); }); kill_child(&mut child); @@ -288,12 +274,7 @@ fn _test_api_pause_resume(target_api: &TargetApi, guest: &Guest) { assert!(target_api.remote_command("ping", None)); // Create the VM first - let cpu_count: u8 = 4; - let request_body = guest.api_create_body( - cpu_count, - direct_kernel_boot_path().to_str().unwrap(), - DIRECT_KERNEL_BOOT_CMDLINE, - ); + let request_body = guest.api_create_body(); let temp_config_path = guest.tmp_dir.as_path().join("config"); std::fs::write(&temp_config_path, request_body).unwrap(); @@ -307,8 +288,8 @@ fn _test_api_pause_resume(target_api: &TargetApi, guest: &Guest) { let r = std::panic::catch_unwind(|| { // Check that the VM booted as expected - assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_cpu_count(None); + guest.validate_memory(None); // We now pause the VM assert!(target_api.remote_command("pause", None)); @@ -336,7 +317,7 @@ fn _test_api_pause_resume(target_api: &TargetApi, guest: &Guest) { thread::sleep(std::time::Duration::new(2, 0)); // Now we should be able to SSH back in and get the right number of CPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); + guest.validate_cpu_count(None); }); kill_child(&mut child); @@ -2522,6 +2503,7 @@ mod common_parallel { use std::process::Command; use block::ImageType; + use test_infra::GuestFactory; use crate::*; @@ -5961,7 +5943,9 @@ mod common_parallel { #[test] fn test_api_http_shutdown() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_shutdown(&target_api, &guest); @@ -5970,7 +5954,9 @@ mod common_parallel { #[test] fn test_api_http_delete() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_delete(&target_api, &guest); @@ -5979,7 +5965,9 @@ mod common_parallel { #[test] fn test_api_http_pause_resume() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_pause_resume(&target_api, &guest); @@ -5988,7 +5976,9 @@ mod common_parallel { #[test] fn test_api_http_create_boot() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_create_boot(&target_api, &guest); @@ -10105,12 +10095,7 @@ mod dbus_api { assert!(http_api.remote_command("ping", None)); // Create the VM first - let cpu_count: u8 = 4; - let request_body = guest.api_create_body( - cpu_count, - direct_kernel_boot_path().to_str().unwrap(), - DIRECT_KERNEL_BOOT_CMDLINE, - ); + let request_body = guest.api_create_body(); let temp_config_path = guest.tmp_dir.as_path().join("config"); std::fs::write(&temp_config_path, request_body).unwrap(); @@ -10125,8 +10110,8 @@ mod dbus_api { guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected - assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_cpu_count(None); + guest.validate_memory(None); // Sync and shutdown without powering off to prevent filesystem // corruption. @@ -10144,8 +10129,8 @@ mod dbus_api { guest.wait_vm_boot().unwrap(); // Check that the VM booted as expected - assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_cpu_count(None); + guest.validate_memory(None); }); kill_child(&mut child); @@ -10157,7 +10142,9 @@ mod dbus_api { #[test] fn test_api_dbus_create_boot() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); _test_api_create_boot(&target_api, &guest); @@ -10166,7 +10153,9 @@ mod dbus_api { #[test] fn test_api_dbus_shutdown() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); _test_api_shutdown(&target_api, &guest); @@ -10175,7 +10164,9 @@ mod dbus_api { #[test] fn test_api_dbus_delete() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); _test_api_delete(&target_api, &guest); @@ -10184,7 +10175,9 @@ mod dbus_api { #[test] fn test_api_dbus_pause_resume() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); _test_api_pause_resume(&target_api, &guest); diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 27b608c987..fba95ed977 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1083,11 +1083,11 @@ impl Guest { ) } - pub fn api_create_body(&self, cpu_count: u8, kernel_path: &str, kernel_cmd: &str) -> String { + pub fn api_create_body(&self) -> String { let mut body = serde_json::json!({ "cpus": { - "boot_vcpus": cpu_count, - "max_vcpus": cpu_count, + "boot_vcpus": self.num_cpu, + "max_vcpus": self.num_cpu, }, "net": [ { @@ -1117,13 +1117,13 @@ impl Guest { .unwrap() .to_str() .unwrap(), - "cmdline": kernel_cmd, + "cmdline": self.kernel_cmdline.as_deref().unwrap(), "host_data": generate_host_data(), }); } else { body["payload"] = serde_json::json!({ - "kernel": kernel_path, - "cmdline": kernel_cmd, + "kernel": self.kernel_path.as_deref().unwrap(), + "cmdline": self.kernel_cmdline.as_deref().unwrap(), }); } From b8a6afd332d32f6fa00f9461401b8bdf0853e249 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Feb 2026 18:19:47 -0800 Subject: [PATCH 0637/1893] 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 --- cloud-hypervisor/tests/integration.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index b3613067e2..4c00f63d5a 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14555,10 +14555,20 @@ mod common_cvm { #[test] fn test_focal_simple_launch() { let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let mut guest = Guest::new(Box::new(disk_config)); + let guest = Guest::new(Box::new(disk_config)); guest.vm_type = GuestVmType::Confidential; guest.boot_timeout = DEFAULT_CVM_TCP_LISTENER_TIMEOUT; guest.nested = false; _test_simple_launch(&guest) } + + #[test] + fn test_api_http_create_boot() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_create_boot(&target_api, &guest); + } } From f6829561e59858c3a9c1415a62298cc8b48a952d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 28 Feb 2026 16:19:10 -0800 Subject: [PATCH 0638/1893] 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 --- cloud-hypervisor/tests/integration.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 4c00f63d5a..01bd9a4ddf 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14555,10 +14555,9 @@ mod common_cvm { #[test] fn test_focal_simple_launch() { let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - guest.vm_type = GuestVmType::Confidential; - guest.boot_timeout = DEFAULT_CVM_TCP_LISTENER_TIMEOUT; - guest.nested = false; + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_simple_launch(&guest) } From 7c48aafb655bd3c44d5fbd863ae2076c87a28fbc Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 28 Feb 2026 16:46:18 -0800 Subject: [PATCH 0639/1893] 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 --- cloud-hypervisor/tests/integration.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 01bd9a4ddf..d9874d2e76 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14570,4 +14570,26 @@ mod common_cvm { let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_create_boot(&target_api, &guest); } + + #[test] + fn test_api_http_shutdown() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_shutdown(&target_api, &guest); + } + + #[test] + fn test_api_http_delete() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_delete(&target_api, &guest); + } } From f57b7c5b86fa0764947d020284c0afd710ce5e81 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 4 Mar 2026 02:04:46 -0800 Subject: [PATCH 0640/1893] 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 --- arch/src/x86_64/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 979fd52a9b..8bc5ec19c7 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -841,11 +841,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 { From 67945b31e5fff04571c2d961c39d2bfefef952b8 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 4 Mar 2026 02:22:14 -0800 Subject: [PATCH 0641/1893] 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 --- cloud-hypervisor/tests/integration.rs | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index d9874d2e76..96963bc4ed 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2616,6 +2616,51 @@ mod common_parallel { handle_child_output(r, &output); } + fn _test_nested_virtualization(nested: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)).with_nested(nested); + let mut child = GuestCommand::new(&guest) + .default_cpus() + .default_memory() + .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + let expected = if nested { "yes" } else { "no" }; + assert_eq!( + guest + .ssh_command("test -c /dev/kvm && echo yes || echo no") + .unwrap() + .trim(), + expected + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } + + #[test] + #[cfg(target_arch = "x86_64")] + fn test_nested_virtualization_on() { + _test_nested_virtualization(true); + } + + #[test] + #[cfg(target_arch = "x86_64")] + fn test_nested_virtualization_off() { + _test_nested_virtualization(false); + } + #[test] fn test_cpu_affinity() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); From a758f8bd8285d6e5b91c728597c35f8c8e3b2bdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:54:11 +0000 Subject: [PATCH 0642/1893] 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] --- .github/workflows/docker-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 6a52f1edf0..1dc0df47b7 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -27,7 +27,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to ghcr - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} From 1f93fef66f874ede0ca4665dce75f13b0d45455e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 16:13:48 +0100 Subject: [PATCH 0643/1893] 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 --- vmm/src/device_manager.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 52e4cddfa0..d1a0f62b9b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -267,10 +267,6 @@ pub enum DeviceManagerError { #[error("Failed to parse disk image format")] DetectImageType(#[source] io::Error), - /// Cannot open qcow disk path - #[error("Cannot open qcow disk path")] - QcowDeviceCreate(#[source] qcow::Error), - /// Cannot create serial manager #[error("Cannot create serial manager")] CreateSerialManager(#[source] SerialManagerError), From 2495fdc0e7634401cb9c85334a0b1b9532fe8012 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 5 Mar 2026 21:27:50 +0000 Subject: [PATCH 0644/1893] 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 --- cloud-hypervisor/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index b4d2bdf534..80cd502914 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -595,6 +595,8 @@ fn start_vmm(cmd_arguments: &ArgMatches) -> Result, Error> { } } + info!("{} starting", env!("BUILD_VERSION")); + let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?; #[cfg(feature = "guest_debug")] From 5925a013af84c2f6ef6e856bffce96cec4cfa17a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 23:54:08 +0000 Subject: [PATCH 0645/1893] 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] --- .github/workflows/docker-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 1dc0df47b7..059aa19672 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -24,7 +24,7 @@ jobs: uses: docker/setup-qemu-action@v3 - 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@v4 From b7b38df99c3a0522723f4f1897e1e8e5117cf0ff Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 22:59:03 +0100 Subject: [PATCH 0646/1893] 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 --- block/src/raw_async.rs | 5 ++--- block/src/raw_async_aio.rs | 5 ++--- block/src/raw_sync.rs | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 3a890d716f..152e5fa3ba 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -42,9 +42,8 @@ impl DiskFile for RawFileDisk { fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth) .map_err(DiskFileError::NewAsyncIo)?; - raw.alignment = DiskTopology::probe(&self.file) - .map(|t| t.logical_block_size) - .unwrap_or(SECTOR_SIZE); + raw.alignment = + DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); Ok(Box::new(raw) as Box) } diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 7266a3633a..fe7196ebba 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -45,9 +45,8 @@ impl DiskFile for RawFileDiskAio { fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { let mut raw = RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth) .map_err(DiskFileError::NewAsyncIo)?; - raw.alignment = DiskTopology::probe(&self.file) - .map(|t| t.logical_block_size) - .unwrap_or(SECTOR_SIZE); + raw.alignment = + DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); Ok(Box::new(raw) as Box) } diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 9c96863b69..9c2d6b7893 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -41,9 +41,8 @@ impl DiskFile for RawFileDiskSync { fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { let mut raw = RawFileSync::new(self.file.as_raw_fd()); - raw.alignment = DiskTopology::probe(&self.file) - .map(|t| t.logical_block_size) - .unwrap_or(SECTOR_SIZE); + raw.alignment = + DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); Ok(Box::new(raw) as Box) } From ff39c35ac21fb17c7a96622d9d13f605dbe86b1d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 23:12:18 +0100 Subject: [PATCH 0647/1893] arch: x86_64: Collapse nested if into match arm guards Do the necessary replacements to satisfy clippy::collapsible_match. Signed-off-by: Anatol Belski --- arch/src/x86_64/mod.rs | 96 ++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 50 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 8bc5ec19c7..2b9ce38122 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -643,15 +643,13 @@ pub fn generate_common_cpuid( for entry in cpuid.as_mut_slice().iter_mut() { 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 => @@ -673,55 +671,53 @@ pub fn generate_common_cpuid( } } } - 0x1d => { - // Tile Information (purely AMX related). - if !config.amx { - entry.eax = 0; - entry.ebx = 0; - entry.ecx = 0; - entry.edx = 0; - } + // Tile Information (purely AMX related). + 0x1d 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 { - #[allow(unused_unsafe)] + 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 { 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; } // 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 { - #[allow(unused_unsafe)] + 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 { - #[allow(unused_unsafe)] - // 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 { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 => + { + #[allow(unused_unsafe)] + // 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; } // Set CPU physical bits 0x8000_0008 => { From 89107d2db4b2e018cd497707cc18179fe3f5ef2b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 23:27:53 +0100 Subject: [PATCH 0648/1893] 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 --- arch/src/x86_64/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 2b9ce38122..d35a878e61 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -641,6 +641,7 @@ pub fn generate_common_cpuid( // 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 => { @@ -711,7 +712,6 @@ pub fn generate_common_cpuid( // SAFETY: cpuid called with valid leaves && unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 => { - #[allow(unused_unsafe)] // SAFETY: cpuid called with valid leaves let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) }; entry.eax = leaf.eax; From f1c33afc8e5fcc94be14cea01a1b58a9259349bc Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 23:38:13 +0100 Subject: [PATCH 0649/1893] virtio-devices: vsock: Simplify discarded accept result Do the necessary replacements to satisfy clippy::map_unwrap_or. Signed-off-by: Anatol Belski --- virtio-devices/src/vsock/unix/muxer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index edce5b1e03..1a8570b75d 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -409,7 +409,7 @@ impl VsockMuxer { // If we're already maxed-out on connections, we'll just accept and // immediately discard this potentially new one. warn!("vsock: connection limit reached; refusing new host connection"); - self.host_sock.accept().map(|_| 0).unwrap_or(0); + let _ = self.host_sock.accept(); return; } self.host_sock From 3c62fabfc3c8676272e27b36a671b5a88d290312 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 23:43:57 +0100 Subject: [PATCH 0650/1893] vmm: Collapse nested if into match arm guards Do the necessary replacements to satisfy clippy::collapsible_match. Signed-off-by: Anatol Belski --- vmm/src/lib.rs | 1 + vmm/src/serial_manager.rs | 49 ++++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 9ffd7fc0bc..5c75a2db0e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -735,6 +735,7 @@ impl Vmm { for signal in signals.forever() { match signal { + #[allow(clippy::collapsible_match)] SIGTERM | SIGINT => { if exit_evt.write(1).is_err() { // Resetting the terminal is usually done as the VMM exits diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 5f8de1874a..8a0d391d67 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -132,33 +132,34 @@ impl SerialManager { let in_fd = match output { ConsoleOutput::Pty(ref fd) => fd.as_raw_fd(), - ConsoleOutput::Tty(_) => { + ConsoleOutput::Tty(_) // If running on an interactive TTY then accept input // SAFETY: trivially safe - if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } { - // SAFETY: STDIN_FILENO is a valid fd - let fd = unsafe { libc::dup(libc::STDIN_FILENO) }; - if fd == -1 { - return Err(Error::DupFd(std::io::Error::last_os_error())); - } - // SAFETY: fd is valid and owned by us - let stdin_clone = unsafe { File::from_raw_fd(fd) }; - // SAFETY: FFI calls with correct arguments - let ret = unsafe { - let mut flags = libc::fcntl(stdin_clone.as_raw_fd(), libc::F_GETFL); - flags |= libc::O_NONBLOCK; - libc::fcntl(stdin_clone.as_raw_fd(), libc::F_SETFL, flags) - }; - - if ret < 0 { - return Err(Error::SetNonBlocking(std::io::Error::last_os_error())); - } - - output = ConsoleOutput::Tty(Arc::new(stdin_clone)); - fd - } else { - return Ok(None); + if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } => + { + // SAFETY: STDIN_FILENO is a valid fd + let fd = unsafe { libc::dup(libc::STDIN_FILENO) }; + if fd == -1 { + return Err(Error::DupFd(std::io::Error::last_os_error())); } + // SAFETY: fd is valid and owned by us + let stdin_clone = unsafe { File::from_raw_fd(fd) }; + // SAFETY: FFI calls with correct arguments + let ret = unsafe { + let mut flags = libc::fcntl(stdin_clone.as_raw_fd(), libc::F_GETFL); + flags |= libc::O_NONBLOCK; + libc::fcntl(stdin_clone.as_raw_fd(), libc::F_SETFL, flags) + }; + + if ret < 0 { + return Err(Error::SetNonBlocking(std::io::Error::last_os_error())); + } + + output = ConsoleOutput::Tty(Arc::new(stdin_clone)); + fd + } + ConsoleOutput::Tty(_) => { + return Ok(None); } ConsoleOutput::Socket(ref fd) => { if let Some(path_in_socket) = socket { From e61349c10e83c9bdf8d53b5d514db955290d0db8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 6 Mar 2026 00:31:21 +0100 Subject: [PATCH 0651/1893] 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 --- vmm/src/gdb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/gdb.rs b/vmm/src/gdb.rs index fc24767d9c..82a5d63a9d 100644 --- a/vmm/src/gdb.rs +++ b/vmm/src/gdb.rs @@ -484,7 +484,7 @@ impl run_blocking::BlockingEventLoop for GdbEventLoop { } } - if conn.peek().map(|b| b.is_some()).unwrap_or(true) { + if conn.peek().map_or(true, |b| b.is_some()) { let byte = conn .read() .map_err(run_blocking::WaitForStopReasonError::Connection)?; From 4ebbbe2294ce4c9f6d49a7fa7de7fe461bce4ab8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 6 Mar 2026 00:39:57 +0100 Subject: [PATCH 0652/1893] 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 --- vmm/src/igvm/igvm_loader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 4d454f8223..6e256c1ecb 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -433,7 +433,7 @@ pub fn load_igvm( let mut now = Instant::now(); // Sort the gpas to group them by the page type - gpas.sort_by(|a, b| a.gpa.cmp(&b.gpa)); + gpas.sort_by_key(|a| a.gpa); let gpas_grouped = gpas .iter() From 5e2539ae1659ec97157f79510ea53fafdc2be68d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 6 Mar 2026 08:22:05 -0800 Subject: [PATCH 0653/1893] vmm: serial_manager: Use more appropriately named variable This isn't an fd - rather it's the UnixListener struct. Signed-off-by: Rob Bradford --- vmm/src/serial_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 8a0d391d67..45edec95eb 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -161,11 +161,11 @@ impl SerialManager { ConsoleOutput::Tty(_) => { return Ok(None); } - ConsoleOutput::Socket(ref fd) => { + ConsoleOutput::Socket(ref listener) => { if let Some(path_in_socket) = socket { socket_path = Some(path_in_socket.clone()); } - fd.as_raw_fd() + listener.as_raw_fd() } _ => return Ok(None), }; From 48dcfc5fd04a4de91718d2975d67a85a46b4916b Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 6 Mar 2026 08:33:53 -0800 Subject: [PATCH 0654/1893] 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 --- vmm/src/console_devices.rs | 40 +++++++++++++++---------------- vmm/src/device_manager.rs | 49 ++++++++++++++++++++------------------ vmm/src/serial_manager.rs | 37 ++++++++++++++-------------- 3 files changed, 64 insertions(+), 62 deletions(-) diff --git a/vmm/src/console_devices.rs b/vmm/src/console_devices.rs index 76655d6c16..066f649afa 100644 --- a/vmm/src/console_devices.rs +++ b/vmm/src/console_devices.rs @@ -56,7 +56,7 @@ pub enum ConsoleDeviceError { type ConsoleDeviceResult = result::Result; #[derive(Clone)] -pub enum ConsoleOutput { +pub enum ConsoleTransport { File(Arc), Pty(Arc), Tty(Arc), @@ -67,10 +67,10 @@ pub enum ConsoleOutput { #[derive(Clone)] pub struct ConsoleInfo { - pub console_main_fd: ConsoleOutput, - pub serial_main_fd: ConsoleOutput, + pub console_main_fd: ConsoleTransport, + pub serial_main_fd: ConsoleTransport, #[cfg(target_arch = "x86_64")] - pub debug_main_fd: ConsoleOutput, + pub debug_main_fd: ConsoleTransport, } fn modify_mode( @@ -185,7 +185,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { let file = File::create(vmconfig.console.file.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; - ConsoleOutput::File(Arc::new(file)) + ConsoleTransport::File(Arc::new(file)) } ConsoleOutputMode::Pty => { let (main_fd, sub_fd, path) = @@ -200,7 +200,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { // Duplicating the file descriptors like this is needed as otherwise @@ -222,26 +222,26 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { return Err(ConsoleDeviceError::NoSocketOptionSupportForConsoleDevice); } - ConsoleOutputMode::Null => ConsoleOutput::Null, - ConsoleOutputMode::Off => ConsoleOutput::Off, + ConsoleOutputMode::Null => ConsoleTransport::Null, + ConsoleOutputMode::Off => ConsoleTransport::Off, }, serial_main_fd: match vmconfig.serial.mode { ConsoleOutputMode::File => { let file = File::create(vmconfig.serial.file.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; - ConsoleOutput::File(Arc::new(file)) + ConsoleTransport::File(Arc::new(file)) } ConsoleOutputMode::Pty => { let (main_fd, sub_fd, path) = create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?; set_raw_mode(&sub_fd.as_raw_fd(), &mut original_termios_opt)?; vmconfig.serial.file = Some(path.clone()); - ConsoleOutput::Pty(Arc::new(main_fd)) + ConsoleTransport::Pty(Arc::new(main_fd)) } ConsoleOutputMode::Tty => { // During vm_shutdown, when serial device is closed, FD#2(STDOUT) @@ -257,41 +257,41 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { let listener = UnixListener::bind(vmconfig.serial.socket.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; - ConsoleOutput::Socket(Arc::new(listener)) + ConsoleTransport::Socket(Arc::new(listener)) } - ConsoleOutputMode::Null => ConsoleOutput::Null, - ConsoleOutputMode::Off => ConsoleOutput::Off, + ConsoleOutputMode::Null => ConsoleTransport::Null, + ConsoleOutputMode::Off => ConsoleTransport::Off, }, #[cfg(target_arch = "x86_64")] debug_main_fd: match vmconfig.debug_console.mode { ConsoleOutputMode::File => { let file = File::create(vmconfig.debug_console.file.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; - ConsoleOutput::File(Arc::new(file)) + ConsoleTransport::File(Arc::new(file)) } ConsoleOutputMode::Pty => { let (main_fd, sub_fd, path) = create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?; set_raw_mode(&sub_fd.as_raw_fd(), &mut original_termios_opt)?; vmconfig.debug_console.file = Some(path.clone()); - ConsoleOutput::Pty(Arc::new(main_fd)) + ConsoleTransport::Pty(Arc::new(main_fd)) } ConsoleOutputMode::Tty => { let out = dup_stdout().map_err(|e| ConsoleDeviceError::CreateConsoleDevice(e.into()))?; set_raw_mode(&out, &mut original_termios_opt)?; - ConsoleOutput::Tty(Arc::new(out)) + ConsoleTransport::Tty(Arc::new(out)) } ConsoleOutputMode::Socket => { return Err(ConsoleDeviceError::NoSocketOptionSupportForConsoleDevice); } - ConsoleOutputMode::Null => ConsoleOutput::Null, - ConsoleOutputMode::Off => ConsoleOutput::Off, + ConsoleOutputMode::Null => ConsoleTransport::Null, + ConsoleOutputMode::Off => ConsoleTransport::Off, }, }; diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index d1a0f62b9b..014de4c0da 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -116,7 +116,7 @@ use vm_migration::{ use vm_virtio::{AccessPlatform, VirtioDeviceType}; use vmm_sys_util::eventfd::EventFd; -use crate::console_devices::{ConsoleDeviceError, ConsoleInfo, ConsoleOutput}; +use crate::console_devices::{ConsoleDeviceError, ConsoleInfo, ConsoleTransport}; use crate::cpu::{CPU_MANAGER_ACPI_SIZE, CpuManager}; use crate::device_tree::{DeviceNode, DeviceTree}; use crate::interrupt::{LegacyUserspaceInterruptManager, MsiInterruptManager}; @@ -2334,17 +2334,17 @@ impl DeviceManager { fn add_virtio_console_device( &mut self, - console_fd: ConsoleOutput, + console_fd: ConsoleTransport, resize_pipe: Option>, ) -> DeviceManagerResult>> { let console_config = self.config.lock().unwrap().console.clone(); let endpoint = match console_fd { - ConsoleOutput::File(file) => Endpoint::File(file), - ConsoleOutput::Pty(file) => { + ConsoleTransport::File(file) => Endpoint::File(file), + ConsoleTransport::Pty(file) => { self.console_resize_pipe = resize_pipe; Endpoint::PtyPair(Arc::new(file.try_clone().unwrap()), file) } - ConsoleOutput::Tty(stdout) => { + ConsoleTransport::Tty(stdout) => { if stdout.is_terminal() { self.console_resize_pipe = resize_pipe; } @@ -2365,11 +2365,11 @@ impl DeviceManager { Endpoint::File(stdout) } } - ConsoleOutput::Socket(_) => { + ConsoleTransport::Socket(_) => { return Err(DeviceManagerError::NoSocketOptionSupportForConsoleDevice); } - ConsoleOutput::Null => Endpoint::Null, - ConsoleOutput::Off => return Ok(None), + ConsoleTransport::Null => Endpoint::Null, + ConsoleTransport::Off => return Ok(None), }; let id = String::from(CONSOLE_DEVICE_NAME); @@ -2434,19 +2434,21 @@ impl DeviceManager { let console_info = console_info.unwrap(); let serial_writer: Option> = match console_info.serial_main_fd { - ConsoleOutput::File(ref file) | ConsoleOutput::Tty(ref file) => { + ConsoleTransport::File(ref file) | ConsoleTransport::Tty(ref file) => { Some(Box::new(Arc::clone(file))) } - ConsoleOutput::Off - | ConsoleOutput::Null - | ConsoleOutput::Pty(_) - | ConsoleOutput::Socket(_) => None, + ConsoleTransport::Off + | ConsoleTransport::Null + | ConsoleTransport::Pty(_) + | ConsoleTransport::Socket(_) => None, }; - if !matches!(console_info.serial_main_fd, ConsoleOutput::Off) { + if !matches!(console_info.serial_main_fd, ConsoleTransport::Off) { let serial = self.add_serial_device(interrupt_manager, serial_writer)?; self.serial_manager = match console_info.serial_main_fd { - ConsoleOutput::Pty(_) | ConsoleOutput::Tty(_) | ConsoleOutput::Socket(_) => { + ConsoleTransport::Pty(_) + | ConsoleTransport::Tty(_) + | ConsoleTransport::Socket(_) => { let serial_manager = SerialManager::new( serial, console_info.serial_main_fd, @@ -2472,14 +2474,15 @@ impl DeviceManager { #[cfg(target_arch = "x86_64")] { - let debug_console_writer: Option> = - match console_info.debug_main_fd { - ConsoleOutput::File(file) | ConsoleOutput::Tty(file) => Some(Box::new(file)), - ConsoleOutput::Off - | ConsoleOutput::Null - | ConsoleOutput::Pty(_) - | ConsoleOutput::Socket(_) => None, - }; + let debug_console_writer: Option> = match console_info + .debug_main_fd + { + ConsoleTransport::File(file) | ConsoleTransport::Tty(file) => Some(Box::new(file)), + ConsoleTransport::Off + | ConsoleTransport::Null + | ConsoleTransport::Pty(_) + | ConsoleTransport::Socket(_) => None, + }; if let Some(writer) = debug_console_writer { let _ = self.add_debug_console_device(writer)?; } diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 45edec95eb..e5d5ad949a 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -24,7 +24,7 @@ use serial_buffer::SerialBuffer; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; -use crate::console_devices::ConsoleOutput; +use crate::console_devices::ConsoleTransport; #[derive(Debug, Error)] pub enum Error { @@ -114,7 +114,7 @@ pub struct SerialManager { #[cfg(target_arch = "aarch64")] serial: Arc>, epoll_file: File, - in_file: ConsoleOutput, + in_file: ConsoleTransport, kill_evt: EventFd, handle: Option>, pty_write_out: Option>, @@ -125,14 +125,14 @@ impl SerialManager { pub fn new( #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] serial: Arc>, #[cfg(target_arch = "aarch64")] serial: Arc>, - mut output: ConsoleOutput, + mut output: ConsoleTransport, socket: Option, ) -> Result> { let mut socket_path: Option = None; let in_fd = match output { - ConsoleOutput::Pty(ref fd) => fd.as_raw_fd(), - ConsoleOutput::Tty(_) + ConsoleTransport::Pty(ref fd) => fd.as_raw_fd(), + ConsoleTransport::Tty(_) // If running on an interactive TTY then accept input // SAFETY: trivially safe if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } => @@ -155,13 +155,13 @@ impl SerialManager { return Err(Error::SetNonBlocking(std::io::Error::last_os_error())); } - output = ConsoleOutput::Tty(Arc::new(stdin_clone)); + output = ConsoleTransport::Tty(Arc::new(stdin_clone)); fd } - ConsoleOutput::Tty(_) => { + ConsoleTransport::Tty(_) => { return Ok(None); } - ConsoleOutput::Socket(ref listener) => { + ConsoleTransport::Socket(ref listener) => { if let Some(path_in_socket) = socket { socket_path = Some(path_in_socket.clone()); } @@ -181,7 +181,7 @@ impl SerialManager { ) .map_err(Error::Epoll)?; - let epoll_fd_data = if let ConsoleOutput::Socket(_) = output { + let epoll_fd_data = if let ConsoleTransport::Socket(_) = output { EpollDispatch::Socket } else { EpollDispatch::File @@ -196,7 +196,7 @@ impl SerialManager { .map_err(Error::Epoll)?; let mut pty_write_out = None; - if let ConsoleOutput::Pty(ref file) = output { + if let ConsoleTransport::Pty(ref file) = output { let write_out = Arc::new(AtomicBool::new(false)); pty_write_out = Some(write_out.clone()); let writer = file.try_clone().map_err(Error::FileClone)?; @@ -295,7 +295,7 @@ impl SerialManager { } }; - if matches!(in_file, ConsoleOutput::Pty(_)) && num_events == 0 { + if matches!(in_file, ConsoleTransport::Pty(_)) && num_events == 0 { // This very specific case happens when the serial is connected // to a PTY. We know EPOLLHUP is always present when there's nothing // connected at the other end of the PTY. That's why getting no event @@ -320,7 +320,7 @@ impl SerialManager { .map_err(Error::AcceptConnection)?; } - let ConsoleOutput::Socket(ref listener) = in_file else { + let ConsoleTransport::Socket(ref listener) = in_file else { unreachable!(); }; @@ -349,7 +349,7 @@ impl SerialManager { if event.events & libc::EPOLLIN as u32 != 0 { let mut input = [0u8; 64]; let count = match &in_file { - ConsoleOutput::Socket(_) => { + ConsoleTransport::Socket(_) => { if let Some(mut serial_reader) = reader.as_ref() { let count = serial_reader .read(&mut input) @@ -371,11 +371,10 @@ impl SerialManager { 0 } } - ConsoleOutput::Pty(file) | ConsoleOutput::Tty(file) => { - (&**file) - .read(&mut input) - .map_err(Error::ReadInput)? - } + ConsoleTransport::Pty(file) + | ConsoleTransport::Tty(file) => (&**file) + .read(&mut input) + .map_err(Error::ReadInput)?, _ => unreachable!(), }; @@ -432,7 +431,7 @@ impl Drop for SerialManager { if let Some(handle) = self.handle.take() { handle.join().ok(); } - if let ConsoleOutput::Socket(_) = self.in_file + if let ConsoleTransport::Socket(_) = self.in_file && let Some(socket_path) = self.socket_path.as_ref() { std::fs::remove_file(socket_path.as_os_str()) From f3d2d6c6692e75fcc8a2cf69f9b7f18a0101b418 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 6 Mar 2026 08:41:18 -0800 Subject: [PATCH 0655/1893] vmm: serial_manager: Be consistent with ConsoleTransport variable Ensure that the same variable name is used for the transport. Signed-off-by: Rob Bradford --- vmm/src/serial_manager.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index e5d5ad949a..5a61b39e58 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -114,7 +114,7 @@ pub struct SerialManager { #[cfg(target_arch = "aarch64")] serial: Arc>, epoll_file: File, - in_file: ConsoleTransport, + transport: ConsoleTransport, kill_evt: EventFd, handle: Option>, pty_write_out: Option>, @@ -125,12 +125,12 @@ impl SerialManager { pub fn new( #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] serial: Arc>, #[cfg(target_arch = "aarch64")] serial: Arc>, - mut output: ConsoleTransport, + mut transport: ConsoleTransport, socket: Option, ) -> Result> { let mut socket_path: Option = None; - let in_fd = match output { + let in_fd = match transport { ConsoleTransport::Pty(ref fd) => fd.as_raw_fd(), ConsoleTransport::Tty(_) // If running on an interactive TTY then accept input @@ -155,7 +155,7 @@ impl SerialManager { return Err(Error::SetNonBlocking(std::io::Error::last_os_error())); } - output = ConsoleTransport::Tty(Arc::new(stdin_clone)); + transport = ConsoleTransport::Tty(Arc::new(stdin_clone)); fd } ConsoleTransport::Tty(_) => { @@ -181,7 +181,7 @@ impl SerialManager { ) .map_err(Error::Epoll)?; - let epoll_fd_data = if let ConsoleTransport::Socket(_) = output { + let epoll_fd_data = if let ConsoleTransport::Socket(_) = transport { EpollDispatch::Socket } else { EpollDispatch::File @@ -196,7 +196,7 @@ impl SerialManager { .map_err(Error::Epoll)?; let mut pty_write_out = None; - if let ConsoleTransport::Pty(ref file) = output { + if let ConsoleTransport::Pty(ref file) = transport { let write_out = Arc::new(AtomicBool::new(false)); pty_write_out = Some(write_out.clone()); let writer = file.try_clone().map_err(Error::FileClone)?; @@ -215,7 +215,7 @@ impl SerialManager { Ok(Some(SerialManager { serial, epoll_file, - in_file: output, + transport, kill_evt, handle: None, pty_write_out, @@ -257,7 +257,7 @@ impl SerialManager { } let epoll_fd = self.epoll_file.as_raw_fd(); - let in_file = self.in_file.clone(); + let transport = self.transport.clone(); let serial = self.serial.clone(); let pty_write_out = self.pty_write_out.clone(); let mut reader: Option = None; @@ -295,7 +295,7 @@ impl SerialManager { } }; - if matches!(in_file, ConsoleTransport::Pty(_)) && num_events == 0 { + if matches!(transport, ConsoleTransport::Pty(_)) && num_events == 0 { // This very specific case happens when the serial is connected // to a PTY. We know EPOLLHUP is always present when there's nothing // connected at the other end of the PTY. That's why getting no event @@ -320,7 +320,7 @@ impl SerialManager { .map_err(Error::AcceptConnection)?; } - let ConsoleTransport::Socket(ref listener) = in_file else { + let ConsoleTransport::Socket(ref listener) = transport else { unreachable!(); }; @@ -348,7 +348,7 @@ impl SerialManager { EpollDispatch::File => { if event.events & libc::EPOLLIN as u32 != 0 { let mut input = [0u8; 64]; - let count = match &in_file { + let count = match &transport { ConsoleTransport::Socket(_) => { if let Some(mut serial_reader) = reader.as_ref() { let count = serial_reader @@ -431,7 +431,7 @@ impl Drop for SerialManager { if let Some(handle) = self.handle.take() { handle.join().ok(); } - if let ConsoleTransport::Socket(_) = self.in_file + if let ConsoleTransport::Socket(_) = self.transport && let Some(socket_path) = self.socket_path.as_ref() { std::fs::remove_file(socket_path.as_os_str()) From 9cb49a244e3fd1b6fa364a11c0796cc23f83bf01 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 6 Mar 2026 08:57:03 -0800 Subject: [PATCH 0656/1893] vmm: device_manager: Be consistent with transport variable name Signed-off-by: Rob Bradford --- vmm/src/device_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 014de4c0da..4dc965379b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2334,11 +2334,11 @@ impl DeviceManager { fn add_virtio_console_device( &mut self, - console_fd: ConsoleTransport, + transport: ConsoleTransport, resize_pipe: Option>, ) -> DeviceManagerResult>> { let console_config = self.config.lock().unwrap().console.clone(); - let endpoint = match console_fd { + let endpoint = match transport { ConsoleTransport::File(file) => Endpoint::File(file), ConsoleTransport::Pty(file) => { self.console_resize_pipe = resize_pipe; From 9d712a10a67d339b5f7c6bf0eb06892e64017502 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 6 Mar 2026 09:05:30 -0800 Subject: [PATCH 0657/1893] 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 --- vmm/src/console_devices.rs | 12 ++++++------ vmm/src/device_manager.rs | 21 ++++++++------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/vmm/src/console_devices.rs b/vmm/src/console_devices.rs index 066f649afa..32cba7b780 100644 --- a/vmm/src/console_devices.rs +++ b/vmm/src/console_devices.rs @@ -67,10 +67,10 @@ pub enum ConsoleTransport { #[derive(Clone)] pub struct ConsoleInfo { - pub console_main_fd: ConsoleTransport, - pub serial_main_fd: ConsoleTransport, + pub console: ConsoleTransport, + pub serial: ConsoleTransport, #[cfg(target_arch = "x86_64")] - pub debug_main_fd: ConsoleTransport, + pub debug: ConsoleTransport, } fn modify_mode( @@ -181,7 +181,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { let file = File::create(vmconfig.console.file.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; @@ -230,7 +230,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult ConsoleTransport::Null, ConsoleOutputMode::Off => ConsoleTransport::Off, }, - serial_main_fd: match vmconfig.serial.mode { + serial: match vmconfig.serial.mode { ConsoleOutputMode::File => { let file = File::create(vmconfig.serial.file.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; @@ -268,7 +268,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult ConsoleTransport::Off, }, #[cfg(target_arch = "x86_64")] - debug_main_fd: match vmconfig.debug_console.mode { + debug: match vmconfig.debug_console.mode { ConsoleOutputMode::File => { let file = File::create(vmconfig.debug_console.file.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 4dc965379b..1ac9afe30e 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2433,7 +2433,7 @@ impl DeviceManager { // SAFETY: console_info is Some, so it's safe to unwrap. let console_info = console_info.unwrap(); - let serial_writer: Option> = match console_info.serial_main_fd { + let serial_writer: Option> = match console_info.serial { ConsoleTransport::File(ref file) | ConsoleTransport::Tty(ref file) => { Some(Box::new(Arc::clone(file))) } @@ -2443,18 +2443,15 @@ impl DeviceManager { | ConsoleTransport::Socket(_) => None, }; - if !matches!(console_info.serial_main_fd, ConsoleTransport::Off) { + if !matches!(console_info.serial, ConsoleTransport::Off) { let serial = self.add_serial_device(interrupt_manager, serial_writer)?; - self.serial_manager = match console_info.serial_main_fd { + self.serial_manager = match console_info.serial { ConsoleTransport::Pty(_) | ConsoleTransport::Tty(_) | ConsoleTransport::Socket(_) => { - let serial_manager = SerialManager::new( - serial, - console_info.serial_main_fd, - serial_config.socket, - ) - .map_err(DeviceManagerError::CreateSerialManager)?; + let serial_manager = + SerialManager::new(serial, console_info.serial, serial_config.socket) + .map_err(DeviceManagerError::CreateSerialManager)?; if let Some(mut serial_manager) = serial_manager { serial_manager .start_thread( @@ -2474,9 +2471,7 @@ impl DeviceManager { #[cfg(target_arch = "x86_64")] { - let debug_console_writer: Option> = match console_info - .debug_main_fd - { + let debug_console_writer: Option> = match console_info.debug { ConsoleTransport::File(file) | ConsoleTransport::Tty(file) => Some(Box::new(file)), ConsoleTransport::Off | ConsoleTransport::Null @@ -2489,7 +2484,7 @@ impl DeviceManager { } let console_resizer = - self.add_virtio_console_device(console_info.console_main_fd, console_resize_pipe)?; + self.add_virtio_console_device(console_info.console, console_resize_pipe)?; Ok(Arc::new(Console { console_resizer })) } From 4c2b2110c9cc64cb568c3b7a11697f3bfd5bece2 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 6 Mar 2026 09:10:21 -0800 Subject: [PATCH 0658/1893] 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 --- vmm/src/serial_manager.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 5a61b39e58..f664768317 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -128,6 +128,17 @@ impl SerialManager { mut transport: ConsoleTransport, socket: Option, ) -> Result> { + let epoll_fd = epoll::create(true).map_err(Error::Epoll)?; + let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?; + + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + kill_evt.as_raw_fd(), + epoll::Event::new(epoll::Events::EPOLLIN, EpollDispatch::Kill as u64), + ) + .map_err(Error::Epoll)?; + let mut socket_path: Option = None; let in_fd = match transport { @@ -170,17 +181,6 @@ impl SerialManager { _ => return Ok(None), }; - let epoll_fd = epoll::create(true).map_err(Error::Epoll)?; - let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?; - - epoll::ctl( - epoll_fd, - epoll::ControlOptions::EPOLL_CTL_ADD, - kill_evt.as_raw_fd(), - epoll::Event::new(epoll::Events::EPOLLIN, EpollDispatch::Kill as u64), - ) - .map_err(Error::Epoll)?; - let epoll_fd_data = if let ConsoleTransport::Socket(_) = transport { EpollDispatch::Socket } else { From e7e5fefb29225aceb0b0d1b614b3438ee6ddb0fd Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 6 Mar 2026 09:13:41 -0800 Subject: [PATCH 0659/1893] 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 --- vmm/src/serial_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index f664768317..6ba2b95377 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -181,7 +181,7 @@ impl SerialManager { _ => return Ok(None), }; - let epoll_fd_data = if let ConsoleTransport::Socket(_) = transport { + let in_event = if let ConsoleTransport::Socket(_) = transport { EpollDispatch::Socket } else { EpollDispatch::File @@ -191,7 +191,7 @@ impl SerialManager { epoll_fd, epoll::ControlOptions::EPOLL_CTL_ADD, in_fd, - epoll::Event::new(epoll::Events::EPOLLIN, epoll_fd_data as u64), + epoll::Event::new(epoll::Events::EPOLLIN, in_event as u64), ) .map_err(Error::Epoll)?; From 072e980e0830665a469e3869b831c50f09d19298 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 6 Mar 2026 09:23:11 -0800 Subject: [PATCH 0660/1893] 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 --- vmm/src/serial_manager.rs | 44 ++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 6ba2b95377..27f359bec6 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -6,6 +6,7 @@ use std::fs::File; use std::io::Read; use std::net::Shutdown; +use std::os::fd::OwnedFd; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::net::UnixStream; use std::panic::AssertUnwindSafe; @@ -113,7 +114,7 @@ pub struct SerialManager { serial: Arc>, #[cfg(target_arch = "aarch64")] serial: Arc>, - epoll_file: File, + epoll_fd: OwnedFd, transport: ConsoleTransport, kill_evt: EventFd, handle: Option>, @@ -208,13 +209,13 @@ impl SerialManager { .set_out(Some(Box::new(buffer))); } - // Use 'File' to enforce closing on 'epoll_fd' + // Use 'OwnedFd' to manage lifetime // SAFETY: epoll_fd is valid - let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; + let epoll_fd = unsafe { OwnedFd::from_raw_fd(epoll_fd) }; Ok(Some(SerialManager { serial, - epoll_file, + epoll_fd, transport, kill_evt, handle: None, @@ -256,7 +257,7 @@ impl SerialManager { return Ok(()); } - let epoll_fd = self.epoll_file.as_raw_fd(); + let epoll_fd = self.epoll_fd.try_clone().map_err(Error::Epoll)?; let transport = self.transport.clone(); let serial = self.serial.clone(); let pty_write_out = self.pty_write_out.clone(); @@ -278,22 +279,23 @@ impl SerialManager { [epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; loop { - let num_events = match epoll::wait(epoll_fd, timeout, &mut events[..]) { - Ok(res) => res, - Err(e) => { - if e.kind() == io::ErrorKind::Interrupted { - // It's well defined from the epoll_wait() syscall - // documentation that the epoll loop can be interrupted - // before any of the requested events occurred or the - // timeout expired. In both those cases, epoll_wait() - // returns an error of type EINTR, but this should not - // be considered as a regular error. Instead it is more - // appropriate to retry, by calling into epoll_wait(). - continue; + let num_events = + match epoll::wait(epoll_fd.as_raw_fd(), timeout, &mut events[..]) { + Ok(res) => res, + Err(e) => { + if e.kind() == io::ErrorKind::Interrupted { + // It's well defined from the epoll_wait() syscall + // documentation that the epoll loop can be interrupted + // before any of the requested events occurred or the + // timeout expired. In both those cases, epoll_wait() + // returns an error of type EINTR, but this should not + // be considered as a regular error. Instead it is more + // appropriate to retry, by calling into epoll_wait(). + continue; + } + return Err(Error::Epoll(e)); } - return Err(Error::Epoll(e)); - } - }; + }; if matches!(transport, ConsoleTransport::Pty(_)) && num_events == 0 { // This very specific case happens when the serial is connected @@ -332,7 +334,7 @@ impl SerialManager { unix_stream.try_clone().map_err(Error::CloneUnixStream)?; epoll::ctl( - epoll_fd, + epoll_fd.as_raw_fd(), epoll::ControlOptions::EPOLL_CTL_ADD, unix_stream.as_raw_fd(), epoll::Event::new( From e5be2196f5fd96b84d0f80179549654c78a32875 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:53:48 +0000 Subject: [PATCH 0661/1893] 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] --- .github/workflows/docker-image.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 059aa19672..0f6a51f2d4 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -46,7 +46,7 @@ jobs: - name: Build and push if: ${{ github.event_name == 'push' }} - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: file: ./resources/Dockerfile platforms: linux/amd64,linux/arm64 @@ -55,7 +55,7 @@ jobs: - name: Build only if: ${{ github.event_name == 'pull_request' }} - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: file: ./resources/Dockerfile platforms: linux/amd64,linux/arm64 From 355cbad09a7b1aaf27a016701a77bf34c67a85ce Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 7 Mar 2026 11:17:23 +0100 Subject: [PATCH 0662/1893] 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 --- virtio-devices/src/iommu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index f4812b04fb..1097b6582e 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -98,7 +98,7 @@ struct VirtioIommuConfig { domain_range: VirtioIommuRange32, probe_size: u32, bypass: u8, - _reserved: [u8; 7], + _reserved: [u8; 3], } /// Virtio IOMMU request type From 313d980538a4a61a77af2d131c29baa8077a16ed Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 7 Mar 2026 11:25:31 +0100 Subject: [PATCH 0663/1893] 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 --- vm-virtio/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm-virtio/src/lib.rs b/vm-virtio/src/lib.rs index b7f5370702..c560e5c86e 100644 --- a/vm-virtio/src/lib.rs +++ b/vm-virtio/src/lib.rs @@ -39,7 +39,7 @@ pub enum VirtioDeviceType { Mem = 24, Fs = 26, Pmem = 27, - Watchdog = 35, // Temporary until official number allocated + Watchdog = 35, Unknown = 0xFF, } From da0d0a20901ff3c2ae8256462ee21b16eb1882fe Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 7 Mar 2026 12:01:25 +0100 Subject: [PATCH 0664/1893] 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 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 --- virtio-devices/src/block.rs | 2 +- virtio-devices/src/console.rs | 4 ++-- virtio-devices/src/lib.rs | 2 +- virtio-devices/src/net.rs | 2 +- virtio-devices/src/pmem.rs | 4 ++-- virtio-devices/src/rng.rs | 4 ++-- virtio-devices/src/vdpa.rs | 4 ++-- virtio-devices/src/vhost_user/blk.rs | 4 ++-- virtio-devices/src/vhost_user/fs.rs | 4 ++-- virtio-devices/src/vhost_user/generic_vhost_user.rs | 4 ++-- virtio-devices/src/vhost_user/net.rs | 6 +++--- virtio-devices/src/vsock/device.rs | 4 ++-- 12 files changed, 22 insertions(+), 22 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 0d2b1fb271..9d09ab91a6 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -737,7 +737,7 @@ impl Block { } if iommu { - avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } if read_only { diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index c8a9f08a02..7896fde9f1 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -25,7 +25,7 @@ use vmm_sys_util::eventfd::EventFd; use super::{ ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, - Error as DeviceError, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, + Error as DeviceError, VIRTIO_F_ACCESS_PLATFORM, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterruptType, }; use crate::seccomp_filters::Thread; @@ -609,7 +609,7 @@ impl Console { } else { let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_CONSOLE_F_SIZE); if iommu { - avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } ( diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index da4f1c91be..111c9007e1 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -71,7 +71,7 @@ const DEVICE_FAILED: u32 = 0x80; const VIRTIO_F_RING_INDIRECT_DESC: u32 = 28; const VIRTIO_F_RING_EVENT_IDX: u32 = 29; const VIRTIO_F_VERSION_1: u32 = 32; -const VIRTIO_F_IOMMU_PLATFORM: u32 = 33; +const VIRTIO_F_ACCESS_PLATFORM: u32 = 33; const VIRTIO_F_IN_ORDER: u32 = 35; const VIRTIO_F_ORDER_PLATFORM: u32 = 36; #[allow(dead_code)] diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 8eee661341..63b9de4116 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -463,7 +463,7 @@ impl Net { | (1 << VIRTIO_F_VERSION_1); if iommu { - avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } // Configure TSO/UFO features when hardware checksum offload is enabled. diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 549b62fd96..b7be248fa4 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -31,7 +31,7 @@ use vmm_sys_util::eventfd::EventFd; use super::{ ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, - EpollHelperHandler, Error as DeviceError, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, + EpollHelperHandler, Error as DeviceError, VIRTIO_F_ACCESS_PLATFORM, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, }; use crate::seccomp_filters::Thread; @@ -307,7 +307,7 @@ impl Pmem { let mut avail_features = 1u64 << VIRTIO_F_VERSION_1; if iommu { - avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } (avail_features, 0, config, false) }; diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 2f980d4d8b..2409e272c1 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -24,7 +24,7 @@ use vmm_sys_util::eventfd::EventFd; use super::{ ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, - EpollHelperHandler, Error as DeviceError, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, + EpollHelperHandler, Error as DeviceError, VIRTIO_F_ACCESS_PLATFORM, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, }; use crate::seccomp_filters::Thread; @@ -180,7 +180,7 @@ impl Rng { let mut avail_features = 1u64 << VIRTIO_F_VERSION_1; if iommu { - avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } (avail_features, 0, false) diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 725f215c77..9d20aac92e 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -28,7 +28,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::{ ActivateError, ActivateResult, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, - DEVICE_FEATURES_OK, GuestMemoryMmap, VIRTIO_F_IOMMU_PLATFORM, VirtioCommon, VirtioDevice, + DEVICE_FEATURES_OK, GuestMemoryMmap, VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioInterruptType, get_host_address_range, }; @@ -164,7 +164,7 @@ impl Vdpa { let iova_range = vhost.get_iova_range().map_err(Error::GetIovaRange)?; - if avail_features & (1u64 << VIRTIO_F_IOMMU_PLATFORM) == 0 { + if avail_features & (1u64 << VIRTIO_F_ACCESS_PLATFORM) == 0 { return Err(Error::MissingAccessPlatformVirtioFeature); } diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index d26350c91a..22896ba7c6 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -31,7 +31,7 @@ use super::{DEFAULT_VIRTIO_FEATURES, Error, Result}; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::VhostUserCommon; -use crate::{GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_IOMMU_PLATFORM, VirtioInterrupt}; +use crate::{GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_ACCESS_PLATFORM, VirtioInterrupt}; const DEFAULT_QUEUE_NUMBER: usize = 1; @@ -239,7 +239,7 @@ impl VirtioDevice for Blk { fn features(&self) -> u64 { let mut features = self.common.avail_features; if self.iommu { - features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } features } diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index d0005af90f..434454fcef 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -25,7 +25,7 @@ use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::VhostUserCommon; use crate::{ - ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_IOMMU_PLATFORM, + ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioSharedMemoryList, }; @@ -248,7 +248,7 @@ impl VirtioDevice for Fs { fn features(&self) -> u64 { let mut features = self.common.avail_features; if self.iommu { - features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } features } diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 5774af928a..d38eee3a92 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -27,7 +27,7 @@ use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::VhostUserCommon; use crate::{ - ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_IOMMU_PLATFORM, + ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioSharedMemoryList, }; @@ -218,7 +218,7 @@ impl VirtioDevice for GenericVhostUser { fn features(&self) -> u64 { let mut features = self.common.avail_features; if self.iommu { - features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } features } diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index e0a71c7342..187d710e39 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -30,8 +30,8 @@ use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::vu_common_ctrl::{VhostUserConfig, VhostUserHandle}; use crate::vhost_user::{DEFAULT_VIRTIO_FEATURES, Error, Result, VhostUserCommon}; use crate::{ - ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, VIRTIO_F_IOMMU_PLATFORM, - VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, + ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, + VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, }; const DEFAULT_QUEUE_NUMBER: usize = 2; @@ -275,7 +275,7 @@ impl VirtioDevice for Net { fn features(&self) -> u64 { let mut features = self.common.avail_features; if self.iommu { - features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } features } diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 27a0af1ff2..aa86aa0c95 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -50,7 +50,7 @@ use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::{ ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, - Error as DeviceError, GuestMemoryMmap, VIRTIO_F_IN_ORDER, VIRTIO_F_IOMMU_PLATFORM, + Error as DeviceError, GuestMemoryMmap, VIRTIO_F_ACCESS_PLATFORM, VIRTIO_F_IN_ORDER, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioInterruptType, }; @@ -349,7 +349,7 @@ where let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_F_IN_ORDER); if iommu { - avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; + avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } (avail_features, 0, false) }; From 7c690ffec020a56acea587f52518ce9bc7fb3533 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 6 Mar 2026 12:09:09 -0800 Subject: [PATCH 0665/1893] 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 --- block/src/fcntl.rs | 33 ++++++++++++++++++ fuzz/fuzz_targets/block.rs | 2 ++ virtio-devices/src/block.rs | 41 +++++++++++++---------- vmm/src/api/openapi/cloud-hypervisor.yaml | 5 ++- vmm/src/config.rs | 26 ++++++++++++-- vmm/src/device_manager.rs | 1 + vmm/src/vm_config.rs | 3 ++ 7 files changed, 91 insertions(+), 20 deletions(-) diff --git a/block/src/fcntl.rs b/block/src/fcntl.rs index a2a684f322..23c6f9f167 100644 --- a/block/src/fcntl.rs +++ b/block/src/fcntl.rs @@ -16,6 +16,7 @@ use std::fmt::Debug; use std::io; use std::os::fd::{AsRawFd, RawFd}; +use std::str::FromStr; use thiserror::Error; @@ -140,6 +141,38 @@ impl LockGranularity { } } +/// User-facing choice for the lock granularity. +/// +/// This allows external management software to create snapshots of the disk +/// image. Without a byte-range lock, some NFS implementations may treat the +/// entire file as exclusively locked and prevent such operations (e.g. NetApp). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum LockGranularityChoice { + /// Byte-range lock covering [0, size). + #[default] + ByteRange, + /// Whole-file lock (l_start=0, l_len=0) - original OFD whole-file lock behavior. + Full, +} + +/// Error returned when parsing a [`LockGranularityChoice`] from a string. +#[derive(Error, Debug)] +#[error("Invalid lock granularity value: {0}, expected 'byte-range' or 'full'")] +pub struct LockGranularityParseError(String); + +impl FromStr for LockGranularityChoice { + type Err = LockGranularityParseError; + + fn from_str(s: &str) -> Result { + match s { + "byte-range" => Ok(LockGranularityChoice::ByteRange), + "full" => Ok(LockGranularityChoice::Full), + _ => Err(LockGranularityParseError(s.to_owned())), + } + } +} + /// Returns a [`struct@libc::flock`] structure for the whole file. const fn get_flock(lock_type: LockType, granularity: LockGranularity) -> libc::flock { libc::flock { diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index 7d1fbdf38f..0ad9193fdb 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -16,6 +16,7 @@ use std::sync::Arc; use std::{ffi, io}; use block::async_io::DiskFile; +use block::fcntl::LockGranularityChoice; use block::raw_sync::RawFileDiskSync; use libfuzzer_sys::{fuzz_target, Corpus}; use seccompiler::SeccompAction; @@ -69,6 +70,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { queue_affinity, true, false, + LockGranularityChoice::default(), ) .unwrap(); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 9d09ab91a6..9bb97d31c3 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -19,7 +19,7 @@ use std::{io, result}; use anyhow::anyhow; use block::async_io::{AsyncIo, AsyncIoError, DiskFile, DiskFileError}; -use block::fcntl::{LockError, LockGranularity, LockType, get_lock_state}; +use block::fcntl::{LockError, LockGranularity, LockGranularityChoice, LockType, get_lock_state}; use block::{ ExecuteAsync, ExecuteError, Request, RequestType, VirtioBlockConfig, build_serial, fcntl, }; @@ -662,6 +662,7 @@ pub struct Block { serial: Vec, queue_affinity: BTreeMap>, disable_sector0_writes: bool, + lock_granularity_choice: LockGranularityChoice, } #[derive(Serialize, Deserialize)] @@ -692,6 +693,7 @@ impl Block { queue_affinity: BTreeMap>, sparse: bool, disable_sector0_writes: bool, + lock_granularity: LockGranularityChoice, ) -> io::Result { let (disk_nsectors, avail_features, acked_features, config, paused) = if let Some(state) = state { @@ -807,6 +809,7 @@ impl Block { serial, queue_affinity, disable_sector0_writes, + lock_granularity_choice: lock_granularity, }) } @@ -815,23 +818,27 @@ impl Block { } /// Returns the granularity for the advisory lock for this disk. - // TODO In future, we could add a `lock_granularity=` configuration to the CLI. - // For now, we stick to QEMU behavior. fn lock_granularity(&mut self) -> LockGranularity { - self.disk_image.physical_size().map_or_else( - // use a safe fallback - |e| { - let fallback = LockGranularity::WholeFile; - warn!( - "Can't get disk size for id={},path={}, falling back to {:?}: error: {e}", - self.id, - self.disk_path.display(), - fallback - ); - fallback - }, - |size| LockGranularity::ByteRange(0, size), - ) + match self.lock_granularity_choice { + LockGranularityChoice::Full => LockGranularity::WholeFile, + LockGranularityChoice::ByteRange => { + // Byte-range lock covering [0, size) + self.disk_image.physical_size().map_or_else( + // use a safe fallback + |e| { + let fallback = LockGranularity::WholeFile; + warn!( + "Can't get disk size for id={},path={}, falling back to {:?}: error: {e}", + self.id, + self.disk_path.display(), + fallback + ); + fallback + }, + |size| LockGranularity::ByteRange(0, size), + ) + } + } } /// Tries to set an advisory lock for the corresponding disk image. diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index c4f4b6acf0..77b16e97ff 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -980,7 +980,10 @@ components: image_type: type: string enum: [FixedVhd, Qcow2, Raw, Vhdx, Unknown] - + lock_granularity: + type: string + enum: [byte-range, full] + default: byte-range NetConfig: type: object diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 46f3443136..b4c04570f5 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1159,7 +1159,7 @@ impl DiskConfig { id=,pci_segment=,rate_limit_group=,\ queue_affinity=,\ serial=,backing_files=on|off,sparse=on|off,\ - image_type="; + image_type=,lock_granularity=byte-range|full"; pub fn parse(disk: &str) -> Result { let mut parser = OptionParser::new(); @@ -1187,7 +1187,8 @@ impl DiskConfig { .add("queue_affinity") .add("backing_files") .add("sparse") - .add("image_type"); + .add("image_type") + .add("lock_granularity"); parser.parse(disk).map_err(Error::ParseDisk)?; @@ -1289,6 +1290,11 @@ impl DiskConfig { ImageType::Unknown }; + let lock_granularity = parser + .convert::("lock_granularity") + .map_err(Error::ParseDisk)? + .unwrap_or_default(); + let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { Some(TokenBucketConfig { size: bw_size, @@ -1341,6 +1347,7 @@ impl DiskConfig { backing_files, sparse, image_type, + lock_granularity, }) } @@ -3800,6 +3807,7 @@ mod unit_tests { backing_files: false, sparse: true, image_type: ImageType::Unknown, + lock_granularity: LockGranularityChoice::default(), } } @@ -3871,6 +3879,20 @@ mod unit_tests { ..disk_fixture() } ); + assert_eq!( + DiskConfig::parse("path=/path/to_file,lock_granularity=full")?, + DiskConfig { + lock_granularity: LockGranularityChoice::Full, + ..disk_fixture() + } + ); + assert_eq!( + DiskConfig::parse("path=/path/to_file,lock_granularity=byte-range")?, + DiskConfig { + lock_granularity: LockGranularityChoice::ByteRange, + ..disk_fixture() + } + ); assert_eq!( DiskConfig::parse("path=/path/to_file,queue_affinity=[0@[1],1@[2],2@[3,4],3@[5-8]]")?, DiskConfig { diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 1ac9afe30e..c3c5618bda 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2853,6 +2853,7 @@ impl DeviceManager { queue_affinity, disk_cfg.sparse, disable_sector0_writes, + disk_cfg.lock_granularity, ) .map_err(DeviceManagerError::CreateVirtioBlock)?; diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 33c2b23acd..d453ead2d9 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -9,6 +9,7 @@ use std::str::FromStr; use std::{fs, result}; use block::ImageType; +pub use block::fcntl::LockGranularityChoice; use log::{debug, warn}; use net_util::MacAddr; use serde::{Deserialize, Serialize}; @@ -302,6 +303,8 @@ pub struct DiskConfig { pub sparse: bool, #[serde(default)] pub image_type: ImageType, + #[serde(default)] + pub lock_granularity: LockGranularityChoice, } impl ApplyLandlock for DiskConfig { From 01e4053bef19f7dc0166b177415bd70bfeb8b583 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 6 Mar 2026 12:09:16 -0800 Subject: [PATCH 0666/1893] 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 --- docs/disk_locking.md | 61 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/disk_locking.md diff --git a/docs/disk_locking.md b/docs/disk_locking.md new file mode 100644 index 0000000000..adb8956a08 --- /dev/null +++ b/docs/disk_locking.md @@ -0,0 +1,61 @@ +# Disk Image Locking + +Cloud Hypervisor places an advisory lock on each disk image opened via +`--disk` to prevent multiple instances from concurrently accessing the +same file. This avoids potential data corruption from overlapping writes. +Locks are advisory and require cooperating processes; a non-cooperating +process can still open and write to a locked file. Locking is host-local +and does not enforce coordination across multiple hosts. + +If the backing file resides on network storage, the storage system must +correctly translate or propagate OFD (Open File Description) locks across +the network to ensure that advisory locking semantics are preserved in a +multi-host environment. In the case of Linux, OFD locks are translated +into NFS locks by the NFS driver. + +The implementation uses Open File Description (OFD) locks (`F_OFD_SETLK`) +rather than traditional POSIX locks (`F_SETLK`). OFD locks are only +released when the last file descriptor referencing the open file +description is closed, preventing accidental early release. + +## Lock Granularity + +The `lock_granularity` parameter controls how the lock is placed on the +disk image: + +``` +--disk path=/foo.img,lock_granularity=byte-range +--disk path=/bar.img,lock_granularity=full +``` + +### `byte-range` (default) + +Locks the byte range `[0, physical_file_size)`. The physical file size +is evaluated once at startup; if the file grows after the lock is +acquired, the newly appended region is not covered by the lock. + +The file is protected against concurrent access by other instances of +Cloud Hypervisor. That's the only thing we can guarantee. + +#### Fallback to full + +One caveat is that if the physical size of the disk image cannot be +determined at startup (e.g. with certain vhost-user backends), Cloud +Hypervisor falls back to a whole-file lock regardless of the +`lock_granularity` setting, as a byte-range lock cannot be safely +computed without knowing the physical file size. + +### `full` + +Locks the entire file using the OFD whole-file semantic (`l_start=0`, +`l_len=0`). This may be needed in environments that depend on whole-file +lock semantics. Note that on some network storage backends, whole-file +OFD locks may be treated as mandatory rather than advisory, which can +cause external tools to fail when accessing the disk image. Lock +behavior may also vary across network filesystem implementations. + +## Disk Resizing + +Cloud Hypervisor supports live disk resizing. Currently, byte-range +locks are not updated. However, as a part of the file is still locked, +no new Cloud Hypervisor instance can open the disk image. From 9f569c7b8db6d6722df1cd26441eefc7002e76ae Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Thu, 5 Mar 2026 23:01:11 +0000 Subject: [PATCH 0667/1893] 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 --- virtio-devices/src/console.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index 7896fde9f1..fc715eaac4 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -526,11 +526,7 @@ impl ConsoleResizer { if let Some(tty) = self.tty.as_ref() { let (cols, rows) = get_win_size(tty); self.config.lock().unwrap().update_console_size(cols, rows); - if self - .acked_features - .fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::AcqRel) - != 0 - { + if self.acked_features.load(Ordering::Acquire) & (1u64 << VIRTIO_CONSOLE_F_SIZE) != 0 { // Send the interrupt to the driver let _ = self.config_evt.write(1); } From 49156c720ce11a2db3c334bc5e63bf3202ef23b9 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Sat, 7 Mar 2026 01:24:32 +0000 Subject: [PATCH 0668/1893] 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 --- virtio-devices/src/console.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index fc715eaac4..5430673e74 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -265,7 +265,7 @@ impl ConsoleEpollHandler { out.flush().map_err(Error::OutputFlush)?; } trans_queue - .add_used(desc_chain.memory(), desc_chain.head_index(), desc.len()) + .add_used(desc_chain.memory(), desc_chain.head_index(), 0) .map_err(Error::QueueAddUsed)?; used_descs = true; } From 2698cfed9824d804fd2b7e43172d917ef9854547 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Sat, 7 Mar 2026 01:25:04 +0000 Subject: [PATCH 0669/1893] 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 --- virtio-devices/src/console.rs | 71 ++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index 5430673e74..ab907db20f 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -51,8 +51,6 @@ const VIRTIO_CONSOLE_F_SIZE: u64 = 0; #[derive(Error, Debug)] enum Error { - #[error("Descriptor chain too short")] - DescriptorChainTooShort, #[error("Failed to read from guest memory")] GuestMemoryRead(#[source] vm_memory::guest_memory::Error), #[error("Failed to write to guest memory")] @@ -210,21 +208,28 @@ impl ConsoleEpollHandler { } while let Some(mut desc_chain) = recv_queue.pop_descriptor_chain(self.mem.memory()) { - let desc = desc_chain.next().ok_or(Error::DescriptorChainTooShort)?; - let len = cmp::min(desc.len(), in_buffer.len() as u32); - let source_slice = in_buffer.drain(..len as usize).collect::>(); - - desc_chain - .memory() - .write_slice( - &source_slice[..], - desc.addr() - .translate_gva(self.access_platform.as_deref(), desc.len() as usize), - ) - .map_err(Error::GuestMemoryWrite)?; + let mut total_len = 0; + while let Some(desc) = desc_chain.next() { + if in_buffer.is_empty() { + break; + } + let len = cmp::min(desc.len(), in_buffer.len() as u32); + let source_slice = in_buffer.drain(..len as usize).collect::>(); + + desc_chain + .memory() + .write_slice( + &source_slice[..], + desc.addr() + .translate_gva(self.access_platform.as_deref(), desc.len() as usize), + ) + .map_err(Error::GuestMemoryWrite)?; + + total_len += len; + } recv_queue - .add_used(desc_chain.memory(), desc_chain.head_index(), len) + .add_used(desc_chain.memory(), desc_chain.head_index(), total_len) .map_err(Error::QueueAddUsed)?; used_descs = true; @@ -248,21 +253,23 @@ impl ConsoleEpollHandler { let mut used_descs = false; while let Some(mut desc_chain) = trans_queue.pop_descriptor_chain(self.mem.memory()) { - let desc = desc_chain.next().ok_or(Error::DescriptorChainTooShort)?; - if let Some(out) = &mut self.out { - let mut buf: Vec = Vec::new(); - desc_chain - .memory() - .write_volatile_to( - desc.addr() - .translate_gva(self.access_platform.as_deref(), desc.len() as usize), - &mut buf, - desc.len() as usize, - ) - .map_err(Error::GuestMemoryRead)?; - - out.write_all(&buf).map_err(Error::OutputWriteAll)?; - out.flush().map_err(Error::OutputFlush)?; + while let Some(desc) = desc_chain.next() { + if let Some(out) = &mut self.out { + let mut buf: Vec = Vec::new(); + desc_chain + .memory() + .write_volatile_to( + desc.addr().translate_gva( + self.access_platform.as_deref(), + desc.len() as usize, + ), + &mut buf, + desc.len() as usize, + ) + .map_err(Error::GuestMemoryRead)?; + + out.write_all(&buf).map_err(Error::OutputWriteAll)?; + } } trans_queue .add_used(desc_chain.memory(), desc_chain.head_index(), 0) @@ -270,6 +277,10 @@ impl ConsoleEpollHandler { used_descs = true; } + if used_descs && let Some(out) = &mut self.out { + out.flush().map_err(Error::OutputFlush)?; + } + Ok(used_descs) } From 210514cbf38c0652eaebac0087671b395e0eb21e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 22 Feb 2026 14:33:44 +0100 Subject: [PATCH 0670/1893] 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 --- block/src/qcow/header.rs | 601 +++++++++++++++++++++++++++++++++++++++ block/src/qcow/mod.rs | 595 ++------------------------------------ 2 files changed, 618 insertions(+), 578 deletions(-) create mode 100644 block/src/qcow/header.rs diff --git a/block/src/qcow/header.rs b/block/src/qcow/header.rs new file mode 100644 index 0000000000..4549668418 --- /dev/null +++ b/block/src/qcow/header.rs @@ -0,0 +1,601 @@ +// 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::io::{Read, Seek, SeekFrom, Write}; +use std::mem::size_of; +use std::str::FromStr; + +use bitflags::bitflags; +use vmm_sys_util::file_traits::FileSync; + +use super::decoder::{Decoder, ZlibDecoder, ZstdDecoder}; +use super::qcow_raw_file::BeUint; +use super::raw_file::RawFile; +use super::{Error, Result, div_round_up_u32, div_round_up_u64}; + +#[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 { + 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, +} + +// 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 +const COMPRESSION_TYPE_ZSTD: u64 = 1; // 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 = (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, +} + +impl QcowHeader { + /// Read header extensions, optionally collecting feature names for error reporting. + pub(super) fn read_header_extensions( + f: &mut RawFile, + header: &mut QcowHeader, + mut feature_table: Option<&mut Vec<(u8, String)>>, + ) -> Result<()> { + // Extensions start directly after the header + f.seek(SeekFrom::Start(header.header_size as u64)) + .map_err(Error::ReadingHeader)?; + + loop { + let ext_type = u32::read_be(f).map_err(Error::ReadingHeader)?; + if ext_type == HEADER_EXT_END { + break; + } + + let ext_length = u32::read_be(f).map_err(Error::ReadingHeader)?; + + match ext_type { + HEADER_EXT_BACKING_FORMAT => { + let mut format_bytes = vec![0u8; ext_length as usize]; + f.read_exact(&mut format_bytes) + .map_err(Error::ReadingHeader)?; + 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(&mut data).map_err(Error::ReadingHeader)?; + let table = feature_table.as_mut().unwrap(); + for entry in data.chunks_exact(FEATURE_NAME_ENTRY_SIZE) { + 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 + f.seek(SeekFrom::Current(ext_length as i64)) + .map_err(Error::ReadingHeader)?; + } + } + + // Skip to the next 8 byte boundary + let padding = (8 - (ext_length % 8)) % 8; + f.seek(SeekFrom::Current(padding as i64)) + .map_err(Error::ReadingHeader)?; + } + + Ok(()) + } + + /// Creates a QcowHeader from a reference to a file. + pub fn new(f: &mut RawFile) -> Result { + f.rewind().map_err(Error::ReadingHeader)?; + let magic = u32::read_be(f).map_err(Error::ReadingHeader)?; + if magic != QCOW_MAGIC { + return Err(Error::InvalidMagic); + } + + // Reads the next u32 from the file. + fn read_u32_be(f: &mut RawFile) -> Result { + u32::read_be(f).map_err(Error::ReadingHeader) + } + + // Reads the next u64 from the file. + fn read_u64_be(f: &mut RawFile) -> Result { + u64::read_be(f).map_err(Error::ReadingHeader) + } + + let version = read_u32_be(f)?; + + let mut header = QcowHeader { + magic, + version, + backing_file_offset: read_u64_be(f)?, + backing_file_size: read_u32_be(f)?, + cluster_bits: read_u32_be(f)?, + size: read_u64_be(f)?, + crypt_method: read_u32_be(f)?, + l1_size: read_u32_be(f)?, + l1_table_offset: read_u64_be(f)?, + refcount_table_offset: read_u64_be(f)?, + refcount_table_clusters: read_u32_be(f)?, + nb_snapshots: read_u32_be(f)?, + snapshots_offset: read_u64_be(f)?, + incompatible_features: if version == 2 { 0 } else { read_u64_be(f)? }, + compatible_features: if version == 2 { 0 } else { read_u64_be(f)? }, + autoclear_features: if version == 2 { 0 } else { read_u64_be(f)? }, + refcount_order: if version == 2 { + DEFAULT_REFCOUNT_ORDER + } else { + read_u32_be(f)? + }, + header_size: if version == 2 { + V2_BARE_HEADER_SIZE + } else { + read_u32_be(f)? + }, + compression_type: CompressionType::Zlib, + backing_file: None, + }; + if version == 3 && header.header_size > V3_BARE_HEADER_SIZE { + let raw_compression_type = read_u64_be(f)? >> (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 { + f.seek(SeekFrom::Start(header.backing_file_offset)) + .map_err(Error::ReadingHeader)?; + let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize]; + f.read_exact(&mut backing_file_name_bytes) + .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 { + 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 { + 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::() 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::() 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::() 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 `file`. + pub fn write_to(&self, file: &mut F) -> Result<()> { + // Writes the next u32 to the file. + fn write_u32_be(f: &mut F, value: u32) -> Result<()> { + u32::write_be(f, value).map_err(Error::WritingHeader) + } + + // Writes the next u64 to the file. + fn write_u64_be(f: &mut F, value: u64) -> Result<()> { + u64::write_be(f, value).map_err(Error::WritingHeader) + } + + write_u32_be(file, self.magic)?; + write_u32_be(file, self.version)?; + write_u64_be(file, self.backing_file_offset)?; + write_u32_be(file, self.backing_file_size)?; + write_u32_be(file, self.cluster_bits)?; + write_u64_be(file, self.size)?; + write_u32_be(file, self.crypt_method)?; + write_u32_be(file, self.l1_size)?; + write_u64_be(file, self.l1_table_offset)?; + write_u64_be(file, self.refcount_table_offset)?; + write_u32_be(file, self.refcount_table_clusters)?; + write_u32_be(file, self.nb_snapshots)?; + write_u64_be(file, self.snapshots_offset)?; + + if self.version == 3 { + write_u64_be(file, self.incompatible_features)?; + write_u64_be(file, self.compatible_features)?; + write_u64_be(file, self.autoclear_features)?; + write_u32_be(file, self.refcount_order)?; + write_u32_be(file, self.header_size)?; + + if self.header_size > V3_BARE_HEADER_SIZE { + write_u64_be(file, 0)?; // no compression + } + + write_u32_be(file, 0)?; // header extension type: end of header extension area + write_u32_be(file, 0)?; // length of header extension data: 0 + } + + if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) { + if self.backing_file_offset > 0 { + file.seek(SeekFrom::Start(self.backing_file_offset)) + .map_err(Error::WritingHeader)?; + } + write!(file, "{backing_file_path}").map_err(Error::WritingHeader)?; + } + + // Set the file length by seeking and writing a zero to the last byte. This avoids needing + // a `File` instead of anything that implements seek as the `file` argument. + // Zeros out 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; + file.seek(SeekFrom::Start( + self.refcount_table_offset + refcount_blocks_size - 2, + )) + .map_err(Error::WritingHeader)?; + file.write(&[0u8]).map_err(Error::WritingHeader)?; + + Ok(()) + } + + /// Write only the incompatible_features field to the file at its fixed offset. + fn write_incompatible_features(&self, file: &mut F) -> Result<()> { + if self.version != 3 { + return Ok(()); + } + file.seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) + .map_err(Error::WritingHeader)?; + u64::write_be(file, self.incompatible_features).map_err(Error::WritingHeader)?; + 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 F, + dirty: bool, + ) -> Result<()> { + 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(Error::SyncingHeader)?; + } + 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 F) -> Result<()> { + if self.version == 3 { + self.incompatible_features |= IncompatFeatures::CORRUPT.bits(); + self.write_incompatible_features(file)?; + file.fsync().map_err(Error::SyncingHeader)?; + } + 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 F, + ) -> Result<()> { + if self.version == 3 && self.autoclear_features != 0 { + self.autoclear_features = 0; + file.seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET)) + .map_err(Error::WritingHeader)?; + u64::write_be(file, 0).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(()) +} diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index fd932406c0..dd0f154f61 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -5,20 +5,34 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause mod decoder; +mod header; mod qcow_raw_file; mod raw_file; mod refcount; mod vec_cache; use std::cmp::{max, min}; -use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; +use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::fs::{OpenOptions, read_link}; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::mem::size_of; use std::os::fd::{AsRawFd, RawFd}; -use std::str::{self, FromStr}; +use std::str; -use bitflags::bitflags; +#[cfg(test)] +use header::{ + AUTOCLEAR_FEATURES_OFFSET, DEFAULT_REFCOUNT_ORDER, HEADER_EXT_BACKING_FORMAT, HEADER_EXT_END, + V2_BARE_HEADER_SIZE, V3_BARE_HEADER_SIZE, +}; +pub use header::{ + BackingFileConfig, CompressionType, ImageType, IncompatFeatures, MissingFeatureError, + QcowHeader, +}; +use header::{ + COMPATIBLE_FEATURES_LAZY_REFCOUNTS, MAX_CLUSTER_BITS, MAX_QCOW_FILE_SIZE, + MAX_RAM_POINTER_TABLE_SIZE, MIN_CLUSTER_BITS, QCOW_MAGIC, max_refcount_clusters, + offset_is_cluster_boundary, +}; use libc::{EINVAL, EIO, ENOSPC}; use log::{error, warn}; use remain::sorted; @@ -28,7 +42,6 @@ use vmm_sys_util::seek_hole::SeekHole; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::BlockBackend; -use crate::qcow::decoder::{Decoder, ZlibDecoder, ZstdDecoder}; use crate::qcow::qcow_raw_file::{BeUint, QcowRawFile}; pub use crate::qcow::raw_file::RawFile; use crate::qcow::refcount::RefCount; @@ -144,68 +157,6 @@ pub enum Error { pub type Result = std::result::Result; -#[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 { - 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, -} - -// Maximum data size supported. -const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB. - -// QCOW magic constant that starts the header. -const QCOW_MAGIC: u32 = 0x5146_49fb; -// Default to a cluster size of 2^DEFAULT_CLUSTER_BITS -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. -const MIN_CLUSTER_BITS: u32 = 9; -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. -const MAX_RAM_POINTER_TABLE_SIZE: u64 = 35_000_000; -// 16-bit refcounts. -const DEFAULT_REFCOUNT_ORDER: u32 = 4; - -const V2_BARE_HEADER_SIZE: u32 = 72; -const V3_BARE_HEADER_SIZE: u32 = 104; -const AUTOCLEAR_FEATURES_OFFSET: u64 = 88; - // bits 0-8 and 56-63 are reserved. const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; @@ -214,94 +165,6 @@ const ZERO_FLAG: u64 = 1 << 0; const COMPRESSED_FLAG: u64 = 1 << 62; const COMPRESSED_SECTOR_SIZE: u64 = 512; const CLUSTER_USED_FLAG: u64 = 1 << 63; -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 -const COMPRESSION_TYPE_ZSTD: u64 = 1; // zstd - -// Header extension types -const HEADER_EXT_END: u32 = 0x00000000; -// Backing file format name (raw, qcow2) -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. - 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, Error)] -pub struct MissingFeatureError { - /// Unsupported feature bits. - features: IncompatFeatures, - /// Feature name table from the qcow2 image. - feature_names: Vec<(u8, String)>, -} - -impl MissingFeatureError { - 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 = (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; fn l2_entry_is_empty(l2_entry: u64) -> bool { l2_entry == 0 @@ -348,422 +211,6 @@ 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) } -/// 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, -} - -impl QcowHeader { - /// Read header extensions, optionally collecting feature names for error reporting. - fn read_header_extensions( - f: &mut RawFile, - header: &mut QcowHeader, - mut feature_table: Option<&mut Vec<(u8, String)>>, - ) -> Result<()> { - // Extensions start directly after the header - f.seek(SeekFrom::Start(header.header_size as u64)) - .map_err(Error::ReadingHeader)?; - - loop { - let ext_type = u32::read_be(f).map_err(Error::ReadingHeader)?; - if ext_type == HEADER_EXT_END { - break; - } - - let ext_length = u32::read_be(f).map_err(Error::ReadingHeader)?; - - match ext_type { - HEADER_EXT_BACKING_FORMAT => { - let mut format_bytes = vec![0u8; ext_length as usize]; - f.read_exact(&mut format_bytes) - .map_err(Error::ReadingHeader)?; - 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(&mut data).map_err(Error::ReadingHeader)?; - let table = feature_table.as_mut().unwrap(); - for entry in data.chunks_exact(FEATURE_NAME_ENTRY_SIZE) { - 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 - f.seek(SeekFrom::Current(ext_length as i64)) - .map_err(Error::ReadingHeader)?; - } - } - - // Skip to the next 8 byte boundary - let padding = (8 - (ext_length % 8)) % 8; - f.seek(SeekFrom::Current(padding as i64)) - .map_err(Error::ReadingHeader)?; - } - - Ok(()) - } - - /// Creates a QcowHeader from a reference to a file. - pub fn new(f: &mut RawFile) -> Result { - f.rewind().map_err(Error::ReadingHeader)?; - let magic = u32::read_be(f).map_err(Error::ReadingHeader)?; - if magic != QCOW_MAGIC { - return Err(Error::InvalidMagic); - } - - // Reads the next u32 from the file. - fn read_u32_be(f: &mut RawFile) -> Result { - u32::read_be(f).map_err(Error::ReadingHeader) - } - - // Reads the next u64 from the file. - fn read_u64_be(f: &mut RawFile) -> Result { - u64::read_be(f).map_err(Error::ReadingHeader) - } - - let version = read_u32_be(f)?; - - let mut header = QcowHeader { - magic, - version, - backing_file_offset: read_u64_be(f)?, - backing_file_size: read_u32_be(f)?, - cluster_bits: read_u32_be(f)?, - size: read_u64_be(f)?, - crypt_method: read_u32_be(f)?, - l1_size: read_u32_be(f)?, - l1_table_offset: read_u64_be(f)?, - refcount_table_offset: read_u64_be(f)?, - refcount_table_clusters: read_u32_be(f)?, - nb_snapshots: read_u32_be(f)?, - snapshots_offset: read_u64_be(f)?, - incompatible_features: if version == 2 { 0 } else { read_u64_be(f)? }, - compatible_features: if version == 2 { 0 } else { read_u64_be(f)? }, - autoclear_features: if version == 2 { 0 } else { read_u64_be(f)? }, - refcount_order: if version == 2 { - DEFAULT_REFCOUNT_ORDER - } else { - read_u32_be(f)? - }, - header_size: if version == 2 { - V2_BARE_HEADER_SIZE - } else { - read_u32_be(f)? - }, - compression_type: CompressionType::Zlib, - backing_file: None, - }; - if version == 3 && header.header_size > V3_BARE_HEADER_SIZE { - let raw_compression_type = read_u64_be(f)? >> (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 { - f.seek(SeekFrom::Start(header.backing_file_offset)) - .map_err(Error::ReadingHeader)?; - let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize]; - f.read_exact(&mut backing_file_name_bytes) - .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 { - 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 { - 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::() 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::() 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::() 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 `file`. - pub fn write_to(&self, file: &mut F) -> Result<()> { - // Writes the next u32 to the file. - fn write_u32_be(f: &mut F, value: u32) -> Result<()> { - u32::write_be(f, value).map_err(Error::WritingHeader) - } - - // Writes the next u64 to the file. - fn write_u64_be(f: &mut F, value: u64) -> Result<()> { - u64::write_be(f, value).map_err(Error::WritingHeader) - } - - write_u32_be(file, self.magic)?; - write_u32_be(file, self.version)?; - write_u64_be(file, self.backing_file_offset)?; - write_u32_be(file, self.backing_file_size)?; - write_u32_be(file, self.cluster_bits)?; - write_u64_be(file, self.size)?; - write_u32_be(file, self.crypt_method)?; - write_u32_be(file, self.l1_size)?; - write_u64_be(file, self.l1_table_offset)?; - write_u64_be(file, self.refcount_table_offset)?; - write_u32_be(file, self.refcount_table_clusters)?; - write_u32_be(file, self.nb_snapshots)?; - write_u64_be(file, self.snapshots_offset)?; - - if self.version == 3 { - write_u64_be(file, self.incompatible_features)?; - write_u64_be(file, self.compatible_features)?; - write_u64_be(file, self.autoclear_features)?; - write_u32_be(file, self.refcount_order)?; - write_u32_be(file, self.header_size)?; - - if self.header_size > V3_BARE_HEADER_SIZE { - write_u64_be(file, 0)?; // no compression - } - - write_u32_be(file, 0)?; // header extension type: end of header extension area - write_u32_be(file, 0)?; // length of header extension data: 0 - } - - if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) { - if self.backing_file_offset > 0 { - file.seek(SeekFrom::Start(self.backing_file_offset)) - .map_err(Error::WritingHeader)?; - } - write!(file, "{backing_file_path}").map_err(Error::WritingHeader)?; - } - - // Set the file length by seeking and writing a zero to the last byte. This avoids needing - // a `File` instead of anything that implements seek as the `file` argument. - // Zeros out 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; - file.seek(SeekFrom::Start( - self.refcount_table_offset + refcount_blocks_size - 2, - )) - .map_err(Error::WritingHeader)?; - file.write(&[0u8]).map_err(Error::WritingHeader)?; - - Ok(()) - } - - /// Write only the incompatible_features field to the file at its fixed offset. - fn write_incompatible_features(&self, file: &mut F) -> Result<()> { - if self.version != 3 { - return Ok(()); - } - file.seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) - .map_err(Error::WritingHeader)?; - u64::write_be(file, self.incompatible_features).map_err(Error::WritingHeader)?; - 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 F, - dirty: bool, - ) -> Result<()> { - 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(Error::SyncingHeader)?; - } - 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 F) -> Result<()> { - if self.version == 3 { - self.incompatible_features |= IncompatFeatures::CORRUPT.bits(); - self.write_incompatible_features(file)?; - file.fsync().map_err(Error::SyncingHeader)?; - } - 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 F, - ) -> Result<()> { - if self.version == 3 && self.autoclear_features != 0 { - self.autoclear_features = 0; - file.seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET)) - .map_err(Error::WritingHeader)?; - u64::write_be(file, 0).map_err(Error::WritingHeader)?; - file.fsync().map_err(Error::SyncingHeader)?; - } - Ok(()) - } -} - -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 -} - trait BackingFileOps: Send + Seek + Read { fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { self.seek(SeekFrom::Start(address))?; @@ -2561,14 +2008,6 @@ impl BlockBackend for QcowFile { } } -// Returns an Error if the given offset doesn't align to a cluster boundary. -fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> { - if offset & ((0x01 << cluster_bits) - 1) != 0 { - return Err(Error::InvalidOffset(offset)); - } - Ok(()) -} - // Ceiling of the division of `dividend`/`divisor`. fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 { dividend / divisor + u64::from(!dividend.is_multiple_of(divisor)) From 63db385c3c93c7a1ed44e5405315cc769396f5fc Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 22 Feb 2026 13:07:13 +0100 Subject: [PATCH 0671/1893] 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 --- block/src/qcow/mod.rs | 75 +++++---------------------------------- block/src/qcow/util.rs | 79 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 67 deletions(-) create mode 100644 block/src/qcow/util.rs diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index dd0f154f61..83eb5a4bbc 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -9,6 +9,7 @@ mod header; mod qcow_raw_file; mod raw_file; mod refcount; +mod util; mod vec_cache; use std::cmp::{max, min}; @@ -37,6 +38,12 @@ use libc::{EINVAL, EIO, ENOSPC}; use log::{error, warn}; use remain::sorted; use thiserror::Error; +pub(crate) use util::MAX_NESTING_DEPTH; +use util::{ + L1_TABLE_OFFSET_MASK, L2_TABLE_OFFSET_MASK, div_round_up_u32, div_round_up_u64, l1_entry_make, + l2_entry_compressed_cluster_layout, l2_entry_is_compressed, l2_entry_is_empty, + l2_entry_is_zero, l2_entry_make_std, l2_entry_make_zero, l2_entry_std_cluster_addr, +}; use vmm_sys_util::file_traits::{FileSetLen, FileSync}; use vmm_sys_util::seek_hole::SeekHole; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; @@ -47,9 +54,6 @@ pub use crate::qcow::raw_file::RawFile; use crate::qcow::refcount::RefCount; use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache}; -/// Nesting depth limit for disk formats that can open other disk files. -pub(super) const MAX_NESTING_DEPTH: u32 = 10; - #[sorted] #[derive(Debug, Error)] pub enum Error { @@ -157,60 +161,6 @@ pub enum Error { pub type Result = std::result::Result; -// bits 0-8 and 56-63 are reserved. -const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; -const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; -// Flags -const ZERO_FLAG: u64 = 1 << 0; -const COMPRESSED_FLAG: u64 = 1 << 62; -const COMPRESSED_SECTOR_SIZE: u64 = 512; -const CLUSTER_USED_FLAG: u64 = 1 << 63; - -fn l2_entry_is_empty(l2_entry: u64) -> bool { - l2_entry == 0 -} - -// Check bit 0 - only valid for standard clusters. -fn l2_entry_is_zero(l2_entry: u64) -> bool { - l2_entry & ZERO_FLAG != 0 -} - -fn l2_entry_is_compressed(l2_entry: u64) -> bool { - l2_entry & COMPRESSED_FLAG != 0 -} - -// Get file offset and size of compressed cluster data -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 -fn l2_entry_std_cluster_addr(l2_entry: u64) -> u64 { - l2_entry & L2_TABLE_OFFSET_MASK -} - -// Make L2 entry for standard (non-compressed) cluster -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 -fn l2_entry_make_zero(cluster_addr: u64) -> u64 { - (cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG | ZERO_FLAG -} - -// Make L1 entry with optional flags -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) -} - trait BackingFileOps: Send + Seek + Read { fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { self.seek(SeekFrom::Start(address))?; @@ -2008,16 +1958,6 @@ impl BlockBackend for QcowFile { } } -// Ceiling of the division of `dividend`/`divisor`. -fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 { - dividend / divisor + u64::from(!dividend.is_multiple_of(divisor)) -} - -// Ceiling of the division of `dividend`/`divisor`. -fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 { - dividend / divisor + u32::from(!dividend.is_multiple_of(divisor)) -} - fn convert_copy(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()> where R: Read + Seek, @@ -2153,6 +2093,7 @@ mod unit_tests { use vmm_sys_util::tempfile::TempFile; use vmm_sys_util::write_zeroes::WriteZeroes; + use super::util::{COMPRESSED_FLAG, ZERO_FLAG}; use super::*; fn valid_header_v3() -> Vec { diff --git a/block/src/qcow/util.rs b/block/src/qcow/util.rs new file mode 100644 index 0000000000..bc8d017725 --- /dev/null +++ b/block/src/qcow/util.rs @@ -0,0 +1,79 @@ +// 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 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)) +} From 9d686b08667e7410aba0a9259f329e8e759e5bda Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 21 Feb 2026 16:18:10 +0100 Subject: [PATCH 0672/1893] 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 --- block/src/qcow/metadata.rs | 924 +++++++++++++++++++++++++++++++++++++ block/src/qcow/mod.rs | 425 +++++++++-------- 2 files changed, 1146 insertions(+), 203 deletions(-) create mode 100644 block/src/qcow/metadata.rs diff --git a/block/src/qcow/metadata.rs b/block/src/qcow/metadata.rs new file mode 100644 index 0000000000..88077236ca --- /dev/null +++ b/block/src/qcow/metadata.rs @@ -0,0 +1,924 @@ +// 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 metadata with lock based synchronization. +//! +//! QcowMetadata wraps the in memory QCOW2 metadata tables behind a single +//! coarse RwLock. This separates metadata lookup from data I/O, allowing +//! data reads and writes to proceed without holding the metadata lock. +//! +//! On L2 cache hit, map_clusters_for_read only needs a read lock with +//! pure shared reference access on the cache. Cache misses and all write +//! operations upgrade to a write lock. + +use std::cmp::min; +use std::io::{self, Seek}; +use std::mem; +use std::sync::RwLock; + +use libc::{EINVAL, EIO}; + +use super::qcow_raw_file::QcowRawFile; +use super::refcount::RefCount; +use super::util::{ + l2_entry_compressed_cluster_layout, l2_entry_is_compressed, l2_entry_is_empty, + l2_entry_is_zero, l2_entry_make_std, l2_entry_make_zero, l2_entry_std_cluster_addr, +}; +use super::vec_cache::{CacheMap, Cacheable, VecCache}; +use super::{QcowHeader, refcount}; + +/// Describes how to satisfy a guest read for a single cluster region. +/// +/// Returned by QcowMetadata::map_clusters_for_read. The caller performs +/// the actual data I/O using its own per queue file descriptor without +/// holding the metadata lock. +#[derive(Debug)] +pub enum ClusterReadMapping { + /// The cluster is not allocated and the guest should see zeros. + /// This covers both truly unallocated clusters where the L1 or L2 + /// entry is zero and clusters with the ZERO flag set. + Zero { length: u64 }, + + /// The cluster is allocated at the given host file offset. + /// The offset is the exact byte position combining cluster base and + /// intra cluster offset. The length is the number of bytes to read, + /// bounded by cluster boundary and guest request. + Allocated { offset: u64, length: u64 }, + + /// The cluster is compressed. The decompressed data is returned inline + /// because decompression is a CPU only operation that was done under the + /// write lock to access the raw compressed bytes from disk. + /// + /// The data field contains exactly the bytes the guest requested, already + /// sliced from the decompressed cluster. + Compressed { data: Vec }, + + /// The cluster is not allocated in this layer but may exist in a backing + /// file. The caller should delegate to the backing file at the given + /// guest offset for the specified length in bytes. + Backing { offset: u64, length: u64 }, +} + +/// Describes how to satisfy a guest write for a single cluster region. +/// +/// Returned by QcowMetadata::map_cluster_for_write. The caller performs +/// the actual data I/O using its own per queue file descriptor without +/// holding the metadata lock. +#[derive(Debug)] +pub enum ClusterWriteMapping { + /// The write target is at the given host file offset. + /// This covers both already allocated clusters and freshly allocated ones. + /// The offset is the exact byte position combining cluster base and + /// intra cluster offset. + Allocated { offset: u64 }, +} + +/// Trait for reading from a backing file in a thread safe manner. +/// +/// Used by QcowMetadata::deallocate_bytes so it can read COW data +/// from the backing file without knowing the concrete backing type. +pub(crate) trait BackingRead: Send + Sync { + fn read_at(&self, address: u64, buf: &mut [u8]) -> io::Result<()>; +} + +/// Action that the caller must perform after deallocate_bytes. +#[derive(Debug)] +pub enum DeallocAction { + /// Punch a hole at the given host file offset for a full cluster. + PunchHole { host_offset: u64, length: u64 }, + /// Write zeros at the given host file offset for a partial cluster. + WriteZeroes { host_offset: u64, length: usize }, +} + +/// Shared QCOW2 metadata protected by a coarse RwLock. +/// +/// Holds the L1 table, L2 cache and refcount state in memory. L2 table +/// entries and refcount blocks are read from disk on cache miss and +/// written back on eviction or when dirty. +/// +/// One instance is shared via Arc across all virtio blk queues. Each +/// queue holds its own QcowRawFile clone for data I/O. +/// +/// Steady state guest I/O is read dominant at the metadata level. Every +/// read and every write to an already allocated cluster only needs an +/// L1 to L2 lookup, which completes under a shared read lock. Only +/// cluster allocation, L2 cache eviction and resize take the exclusive +/// write lock, so contention stays low and queues scale. +pub struct QcowMetadata { + inner: RwLock, +} + +/// The actual metadata state, accessible only through the RwLock. +pub(crate) struct QcowState { + pub(crate) header: QcowHeader, + pub(crate) l1_table: VecCache, + pub(crate) l2_entries: u64, + pub(crate) l2_cache: CacheMap>, + pub(crate) refcounts: RefCount, + pub(crate) avail_clusters: Vec, + pub(crate) unref_clusters: Vec, + /// Dedicated file descriptor for metadata I/O covering L2 table reads, + /// refcount block reads and dirty eviction writes. This is a dup clone + /// of the original fd, separate from the per queue data I/O fds. + pub(crate) raw_file: QcowRawFile, +} + +impl QcowMetadata { + pub(super) fn new(inner: QcowState) -> Self { + QcowMetadata { + inner: RwLock::new(inner), + } + } + + /// Maps a multicluster guest read range to a list of read mappings. + /// + /// This walks the range in cluster sized steps under a single lock + /// acquisition, reducing lock roundtrips for large reads. The returned + /// mappings are ordered by guest address and ready for io_uring + /// submission. The caller can coalesce adjacent allocated entries into + /// fewer submissions. + /// + /// On the read lock fast path, if all L2 tables are cached, the lookup + /// is pure memory access with no I/O and concurrent readers are allowed. + /// + /// On the write lock slow path, if an L2 cache miss occurs, the L2 + /// table is read from disk via the metadata fd, the cache is populated + /// and the mapping is returned. + /// + /// The has_backing_file flag indicates whether a backing file exists, + /// needed to distinguish zero versus backing for unallocated clusters. + pub fn map_clusters_for_read( + &self, + address: u64, + total_length: usize, + has_backing_file: bool, + ) -> io::Result> { + let inner = self.inner.read().unwrap(); + let cluster_size = inner.raw_file.cluster_size(); + let mut mappings = Vec::new(); + let mut mapped = 0usize; + let mut need_write_lock = false; + + // Fast path, try all chunks under read lock + while mapped < total_length { + let curr_addr = address + mapped as u64; + let offset_in_cluster = inner.raw_file.cluster_offset(curr_addr) as usize; + let count = min( + total_length - mapped, + cluster_size as usize - offset_in_cluster, + ); + + match inner.try_map_read(curr_addr, count, has_backing_file)? { + Some(mapping) => mappings.push(mapping), + None => { + need_write_lock = true; + break; + } + } + mapped += count; + } + + if !need_write_lock { + return Ok(mappings); + } + + // Slow path, drop read lock, take write lock, redo from where we stopped + drop(inner); + let mut inner = self.inner.write().unwrap(); + + // Remap everything under write lock for consistency since the L2 cache + // may have been evicted between the read to write lock transition. + mappings.clear(); + mapped = 0; + + while mapped < total_length { + let curr_addr = address + mapped as u64; + let offset_in_cluster = inner.raw_file.cluster_offset(curr_addr) as usize; + let count = min( + total_length - mapped, + cluster_size as usize - offset_in_cluster, + ); + + mappings.push(inner.map_read_with_populate(curr_addr, count, has_backing_file)?); + mapped += count; + } + + Ok(mappings) + } + + /// Maps a guest write address to a write mapping. + /// + /// Always takes a write lock since writes may need to allocate clusters, + /// update L2 entries and update refcounts. + /// + /// The backing_data parameter is the COW source. If the cluster is + /// unallocated and a backing file exists, the caller should have already + /// read the backing cluster data and pass it here. If None, the new + /// cluster is zeroed. + pub fn map_cluster_for_write( + &self, + address: u64, + backing_data: Option>, + ) -> io::Result { + let mut inner = self.inner.write().unwrap(); + inner.map_write(address, backing_data) + } + + pub fn flush(&self) -> io::Result<()> { + let mut inner = self.inner.write().unwrap(); + inner.sync_caches()?; + let mut unref = mem::take(&mut inner.unref_clusters); + inner.avail_clusters.append(&mut unref); + Ok(()) + } + + /// Deallocates a range of bytes. Full clusters are deallocated via metadata. + /// Partial clusters need the caller to write zeros. This method returns a + /// list of actions the caller should take. + pub(crate) fn deallocate_bytes( + &self, + address: u64, + length: usize, + sparse: bool, + virtual_size: u64, + cluster_size: u64, + backing_file: Option<&dyn BackingRead>, + ) -> io::Result> { + let mut inner = self.inner.write().unwrap(); + let mut actions = Vec::new(); + + let file_end = virtual_size; + let remaining_in_file = file_end.saturating_sub(address); + let write_count = min(length as u64, remaining_in_file) as usize; + + let mut nwritten = 0usize; + while nwritten < write_count { + let curr_addr = address + nwritten as u64; + let offset_in_cluster = inner.raw_file.cluster_offset(curr_addr) as usize; + let count = min( + write_count - nwritten, + cluster_size as usize - offset_in_cluster, + ); + + if count == cluster_size as usize { + let punch_offset = inner.deallocate_cluster(curr_addr, sparse)?; + if let Some(host_offset) = punch_offset { + actions.push(DeallocAction::PunchHole { + host_offset, + length: cluster_size, + }); + } + } else { + // Partial cluster - COW from backing to preserve non zeroed bytes, + // then the caller writes zeros to the partial range. + let backing_data = if let Some(backing) = backing_file { + let cluster_begin = curr_addr - offset_in_cluster as u64; + let mut data = vec![0u8; cluster_size as usize]; + backing.read_at(cluster_begin, &mut data)?; + Some(data) + } else { + None + }; + let mapping = inner.map_write(curr_addr, backing_data)?; + let ClusterWriteMapping::Allocated { offset } = mapping; + actions.push(DeallocAction::WriteZeroes { + host_offset: offset, + length: count, + }); + } + + nwritten += count; + } + Ok(actions) + } + + pub fn virtual_size(&self) -> u64 { + self.inner.read().unwrap().header.size + } + + pub fn cluster_size(&self) -> u64 { + self.inner.read().unwrap().raw_file.cluster_size() + } + + /// Returns the intra cluster byte offset for a given guest address. + pub fn cluster_offset(&self, address: u64) -> u64 { + self.inner.read().unwrap().raw_file.cluster_offset(address) + } +} + +impl QcowState { + /// Fast path read mapping under read lock only. Returns None on cache + /// miss. + /// + /// All access here is through shared reference. CacheMap::get, + /// VecCache::get and index operations are all shared reference compatible. + fn try_map_read( + &self, + address: u64, + count: usize, + has_backing_file: bool, + ) -> io::Result> { + if address >= self.header.size { + return Err(io::Error::from_raw_os_error(EINVAL)); + } + + let l1_index = self.l1_table_index(address) as usize; + let l2_addr_disk = match self.l1_table.get(l1_index) { + Some(&addr) => addr, + None => return Err(io::Error::from_raw_os_error(EINVAL)), + }; + + if l2_addr_disk == 0 { + return Ok(Some(self.unallocated_read_mapping( + address, + count, + has_backing_file, + ))); + } + + let l2_table = match self.l2_cache.get(l1_index) { + Some(table) => table, + None => return Ok(None), // cache miss, need write lock + }; + + let l2_index = self.l2_table_index(address) as usize; + let l2_entry = l2_table[l2_index]; + + // Compressed entries require disk I/O for decompression - can't do + // that under a read lock. Fall through to the write lock path. + if l2_entry_is_compressed(l2_entry) { + return Ok(None); + } + + if l2_entry_is_empty(l2_entry) { + Ok(Some(self.unallocated_read_mapping( + address, + count, + has_backing_file, + ))) + } else if l2_entry_is_zero(l2_entry) { + // Match original QcowFile::file_read semantics where zero flagged + // entries fall through to backing file when one exists or return + // zeros otherwise. + Ok(Some(self.unallocated_read_mapping( + address, + count, + has_backing_file, + ))) + } else { + let cluster_addr = l2_entry_std_cluster_addr(l2_entry); + let cluster_size = self.raw_file.cluster_size(); + if cluster_addr & (cluster_size - 1) != 0 { + // Fall through to write lock path which sets the corrupt bit + return Ok(None); + } + let intra_offset = self.raw_file.cluster_offset(address); + Ok(Some(ClusterReadMapping::Allocated { + offset: cluster_addr + intra_offset, + length: count as u64, + })) + } + } + + /// Slow path read mapping. Requires exclusive access to populate cache. + fn map_read_with_populate( + &mut self, + address: u64, + count: usize, + has_backing_file: bool, + ) -> io::Result { + if address >= self.header.size { + return Err(io::Error::from_raw_os_error(EINVAL)); + } + + let l1_index = self.l1_table_index(address) as usize; + let l2_addr_disk = match self.l1_table.get(l1_index) { + Some(&addr) => addr, + None => return Err(io::Error::from_raw_os_error(EINVAL)), + }; + + if l2_addr_disk == 0 { + return Ok(self.unallocated_read_mapping(address, count, has_backing_file)); + } + + // Populate cache if needed as this does I/O via the metadata raw file + self.cache_l2_cluster(l1_index, l2_addr_disk)?; + + let l2_index = self.l2_table_index(address) as usize; + let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; + + if l2_entry_is_empty(l2_entry) { + Ok(self.unallocated_read_mapping(address, count, has_backing_file)) + } else if l2_entry_is_compressed(l2_entry) { + // Under write lock we can do I/O for decompression + let decompressed = self.decompress_l2_cluster(l2_entry)?; + let start = self.raw_file.cluster_offset(address) as usize; + let end = start + .checked_add(count) + .ok_or_else(|| io::Error::from_raw_os_error(EINVAL))?; + if end > decompressed.len() { + return Err(io::Error::from_raw_os_error(EINVAL)); + } + Ok(ClusterReadMapping::Compressed { + data: decompressed[start..end].to_vec(), + }) + } else if l2_entry_is_zero(l2_entry) { + // Match original QcowFile::file_read semantics where zero flagged + // entries fall through to backing file when one exists or return + // zeros otherwise. + Ok(self.unallocated_read_mapping(address, count, has_backing_file)) + } else { + let cluster_addr = l2_entry_std_cluster_addr(l2_entry); + let cluster_size = self.raw_file.cluster_size(); + if cluster_addr & (cluster_size - 1) != 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + let intra_offset = self.raw_file.cluster_offset(address); + Ok(ClusterReadMapping::Allocated { + offset: cluster_addr + intra_offset, + length: count as u64, + }) + } + } + + fn unallocated_read_mapping( + &self, + address: u64, + count: usize, + has_backing_file: bool, + ) -> ClusterReadMapping { + if has_backing_file { + ClusterReadMapping::Backing { + offset: address, + length: count as u64, + } + } else { + ClusterReadMapping::Zero { + length: count as u64, + } + } + } + + /// Write path mapping. Always called under write lock. + fn map_write( + &mut self, + address: u64, + backing_data: Option>, + ) -> io::Result { + if address >= self.header.size { + return Err(io::Error::from_raw_os_error(EINVAL)); + } + + let l1_index = self.l1_table_index(address) as usize; + let l2_addr_disk = match self.l1_table.get(l1_index) { + Some(&addr) => addr, + None => return Err(io::Error::from_raw_os_error(EINVAL)), + }; + let l2_index = self.l2_table_index(address) as usize; + + let mut set_refcounts = Vec::new(); + + if let Some(new_addr) = self.cache_l2_cluster_alloc(l1_index, l2_addr_disk)? { + set_refcounts.push((new_addr, 1)); + } + + let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; + let cluster_addr = if l2_entry_is_compressed(l2_entry) { + let decompressed_cluster = self.decompress_l2_cluster(l2_entry)?; + let cluster_addr = self.append_data_cluster(None)?; + self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?; + self.raw_file + .file_mut() + .seek(io::SeekFrom::Start(cluster_addr))?; + let nwritten = io::Write::write(self.raw_file.file_mut(), &decompressed_cluster)?; + if nwritten != decompressed_cluster.len() { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + self.deallocate_compressed_cluster(l2_entry)?; + cluster_addr + } else if l2_entry_is_empty(l2_entry) || l2_entry_is_zero(l2_entry) { + let cluster_addr = self.append_data_cluster(backing_data)?; + self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?; + cluster_addr + } else { + // Already allocated - validate alignment + let cluster_addr = l2_entry_std_cluster_addr(l2_entry); + if cluster_addr & (self.raw_file.cluster_size() - 1) != 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + cluster_addr + }; + + // Apply deferred refcount updates + for (addr, refcount) in set_refcounts { + self.set_cluster_refcount_track_freed(addr, refcount)?; + } + + let intra_offset = self.raw_file.cluster_offset(address); + Ok(ClusterWriteMapping::Allocated { + offset: cluster_addr + intra_offset, + }) + } + + // -- Address computation helpers -- + + fn l1_table_index(&self, address: u64) -> u64 { + (address / self.raw_file.cluster_size()) / self.l2_entries + } + + fn l2_table_index(&self, address: u64) -> u64 { + (address / self.raw_file.cluster_size()) % self.l2_entries + } + + // -- Cache and allocation operations requiring exclusive access -- + + /// Populates the L2 cache for read operations without allocation. + fn cache_l2_cluster(&mut self, l1_index: usize, l2_addr_disk: u64) -> io::Result<()> { + if !self.l2_cache.contains_key(l1_index) { + let cluster_size = self.raw_file.cluster_size(); + if l2_addr_disk & (cluster_size - 1) != 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + let l2_table = + VecCache::from_vec(self.raw_file.read_pointer_cluster(l2_addr_disk, None)?); + let l1_table = &self.l1_table; + let raw_file = &mut self.raw_file; + self.l2_cache.insert(l1_index, l2_table, |index, evicted| { + raw_file.write_pointer_table_direct(l1_table[index], evicted.iter()) + })?; + } + Ok(()) + } + + /// Populates the L2 cache for write operations and may allocate a new + /// L2 table. Returns the address of the newly allocated cluster if any. + fn cache_l2_cluster_alloc( + &mut self, + l1_index: usize, + l2_addr_disk: u64, + ) -> io::Result> { + let mut new_cluster: Option = None; + if !self.l2_cache.contains_key(l1_index) { + let l2_table = if l2_addr_disk == 0 { + // Allocate a new cluster to store the L2 table + let new_addr = self.get_new_cluster(None)?; + new_cluster = Some(new_addr); + self.l1_table[l1_index] = new_addr; + VecCache::new(self.l2_entries as usize) + } else { + let cluster_size = self.raw_file.cluster_size(); + if l2_addr_disk & (cluster_size - 1) != 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + VecCache::from_vec(self.raw_file.read_pointer_cluster(l2_addr_disk, None)?) + }; + let l1_table = &self.l1_table; + let raw_file = &mut self.raw_file; + self.l2_cache.insert(l1_index, l2_table, |index, evicted| { + raw_file.write_pointer_table_direct(l1_table[index], evicted.iter()) + })?; + } + Ok(new_cluster) + } + + /// Allocates a new cluster from the free list or by extending the file. + fn get_new_cluster(&mut self, initial_data: Option>) -> io::Result { + if let Some(free_cluster) = self.avail_clusters.pop() { + if free_cluster == 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + if let Some(initial_data) = initial_data { + self.raw_file.write_cluster(free_cluster, &initial_data)?; + } else { + self.raw_file.zero_cluster(free_cluster)?; + } + return Ok(free_cluster); + } + + let max_valid = self.refcounts.max_valid_cluster_offset(); + if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid)? { + if new_cluster == 0 { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + if let Some(initial_data) = initial_data { + self.raw_file.write_cluster(new_cluster, &initial_data)?; + } + Ok(new_cluster) + } else { + log::error!("No free clusters in get_new_cluster()"); + Err(io::Error::from_raw_os_error(libc::ENOSPC)) + } + } + + /// Allocates a data cluster and sets its refcount to 1. + fn append_data_cluster(&mut self, initial_data: Option>) -> io::Result { + let new_addr = self.get_new_cluster(initial_data)?; + self.set_cluster_refcount_track_freed(new_addr, 1)?; + Ok(new_addr) + } + + /// Updates the L1 and L2 tables to point to a new cluster address. + fn update_cluster_addr( + &mut self, + l1_index: usize, + l2_index: usize, + cluster_addr: u64, + set_refcounts: &mut Vec<(u64, u64)>, + ) -> io::Result<()> { + if !self.l2_cache.get(l1_index).unwrap().dirty() { + // Free the previously used cluster if one exists. Modified tables are always + // written to new clusters so the L1 table can be committed to disk after they + // are and L1 never points at an invalid table. + let addr = self.l1_table[l1_index]; + if addr != 0 { + self.unref_clusters.push(addr); + set_refcounts.push((addr, 0)); + } + + // Allocate a new cluster to store the L2 table and update the L1 table to point + // to the new table. The cluster will be written when the cache is flushed. + let new_addr = self.get_new_cluster(None)?; + set_refcounts.push((new_addr, 1)); + self.l1_table[l1_index] = new_addr; // marks l1_table dirty via IndexMut + } + // Write the L2 entry - IndexMut marks the L2 table dirty automatically. + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = l2_entry_make_std(cluster_addr); + Ok(()) + } + + /// Deallocates a cluster at the given guest address. + /// + /// If sparse is true, fully deallocates and returns the host offset if + /// the underlying storage should be punched after the refcount dropped + /// to zero. If sparse is false, uses the zero flag optimization when + /// possible. + /// + /// Returns None if no host punch_hole is needed. + pub(super) fn deallocate_cluster( + &mut self, + address: u64, + sparse: bool, + ) -> io::Result> { + if address >= self.header.size { + return Err(io::Error::from_raw_os_error(EINVAL)); + } + + let l1_index = self.l1_table_index(address) as usize; + let l2_addr_disk = match self.l1_table.get(l1_index) { + Some(&addr) => addr, + None => return Err(io::Error::from_raw_os_error(EINVAL)), + }; + let l2_index = self.l2_table_index(address) as usize; + + if l2_addr_disk == 0 { + return Ok(None); + } + + self.cache_l2_cluster(l1_index, l2_addr_disk)?; + + let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; + if l2_entry_is_empty(l2_entry) || l2_entry_is_zero(l2_entry) { + return Ok(None); + } + + if l2_entry_is_compressed(l2_entry) { + self.deallocate_compressed_cluster(l2_entry)?; + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = 0; + return Ok(None); + } + + let cluster_addr = l2_entry_std_cluster_addr(l2_entry); + let refcount = self + .refcounts + .get_cluster_refcount(&mut self.raw_file, cluster_addr) + .map_err(|e| { + if matches!(e, refcount::Error::RefblockUnaligned(_)) { + self.set_corrupt_bit_best_effort(); + } + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed to get cluster refcount: {e}"), + ) + })?; + if refcount == 0 { + return Err(io::Error::from_raw_os_error(EINVAL)); + } + + if sparse { + let new_refcount = refcount - 1; + self.set_cluster_refcount_track_freed(cluster_addr, new_refcount)?; + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = 0; + if new_refcount == 0 { + self.unref_clusters.push(cluster_addr); + return Ok(Some(cluster_addr)); + } + } else if refcount == 1 { + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = l2_entry_make_zero(cluster_addr); + } else { + self.set_cluster_refcount_track_freed(cluster_addr, refcount - 1)?; + self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = 0; + } + Ok(None) + } + + /// Sets refcount for a cluster, tracking any newly freed clusters. + fn set_cluster_refcount_track_freed(&mut self, address: u64, refcount: u64) -> io::Result<()> { + let mut newly_unref = self.set_cluster_refcount(address, refcount)?; + self.unref_clusters.append(&mut newly_unref); + Ok(()) + } + + /// Sets the refcount for a cluster. Returns freed cluster addresses. + fn set_cluster_refcount(&mut self, address: u64, refcount: u64) -> io::Result> { + let mut added_clusters = Vec::new(); + let mut unref_clusters = Vec::new(); + let mut refcount_set = false; + let mut new_cluster = None; + + while !refcount_set { + match self.refcounts.set_cluster_refcount( + &mut self.raw_file, + address, + refcount, + new_cluster.take(), + ) { + Ok(None) => { + refcount_set = true; + } + Ok(Some(freed_cluster)) => { + let mut freed = self.set_cluster_refcount(freed_cluster, 0)?; + unref_clusters.append(&mut freed); + refcount_set = true; + } + Err(refcount::Error::EvictingRefCounts(e)) => { + return Err(e); + } + Err(refcount::Error::InvalidIndex) => { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EINVAL)); + } + Err(refcount::Error::NeedCluster(addr)) => { + new_cluster = Some(( + addr, + VecCache::from_vec(self.raw_file.read_refcount_block(addr)?), + )); + } + Err(refcount::Error::NeedNewCluster) => { + let addr = self.get_new_cluster(None)?; + added_clusters.push(addr); + new_cluster = Some(( + addr, + VecCache::new(self.refcounts.refcounts_per_block() as usize), + )); + } + Err(refcount::Error::ReadingRefCounts(e)) => { + return Err(e); + } + Err(refcount::Error::RefcountOverflow { .. }) => { + return Err(io::Error::from_raw_os_error(EINVAL)); + } + Err(refcount::Error::RefblockUnaligned(_)) => { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + } + } + + for addr in added_clusters { + self.set_cluster_refcount(addr, 1)?; + } + Ok(unref_clusters) + } + + /// Flushes all dirty metadata to disk. + pub(super) fn sync_caches(&mut self) -> io::Result<()> { + use super::l1_entry_make; + + // Write out all dirty L2 tables. + for (l1_index, l2_table) in self.l2_cache.iter_mut().filter(|(_k, v)| v.dirty()) { + let addr = self.l1_table[*l1_index]; + if addr != 0 { + self.raw_file + .write_pointer_table_direct(addr, l2_table.iter())?; + } else { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EINVAL)); + } + l2_table.mark_clean(); + } + // Write the modified refcount blocks. + self.refcounts.flush_blocks(&mut self.raw_file)?; + // Sync metadata and data clusters. + self.raw_file.file_mut().sync_all()?; + + // Push L1 table and refcount table last. + let mut sync_required = if self.l1_table.dirty() { + let refcounts = &mut self.refcounts; + self.raw_file.write_pointer_table( + self.header.l1_table_offset, + self.l1_table.iter(), + |raw_file, l2_addr| { + if l2_addr == 0 { + Ok(0) + } else { + let refcount = refcounts + .get_cluster_refcount(raw_file, l2_addr) + .map_err(|e| io::Error::other(super::Error::GettingRefcount(e)))?; + Ok(l1_entry_make(l2_addr, refcount == 1)) + } + }, + )?; + self.l1_table.mark_clean(); + true + } else { + false + }; + sync_required |= self.refcounts.flush_table(&mut self.raw_file)?; + if sync_required { + self.raw_file.file_mut().sync_data()?; + } + + Ok(()) + } + + /// Decompresses a compressed cluster, returning the raw decompressed bytes. + fn decompress_l2_cluster(&mut self, l2_entry: u64) -> io::Result> { + let (compressed_addr, compressed_size) = + l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); + self.raw_file + .file_mut() + .seek(io::SeekFrom::Start(compressed_addr))?; + let mut compressed = vec![0u8; compressed_size]; + io::Read::read_exact(self.raw_file.file_mut(), &mut compressed)?; + let decoder = self.header.get_decoder(); + let cluster_size = self.raw_file.cluster_size() as usize; + let mut decompressed = vec![0u8; cluster_size]; + let decompressed_size = decoder + .decode(&compressed, &mut decompressed) + .map_err(|_| { + self.set_corrupt_bit_best_effort(); + io::Error::from_raw_os_error(EIO) + })?; + if decompressed_size as u64 != self.raw_file.cluster_size() { + self.set_corrupt_bit_best_effort(); + return Err(io::Error::from_raw_os_error(EIO)); + } + Ok(decompressed) + } + + /// Deallocates the clusters spanned by a compressed L2 entry. + fn deallocate_compressed_cluster(&mut self, l2_entry: u64) -> io::Result<()> { + let (compressed_addr, compressed_size) = + l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); + let cluster_size = self.raw_file.cluster_size(); + + // Calculate the end of the compressed data region + let compressed_clusters_end = self.raw_file.cluster_address( + compressed_addr // Start of compressed data + + compressed_size as u64 // Add size to get end address + + cluster_size + - 1, // Catch possibly partially used last cluster + ); + + // Decrement refcount for each cluster spanned by the compressed data + let mut addr = self.raw_file.cluster_address(compressed_addr); + while addr < compressed_clusters_end { + let refcount = self + .refcounts + .get_cluster_refcount(&mut self.raw_file, addr) + .map_err(|e| { + if matches!(e, refcount::Error::RefblockUnaligned(_)) { + self.set_corrupt_bit_best_effort(); + } + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed to get cluster refcount: {e}"), + ) + })?; + if refcount > 0 { + self.set_cluster_refcount_track_freed(addr, refcount - 1)?; + } + addr += cluster_size; + } + Ok(()) + } + + /// Best effort attempt to mark the image corrupt. + fn set_corrupt_bit_best_effort(&mut self) { + if let Err(e) = self.header.set_corrupt_bit(self.raw_file.file_mut()) { + log::warn!("Failed to persist corrupt bit: {e}"); + } + } +} diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 83eb5a4bbc..06dce2c4ce 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -6,6 +6,7 @@ mod decoder; mod header; +pub(crate) mod metadata; mod qcow_raw_file; mod raw_file; mod refcount; @@ -182,7 +183,7 @@ impl BackingFileOps for RawFile { } /// Backing file wrapper -struct BackingFile { +pub(crate) struct BackingFile { inner: Box, virtual_size: u64, } @@ -276,6 +277,209 @@ impl Debug for BackingFile { } } +/// Parses and validates a QCOW2 image file, returning the metadata, backing +/// file and sparse flag. +/// +/// This shared constructor is used by both QcowFile for sequential I/O +/// and QcowDiskSync for lock based parallel I/O. +pub(crate) fn parse_qcow( + mut file: RawFile, + max_nesting_depth: u32, + sparse: bool, +) -> Result<(metadata::QcowState, Option, bool)> { + let mut header = QcowHeader::new(&mut file)?; + + // Only v2 and v3 files are supported. + if header.version != 2 && header.version != 3 { + return Err(Error::UnsupportedVersion(header.version)); + } + + // Make sure that the L1 table fits in RAM. + if u64::from(header.l1_size) > MAX_RAM_POINTER_TABLE_SIZE { + return Err(Error::InvalidL1TableSize(header.l1_size)); + } + + let cluster_bits: u32 = header.cluster_bits; + if !(MIN_CLUSTER_BITS..=MAX_CLUSTER_BITS).contains(&cluster_bits) { + return Err(Error::InvalidClusterSize); + } + let cluster_size = 0x01u64 << cluster_bits; + + // Limit the total size of the disk. + if header.size > MAX_QCOW_FILE_SIZE { + return Err(Error::FileTooBig(header.size)); + } + + let direct_io = file.is_direct(); + + let backing_file = BackingFile::new( + header.backing_file.as_ref(), + direct_io, + max_nesting_depth, + sparse, + )?; + + // Validate refcount order to be 0..6 + let refcount_bits: u64 = 0x01u64 + .checked_shl(header.refcount_order) + .ok_or(Error::UnsupportedRefcountOrder)?; + if refcount_bits > 64 { + return Err(Error::UnsupportedRefcountOrder); + } + + // Need at least one refcount cluster + if header.refcount_table_clusters == 0 { + return Err(Error::NoRefcountClusters); + } + offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?; + offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?; + // refcount table must be a cluster boundary, and within the file's virtual or actual size. + offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?; + let file_size = file.metadata().map_err(Error::GettingFileSize)?.len(); + if header.refcount_table_offset > max(file_size, header.size) { + return Err(Error::RefcountTableOffEnd); + } + + // The first cluster should always have a non-zero refcount, so if it is 0, + // this is an old file with broken refcounts, which requires a rebuild. + let mut refcount_rebuild_required = true; + file.seek(SeekFrom::Start(header.refcount_table_offset)) + .map_err(Error::SeekingFile)?; + let first_refblock_addr = u64::read_be(&mut file).map_err(Error::ReadingHeader)?; + if first_refblock_addr != 0 { + file.seek(SeekFrom::Start(first_refblock_addr)) + .map_err(Error::SeekingFile)?; + let first_cluster_refcount = u16::read_be(&mut file).map_err(Error::ReadingHeader)?; + if first_cluster_refcount != 0 { + refcount_rebuild_required = false; + } + } + + if (header.compatible_features & COMPATIBLE_FEATURES_LAZY_REFCOUNTS) != 0 { + refcount_rebuild_required = true; + } + + let mut raw_file = + QcowRawFile::from(file, cluster_size, refcount_bits).ok_or(Error::InvalidClusterSize)?; + let is_writable = raw_file.file().is_writable(); + + if header.is_corrupt() { + if is_writable { + return Err(Error::CorruptImage); + } + let path = read_link(format!("/proc/self/fd/{}", raw_file.file().as_raw_fd())) + .map_or_else(|_| "".to_string(), |p| p.display().to_string()); + warn!("QCOW2 image is marked corrupt, opening read-only: {path}"); + } + + // Image already has dirty bit set. Refcounts may be invalid. + if IncompatFeatures::from_bits_truncate(header.incompatible_features) + .contains(IncompatFeatures::DIRTY) + { + log::warn!("QCOW2 image not cleanly closed, rebuilding refcounts"); + refcount_rebuild_required = true; + } + + // Skip refcount rebuilding for readonly files. + if refcount_rebuild_required && is_writable { + QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?; + } + + let entries_per_cluster = cluster_size / size_of::() as u64; + let num_clusters = div_round_up_u64(header.size, cluster_size); + let num_l2_clusters = div_round_up_u64(num_clusters, entries_per_cluster); + let l1_clusters = div_round_up_u64(num_l2_clusters, entries_per_cluster); + let header_clusters = div_round_up_u64(size_of::() as u64, cluster_size); + if num_l2_clusters > MAX_RAM_POINTER_TABLE_SIZE { + return Err(Error::TooManyL1Entries(num_l2_clusters)); + } + let l1_table = VecCache::from_vec( + raw_file + .read_pointer_table( + header.l1_table_offset, + num_l2_clusters, + Some(L1_TABLE_OFFSET_MASK), + ) + .map_err(Error::ReadingHeader)?, + ); + + let num_clusters = div_round_up_u64(header.size, cluster_size); + let refcount_clusters = max_refcount_clusters( + header.refcount_order, + cluster_size as u32, + (num_clusters + l1_clusters + num_l2_clusters + header_clusters) as u32, + ); + // Check that the given header doesn't have a suspiciously sized refcount table. + if u64::from(header.refcount_table_clusters) > 2 * refcount_clusters { + return Err(Error::RefcountTableTooLarge); + } + if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE { + return Err(Error::TooManyRefcounts(refcount_clusters)); + } + let refcount_block_entries = cluster_size * 8 / refcount_bits; + let mut refcounts = RefCount::new( + &mut raw_file, + header.refcount_table_offset, + refcount_clusters, + refcount_block_entries, + cluster_size, + refcount_bits, + ) + .map_err(Error::ReadingRefCounts)?; + + let l2_entries = cluster_size / size_of::() as u64; + + // Check that the L1 and refcount tables fit in a 64bit address space. + let l1_index = (header.size / cluster_size) / l2_entries; + header + .l1_table_offset + .checked_add(l1_index * size_of::() as u64) + .ok_or(Error::InvalidL1TableOffset)?; + header + .refcount_table_offset + .checked_add(u64::from(header.refcount_table_clusters) * cluster_size) + .ok_or(Error::InvalidRefcountTableOffset)?; + + // Find available (refcount == 0) clusters for the free list. + let file_size = raw_file + .file_mut() + .metadata() + .map_err(Error::GettingFileSize)? + .len(); + let mut avail_clusters = Vec::new(); + for i in (0..file_size).step_by(cluster_size as usize) { + let refcount = refcounts + .get_cluster_refcount(&mut raw_file, i) + .map_err(Error::GettingRefcount)?; + if refcount == 0 { + avail_clusters.push(i); + } + } + + if is_writable { + if !IncompatFeatures::from_bits_truncate(header.incompatible_features) + .contains(IncompatFeatures::DIRTY) + { + header.set_dirty_bit(raw_file.file_mut(), true)?; + } + + header.clear_autoclear_features(raw_file.file_mut())?; + } + + let inner = metadata::QcowState { + raw_file, + header, + l1_table, + l2_entries, + l2_cache: CacheMap::new(100), + refcounts, + avail_clusters, + unref_clusters: Vec::new(), + }; + + Ok((inner, backing_file, sparse)) +} + /// Represents a qcow2 file. This is a sparse file format maintained by the qemu project. /// Full documentation of the format can be found in the qemu repository. /// @@ -321,190 +525,34 @@ impl QcowFile { /// Creates a QcowFile from `file` and with a max nesting depth. File must be a valid qcow2 /// image. pub fn from_with_nesting_depth( - mut file: RawFile, + file: RawFile, max_nesting_depth: u32, sparse: bool, ) -> Result { - let header = QcowHeader::new(&mut file)?; - - // Only v2 and v3 files are supported. - if header.version != 2 && header.version != 3 { - return Err(Error::UnsupportedVersion(header.version)); - } - - // Make sure that the L1 table fits in RAM. - if u64::from(header.l1_size) > MAX_RAM_POINTER_TABLE_SIZE { - return Err(Error::InvalidL1TableSize(header.l1_size)); - } - - let cluster_bits: u32 = header.cluster_bits; - if !(MIN_CLUSTER_BITS..=MAX_CLUSTER_BITS).contains(&cluster_bits) { - return Err(Error::InvalidClusterSize); - } - let cluster_size = 0x01u64 << cluster_bits; - - // Limit the total size of the disk. - if header.size > MAX_QCOW_FILE_SIZE { - return Err(Error::FileTooBig(header.size)); - } - - let direct_io = file.is_direct(); - - let backing_file = BackingFile::new( - header.backing_file.as_ref(), - direct_io, - max_nesting_depth, - sparse, - )?; - - // Validate refcount order to be 0..6 - let refcount_bits: u64 = 0x01u64 - .checked_shl(header.refcount_order) - .ok_or(Error::UnsupportedRefcountOrder)?; - if refcount_bits > 64 { - return Err(Error::UnsupportedRefcountOrder); - } - - // Need at least one refcount cluster - if header.refcount_table_clusters == 0 { - return Err(Error::NoRefcountClusters); - } - offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?; - offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?; - // refcount table must be a cluster boundary, and within the file's virtual or actual size. - offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?; - let file_size = file.metadata().map_err(Error::GettingFileSize)?.len(); - if header.refcount_table_offset > max(file_size, header.size) { - return Err(Error::RefcountTableOffEnd); - } - - // The first cluster should always have a non-zero refcount, so if it is 0, - // this is an old file with broken refcounts, which requires a rebuild. - let mut refcount_rebuild_required = true; - file.seek(SeekFrom::Start(header.refcount_table_offset)) - .map_err(Error::SeekingFile)?; - let first_refblock_addr = u64::read_be(&mut file).map_err(Error::ReadingHeader)?; - if first_refblock_addr != 0 { - file.seek(SeekFrom::Start(first_refblock_addr)) - .map_err(Error::SeekingFile)?; - let first_cluster_refcount = u16::read_be(&mut file).map_err(Error::ReadingHeader)?; - if first_cluster_refcount != 0 { - refcount_rebuild_required = false; - } - } - - if (header.compatible_features & COMPATIBLE_FEATURES_LAZY_REFCOUNTS) != 0 { - refcount_rebuild_required = true; - } - - let mut raw_file = QcowRawFile::from(file, cluster_size, refcount_bits) - .ok_or(Error::InvalidClusterSize)?; - let is_writable = raw_file.file().is_writable(); - - if header.is_corrupt() { - if is_writable { - return Err(Error::CorruptImage); - } - let path = read_link(format!("/proc/self/fd/{}", raw_file.file().as_raw_fd())) - .map_or_else(|_| "".to_string(), |p| p.display().to_string()); - warn!("QCOW2 image is marked corrupt, opening read-only: {path}"); - } - - // Image already has dirty bit set. Refcounts may be invalid. - if IncompatFeatures::from_bits_truncate(header.incompatible_features) - .contains(IncompatFeatures::DIRTY) - { - log::warn!("QCOW2 image not cleanly closed, rebuilding refcounts"); - refcount_rebuild_required = true; - } - - // Skip refcount rebuilding for readonly files. - if refcount_rebuild_required && is_writable { - QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?; - } - - let entries_per_cluster = cluster_size / size_of::() as u64; - let num_clusters = div_round_up_u64(header.size, cluster_size); - let num_l2_clusters = div_round_up_u64(num_clusters, entries_per_cluster); - let l1_clusters = div_round_up_u64(num_l2_clusters, entries_per_cluster); - let header_clusters = div_round_up_u64(size_of::() as u64, cluster_size); - if num_l2_clusters > MAX_RAM_POINTER_TABLE_SIZE { - return Err(Error::TooManyL1Entries(num_l2_clusters)); - } - let l1_table = VecCache::from_vec( - raw_file - .read_pointer_table( - header.l1_table_offset, - num_l2_clusters, - Some(L1_TABLE_OFFSET_MASK), - ) - .map_err(Error::ReadingHeader)?, - ); - - let num_clusters = div_round_up_u64(header.size, cluster_size); - let refcount_clusters = max_refcount_clusters( - header.refcount_order, - cluster_size as u32, - (num_clusters + l1_clusters + num_l2_clusters + header_clusters) as u32, - ); - // Check that the given header doesn't have a suspiciously sized refcount table. - if u64::from(header.refcount_table_clusters) > 2 * refcount_clusters { - return Err(Error::RefcountTableTooLarge); - } - if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE { - return Err(Error::TooManyRefcounts(refcount_clusters)); - } - let refcount_block_entries = cluster_size * 8 / refcount_bits; - let refcounts = RefCount::new( - &mut raw_file, - header.refcount_table_offset, - refcount_clusters, - refcount_block_entries, - cluster_size, - refcount_bits, - ) - .map_err(Error::ReadingRefCounts)?; - - let l2_entries = cluster_size / size_of::() as u64; - - let mut qcow = QcowFile { + let (inner, backing_file, sparse) = parse_qcow(file, max_nesting_depth, sparse)?; + let metadata::QcowState { + raw_file, + header, + l1_table, + l2_entries, + l2_cache, + refcounts, + avail_clusters, + unref_clusters, + } = inner; + Ok(QcowFile { raw_file, header, l1_table, l2_entries, - l2_cache: CacheMap::new(100), + l2_cache, refcounts, current_offset: 0, - unref_clusters: Vec::new(), - avail_clusters: Vec::new(), + unref_clusters, + avail_clusters, backing_file, sparse, - }; - - // Check that the L1 and refcount tables fit in a 64bit address space. - qcow.header - .l1_table_offset - .checked_add(qcow.l1_address_offset(qcow.virtual_size())) - .ok_or(Error::InvalidL1TableOffset)?; - qcow.header - .refcount_table_offset - .checked_add(u64::from(qcow.header.refcount_table_clusters) * cluster_size) - .ok_or(Error::InvalidRefcountTableOffset)?; - - qcow.find_avail_clusters()?; - - if is_writable { - if !IncompatFeatures::from_bits_truncate(qcow.header.incompatible_features) - .contains(IncompatFeatures::DIRTY) - { - qcow.header.set_dirty_bit(qcow.raw_file.file_mut(), true)?; - } - - qcow.header - .clear_autoclear_features(qcow.raw_file.file_mut())?; - } - - Ok(qcow) + }) } /// Creates a new QcowFile at the given path. @@ -779,29 +827,6 @@ impl QcowFile { Ok(()) } - fn find_avail_clusters(&mut self) -> Result<()> { - let cluster_size = self.raw_file.cluster_size(); - - let file_size = self - .raw_file - .file_mut() - .metadata() - .map_err(Error::GettingFileSize)? - .len(); - - for i in (0..file_size).step_by(cluster_size as usize) { - let refcount = self - .refcounts - .get_cluster_refcount(&mut self.raw_file, i) - .map_err(Error::GettingRefcount)?; - if refcount == 0 { - self.avail_clusters.push(i); - } - } - - Ok(()) - } - /// Rebuild the reference count tables. fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> Result<()> { fn add_ref( @@ -1130,12 +1155,6 @@ impl QcowFile { self.header.size } - // Gets the offset of `address` in the L1 table. - fn l1_address_offset(&self, address: u64) -> u64 { - let l1_index = self.l1_table_index(address); - l1_index * size_of::() as u64 - } - // Gets the offset of `address` in the L1 table. fn l1_table_index(&self, address: u64) -> u64 { (address / self.raw_file.cluster_size()) / self.l2_entries From a4a5b19f64a0f6322042e110166e528c3f648c78 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 17 Feb 2026 22:59:21 +0100 Subject: [PATCH 0673/1893] 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 --- block/src/qcow/metadata.rs | 121 +++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 4 deletions(-) diff --git a/block/src/qcow/metadata.rs b/block/src/qcow/metadata.rs index 88077236ca..c78a2af0be 100644 --- a/block/src/qcow/metadata.rs +++ b/block/src/qcow/metadata.rs @@ -26,8 +26,9 @@ use libc::{EINVAL, EIO}; use super::qcow_raw_file::QcowRawFile; use super::refcount::RefCount; use super::util::{ - l2_entry_compressed_cluster_layout, l2_entry_is_compressed, l2_entry_is_empty, - l2_entry_is_zero, l2_entry_make_std, l2_entry_make_zero, l2_entry_std_cluster_addr, + div_round_up_u64, l1_entry_make, l2_entry_compressed_cluster_layout, l2_entry_is_compressed, + l2_entry_is_empty, l2_entry_is_zero, l2_entry_make_std, l2_entry_make_zero, + l2_entry_std_cluster_addr, }; use super::vec_cache::{CacheMap, Cacheable, VecCache}; use super::{QcowHeader, refcount}; @@ -237,6 +238,16 @@ impl QcowMetadata { Ok(()) } + /// Resizes the QCOW2 image to the given new size. Only grow is + /// supported, shrink would require walking all L2 tables to reclaim + /// clusters beyond the new size and risks data loss. + /// + /// Returns an error if the new size is smaller than the current size. + pub fn resize(&self, new_size: u64) -> io::Result<()> { + let mut inner = self.inner.write().unwrap(); + inner.resize(new_size) + } + /// Deallocates a range of bytes. Full clusters are deallocated via metadata. /// Partial clusters need the caller to write zeros. This method returns a /// list of actions the caller should take. @@ -658,6 +669,110 @@ impl QcowState { Ok(()) } + /// Resizes the image to the given new size. Only grow is supported, + /// shrink would require walking all L2 tables to reclaim clusters + /// beyond the new size and risks data loss. + fn resize(&mut self, new_size: u64) -> io::Result<()> { + let current_size = self.header.size; + + if new_size == current_size { + return Ok(()); + } + + if new_size < current_size { + return Err(io::Error::other("shrinking QCOW2 images is not supported")); + } + + let cluster_size = self.raw_file.cluster_size(); + let entries_per_cluster = cluster_size / size_of::() as u64; + let new_clusters = div_round_up_u64(new_size, cluster_size); + let needed_l1_entries = div_round_up_u64(new_clusters, entries_per_cluster) as u32; + + if needed_l1_entries > self.header.l1_size { + self.grow_l1_table(needed_l1_entries)?; + } + + self.header.size = new_size; + + self.raw_file.file_mut().rewind()?; + self.header + .write_to(self.raw_file.file_mut()) + .map_err(|e| io::Error::other(format!("failed to write header during resize: {e}")))?; + + self.raw_file.file_mut().sync_all()?; + + Ok(()) + } + + /// Grows the L1 table to accommodate at least the requested number of entries. + fn grow_l1_table(&mut self, new_l1_size: u32) -> io::Result<()> { + let old_l1_size = self.header.l1_size; + let old_l1_offset = self.header.l1_table_offset; + let cluster_size = self.raw_file.cluster_size(); + + let new_l1_bytes = new_l1_size as u64 * size_of::() as u64; + let new_l1_clusters = div_round_up_u64(new_l1_bytes, cluster_size); + + // Allocate contiguous clusters at file end for new L1 table + let file_size = self.raw_file.file_mut().seek(io::SeekFrom::End(0))?; + let new_l1_offset = self.raw_file.cluster_address(file_size + cluster_size - 1); + + let new_file_end = new_l1_offset + new_l1_clusters * cluster_size; + self.raw_file.file_mut().set_len(new_file_end)?; + + // Set refcounts for the contiguous range + for i in 0..new_l1_clusters { + self.set_cluster_refcount_track_freed(new_l1_offset + i * cluster_size, 1)?; + } + + let mut new_l1_data = vec![0u64; new_l1_size as usize]; + let old_entries = self.l1_table.get_values(); + new_l1_data[..old_entries.len()].copy_from_slice(old_entries); + + for l2_addr in new_l1_data.iter_mut() { + if *l2_addr != 0 { + let refcount = self + .refcounts + .get_cluster_refcount(&mut self.raw_file, *l2_addr) + .map_err(|e| { + io::Error::other(format!("failed to get refcount during resize: {e}")) + })?; + *l2_addr = l1_entry_make(*l2_addr, refcount == 1); + } + } + + // Write the new L1 table to disk + self.raw_file + .write_pointer_table_direct(new_l1_offset, new_l1_data.iter())?; + + self.raw_file.file_mut().sync_all()?; + + self.header.l1_size = new_l1_size; + self.header.l1_table_offset = new_l1_offset; + + self.raw_file.file_mut().rewind()?; + self.header + .write_to(self.raw_file.file_mut()) + .map_err(|e| io::Error::other(format!("failed to write header during resize: {e}")))?; + + self.raw_file.file_mut().sync_all()?; + + // Free old L1 table clusters + let old_l1_bytes = old_l1_size as u64 * size_of::() as u64; + let old_l1_clusters = div_round_up_u64(old_l1_bytes, cluster_size); + for i in 0..old_l1_clusters { + let cluster_addr = old_l1_offset + i * cluster_size; + // Best effort: the old L1 clusters are no longer reachable, + // so a refcount update failure just leaks space. + let _ = self.set_cluster_refcount(cluster_addr, 0); + } + + // Update L1 table cache + self.l1_table.extend(new_l1_size as usize); + + Ok(()) + } + /// Deallocates a cluster at the given guest address. /// /// If sparse is true, fully deallocates and returns the host offset if @@ -804,8 +919,6 @@ impl QcowState { /// Flushes all dirty metadata to disk. pub(super) fn sync_caches(&mut self) -> io::Result<()> { - use super::l1_entry_make; - // Write out all dirty L2 tables. for (l1_index, l2_table) in self.l2_cache.iter_mut().filter(|(_k, v)| v.dirty()) { let addr = self.l1_table[*l1_index]; From 57e89b04f6d9e7da4d5d0b84d0021c9f1b706b7a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 21 Feb 2026 16:52:20 +0100 Subject: [PATCH 0674/1893] 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 --- block/src/qcow/metadata.rs | 33 +- block/src/qcow/mod.rs | 145 ++++-- block/src/qcow_sync.rs | 1005 ++++++++++++++++++++++++------------ 3 files changed, 816 insertions(+), 367 deletions(-) diff --git a/block/src/qcow/metadata.rs b/block/src/qcow/metadata.rs index c78a2af0be..b4b64cabd0 100644 --- a/block/src/qcow/metadata.rs +++ b/block/src/qcow/metadata.rs @@ -130,7 +130,7 @@ pub(crate) struct QcowState { } impl QcowMetadata { - pub(super) fn new(inner: QcowState) -> Self { + pub(crate) fn new(inner: QcowState) -> Self { QcowMetadata { inner: RwLock::new(inner), } @@ -238,6 +238,21 @@ impl QcowMetadata { Ok(()) } + /// Flushes dirty metadata caches and clears the dirty bit for + /// clean shutdown. + pub fn shutdown(&self) { + let mut inner = self.inner.write().unwrap(); + let _ = inner.sync_caches(); + let QcowState { + ref mut header, + ref mut raw_file, + .. + } = *inner; + if raw_file.file().is_writable() { + let _ = header.set_dirty_bit(raw_file.file_mut(), false); + } + } + /// Resizes the QCOW2 image to the given new size. Only grow is /// supported, shrink would require walking all L2 tables to reclaim /// clusters beyond the new size and risks data loss. @@ -260,6 +275,9 @@ impl QcowMetadata { cluster_size: u64, backing_file: Option<&dyn BackingRead>, ) -> io::Result> { + if address.checked_add(length as u64).is_none() { + return Ok(Vec::new()); + } let mut inner = self.inner.write().unwrap(); let mut actions = Vec::new(); @@ -476,6 +494,19 @@ impl QcowState { } } + /// Maps a single cluster region for a sequential read. + pub(crate) fn map_cluster_read( + &mut self, + address: u64, + count: usize, + has_backing_file: bool, + ) -> io::Result { + match self.try_map_read(address, count, has_backing_file)? { + Some(mapping) => Ok(mapping), + None => self.map_read_with_populate(address, count, has_backing_file), + } + } + /// Write path mapping. Always called under write lock. fn map_write( &mut self, diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 06dce2c4ce..c0b4e8c720 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -7,7 +7,7 @@ mod decoder; mod header; pub(crate) mod metadata; -mod qcow_raw_file; +pub(crate) mod qcow_raw_file; mod raw_file; mod refcount; mod util; @@ -37,6 +37,7 @@ use header::{ }; use libc::{EINVAL, EIO, ENOSPC}; use log::{error, warn}; +use metadata::ClusterReadMapping; use remain::sorted; use thiserror::Error; pub(crate) use util::MAX_NESTING_DEPTH; @@ -162,29 +163,22 @@ pub enum Error { pub type Result = std::result::Result; -trait BackingFileOps: Send + Seek + Read { - fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { - self.seek(SeekFrom::Start(address))?; - self.read_exact(buf) - } - fn clone_box(&self) -> Box; -} - -impl BackingFileOps for QcowFile { - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } -} - -impl BackingFileOps for RawFile { - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } +/// Concrete backing file variants. +pub(crate) enum BackingKind { + /// Raw backing file. + Raw(RawFile), + /// QCOW2 backing parsed into metadata and raw file. + Qcow { + inner: Box, + backing: Option>, + }, + /// Full QcowFile used as backing, only in tests. + #[cfg(test)] + QcowFile(Box), } - /// Backing file wrapper pub(crate) struct BackingFile { - inner: Box, + kind: BackingKind, virtual_size: u64, } @@ -217,56 +211,108 @@ impl BackingFile { None => detect_image_type(&mut raw_file)?, }; - let (inner, virtual_size): (Box, u64) = match backing_format { + let (kind, virtual_size) = match backing_format { ImageType::Raw => { let size = raw_file .seek(SeekFrom::End(0)) .map_err(Error::BackingFileIo)?; raw_file.rewind().map_err(Error::BackingFileIo)?; - (Box::new(raw_file), size) + (BackingKind::Raw(raw_file), size) } ImageType::Qcow2 => { - let backing_qcow = - QcowFile::from_with_nesting_depth(raw_file, max_nesting_depth - 1, sparse) + let (inner, nested_backing, _sparse) = + parse_qcow(raw_file, max_nesting_depth - 1, sparse) .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; - let size = backing_qcow.virtual_size(); - (Box::new(backing_qcow), size) + let size = inner.header.size; + ( + BackingKind::Qcow { + inner: Box::new(inner), + backing: nested_backing.map(Box::new), + }, + size, + ) } }; - Ok(Some(Self { - inner, - virtual_size, - })) + Ok(Some(Self { kind, virtual_size })) + } + + /// Consume and return the kind and virtual size. + pub(crate) fn into_kind(self) -> (BackingKind, u64) { + (self.kind, self.virtual_size) } /// Read from backing file, returning zeros for any portion beyond backing file size. #[inline] - fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { + pub(crate) fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { if address >= self.virtual_size { - // Entire read is beyond backing file buf.fill(0); return Ok(()); } let available = (self.virtual_size - address) as usize; - if available >= buf.len() { - // Entire read is within backing file - self.inner.read_at(address, buf) + let (target, overflow) = if available >= buf.len() { + (buf, &mut [][..]) } else { - // Partial read, fill the rest with zeroes - self.inner.read_at(address, &mut buf[..available])?; - buf[available..].fill(0); - Ok(()) - } + buf.split_at_mut(available) + }; + Self::read_at_inner(&mut self.kind, address, target)?; + overflow.fill(0); + Ok(()) } -} -impl Clone for BackingFile { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone_box(), - virtual_size: self.virtual_size, + fn read_at_inner(kind: &mut BackingKind, address: u64, buf: &mut [u8]) -> std::io::Result<()> { + match kind { + BackingKind::Raw(file) => { + file.seek(SeekFrom::Start(address))?; + file.read_exact(buf) + } + #[cfg(test)] + BackingKind::QcowFile(qcow) => { + qcow.seek(SeekFrom::Start(address))?; + qcow.read_exact(buf) + } + BackingKind::Qcow { inner, backing } => { + let has_backing = backing.is_some(); + let cluster_size = inner.raw_file.cluster_size(); + let mut pos = 0usize; + while pos < buf.len() { + let curr_addr = address + pos as u64; + let intra = inner.raw_file.cluster_offset(curr_addr) as usize; + let count = min(buf.len() - pos, cluster_size as usize - intra); + let mapping = inner.map_cluster_read(curr_addr, count, has_backing)?; + match mapping { + ClusterReadMapping::Zero { length } => { + buf[pos..pos + length as usize].fill(0); + } + ClusterReadMapping::Allocated { + offset: host_off, + length, + } => { + inner.raw_file.file_mut().seek(SeekFrom::Start(host_off))?; + inner + .raw_file + .file_mut() + .read_exact(&mut buf[pos..pos + length as usize])?; + } + ClusterReadMapping::Compressed { data } => { + buf[pos..pos + data.len()].copy_from_slice(&data); + } + ClusterReadMapping::Backing { + offset: backing_off, + length, + } => { + if let Some(bf) = backing.as_mut() { + bf.read_at(backing_off, &mut buf[pos..pos + length as usize])?; + } else { + buf[pos..pos + length as usize].fill(0); + } + } + } + pos += count; + } + Ok(()) + } } } } @@ -497,7 +543,7 @@ pub(crate) fn parse_qcow( /// # Ok(()) /// # } /// ``` -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct QcowFile { raw_file: QcowRawFile, header: QcowHeader, @@ -605,11 +651,12 @@ impl QcowFile { Ok(qcow) } + #[cfg(test)] pub fn set_backing_file(&mut self, backing: Option>) { self.backing_file = backing.map(|b| { let virtual_size = b.virtual_size(); BackingFile { - inner: Box::new(*b), + kind: BackingKind::QcowFile(b), virtual_size, } }); diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 2707f5dfba..22d361adad 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -2,80 +2,236 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +use std::cmp::min; use std::collections::VecDeque; use std::fs::File; -use std::io::{self, Seek, SeekFrom}; -use std::os::fd::AsRawFd; -use std::sync::{Arc, Mutex}; +use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}; +use std::sync::Arc; +use std::{io, ptr, slice}; use vmm_sys_util::eventfd::EventFd; -use vmm_sys_util::write_zeroes::PunchHole; +use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::qcow::{Error as QcowError, MAX_NESTING_DEPTH, QcowFile, RawFile, Result as QcowResult}; -use crate::{AsyncAdaptor, BlockBackend}; +use crate::qcow::metadata::{ + BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, +}; +use crate::qcow::qcow_raw_file::QcowRawFile; +use crate::qcow::{ + BackingFile, BackingKind, Error as QcowError, MAX_NESTING_DEPTH, RawFile, Result as QcowResult, + parse_qcow, +}; + +/// Raw backing file using pread64 on a duplicated fd. +struct RawBacking { + fd: OwnedFd, + virtual_size: u64, +} + +// SAFETY: The only I/O operation is pread64 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() { + pread_exact(self.fd.as_raw_fd(), buf, address) + } else { + pread_exact(self.fd.as_raw_fd(), &mut buf[..available], address)?; + buf[available..].fill(0); + Ok(()) + } + } +} + +/// QCOW2 backing file with RwLock metadata and pread64 data reads. +/// +/// Read only because backing files never receive writes. Nested backing +/// files are handled recursively. +struct Qcow2MetadataBacking { + metadata: Arc, + data_fd: OwnedFd, + backing_file: Option>, +} + +// SAFETY: All reads go through QcowMetadata which uses RwLock +// and pread64 which is position independent and thread safe. +unsafe impl Sync for Qcow2MetadataBacking {} + +impl BackingRead for Qcow2MetadataBacking { + 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 Qcow2MetadataBacking { + /// Resolve cluster mappings via metadata then read allocated clusters + /// with pread64. + 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, + } => { + pread_exact( + self.data_fd.as_raw_fd(), + &mut buf[buf_offset..buf_offset + length as usize], + host_offset, + )?; + buf_offset += length as usize; + } + ClusterReadMapping::Compressed { data } => { + let len = data.len(); + buf[buf_offset..buf_offset + len].copy_from_slice(&data); + buf_offset += len; + } + 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 Qcow2MetadataBacking { + fn drop(&mut self) { + self.metadata.shutdown(); + } +} + +/// Construct a thread safe backing file reader. +fn shared_backing_from(bf: BackingFile) -> QcowResult> { + let (kind, virtual_size) = bf.into_kind(); + match kind { + BackingKind::Raw(raw_file) => { + // SAFETY: raw_file holds a valid open fd. + let dup_fd = unsafe { libc::dup(raw_file.as_raw_fd()) }; + if dup_fd < 0 { + return Err(QcowError::BackingFileIo(io::Error::last_os_error())); + } + // SAFETY: dup_fd is a freshly duplicated valid fd. + let fd = unsafe { OwnedFd::from_raw_fd(dup_fd) }; + Ok(Arc::new(RawBacking { fd, virtual_size })) + } + BackingKind::Qcow { inner, backing } => { + // SAFETY: inner.raw_file holds a valid open fd. + let dup_fd = unsafe { libc::dup(inner.raw_file.as_raw_fd()) }; + if dup_fd < 0 { + return Err(QcowError::BackingFileIo(io::Error::last_os_error())); + } + // SAFETY: dup_fd is a freshly duplicated valid fd. + let data_fd = unsafe { OwnedFd::from_raw_fd(dup_fd) }; + Ok(Arc::new(Qcow2MetadataBacking { + metadata: Arc::new(QcowMetadata::new(*inner)), + data_fd, + backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?, + })) + } + #[cfg(test)] + BackingKind::QcowFile(_) => { + unreachable!("QcowFile variant is only used by set_backing_file() in tests") + } + } +} pub struct QcowDiskSync { - // FIXME: The Mutex serializes all QCOW2 I/O operations across queues, which - // is necessary for correctness but eliminates any parallelism benefit from - // multiqueue. QcowFile has internal mutable state (L2 cache, refcounts, file - // position) that is not safe to share across threads via Clone. - // - // A proper fix would require restructuring QcowFile to separate metadata - // operations (which need synchronization) from data I/O (which could be - // parallelized with per queue file descriptors). See #7560 for details. - qcow_file: Arc>, + metadata: Arc, + /// Shared across queues, resolved once at construction. + backing_file: Option>, + sparse: bool, + data_raw_file: QcowRawFile, } impl QcowDiskSync { pub fn new(file: File, direct_io: bool, backing_files: bool, sparse: bool) -> QcowResult { let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 }; - let qcow_file = QcowFile::from_with_nesting_depth( - RawFile::new(file, direct_io), - max_nesting_depth, - sparse, - ) - .map_err(|e| match e { - QcowError::MaxNestingDepthExceeded if !backing_files => QcowError::BackingFilesDisabled, - other => other, - })?; + let (inner, backing_file, sparse) = + parse_qcow(RawFile::new(file, direct_io), max_nesting_depth, sparse).map_err(|e| { + match e { + QcowError::MaxNestingDepthExceeded if !backing_files => { + QcowError::BackingFilesDisabled + } + other => other, + } + })?; + let data_raw_file = inner.raw_file.clone(); Ok(QcowDiskSync { - qcow_file: Arc::new(Mutex::new(qcow_file)), + metadata: Arc::new(QcowMetadata::new(inner)), + backing_file: backing_file.map(shared_backing_from).transpose()?, + sparse, + data_raw_file, }) } } impl DiskFile for QcowDiskSync { fn logical_size(&mut self) -> DiskFileResult { - self.qcow_file - .lock() - .unwrap() - .seek(SeekFrom::End(0)) - .map_err(DiskFileError::Size) + Ok(self.metadata.virtual_size()) } fn physical_size(&mut self) -> DiskFileResult { - self.qcow_file.lock().unwrap().physical_size().map_err(|e| { - let io_inner = match e { - crate::Error::GetFileMetadata(e) => e, - _ => unreachable!(), - }; - DiskFileError::Size(io_inner) - }) + self.data_raw_file + .physical_size() + .map_err(DiskFileError::Size) } fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { - Ok(Box::new(QcowSync::new(Arc::clone(&self.qcow_file))) as Box) + Ok(Box::new(QcowSync::new( + Arc::clone(&self.metadata), + self.data_raw_file.clone(), + self.backing_file.as_ref().map(Arc::clone), + self.sparse, + )) as Box) } fn resize(&mut self, size: u64) -> DiskFileResult<()> { - self.qcow_file - .lock() - .unwrap() + if self.backing_file.is_some() { + return Err(DiskFileError::ResizeError(io::Error::other( + "resize not supported with backing file", + ))); + } + self.metadata .resize(size) - .map_err(|e| DiskFileError::ResizeError(io::Error::other(e))) + .map_err(DiskFileError::ResizeError) } fn supports_sparse_operations(&self) -> bool { @@ -87,20 +243,38 @@ impl DiskFile for QcowDiskSync { } fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd()) + BorrowedDiskFd::new(self.data_raw_file.as_raw_fd()) + } +} + +impl Drop for QcowDiskSync { + fn drop(&mut self) { + self.metadata.shutdown(); } } pub struct QcowSync { - qcow_file: Arc>, + metadata: Arc, + data_file: QcowRawFile, + /// See the backing_file field on QcowDiskSync. + backing_file: Option>, + sparse: bool, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, } impl QcowSync { - pub fn new(qcow_file: Arc>) -> Self { + fn new( + metadata: Arc, + data_file: QcowRawFile, + backing_file: Option>, + sparse: bool, + ) -> Self { QcowSync { - qcow_file, + metadata, + data_file, + backing_file, + sparse, eventfd: EventFd::new(libc::EFD_NONBLOCK) .expect("Failed creating EventFd for QcowSync"), completion_list: VecDeque::new(), @@ -108,7 +282,152 @@ impl QcowSync { } } -impl AsyncAdaptor for QcowFile {} +// -- Position independent I/O helpers -- +// +// Duplicated file descriptors share the kernel file description and thus the +// file position. Using seek then read from multiple queues races on that +// shared position. pread64 and pwrite64 are atomic and never touch the position. + +/// Read exactly the requested bytes at offset, looping on short reads. +fn pread_exact(fd: RawFd, buf: &mut [u8], offset: u64) -> io::Result<()> { + let mut total = 0usize; + while total < buf.len() { + // SAFETY: buf and fd are valid for the lifetime of the call. + let ret = unsafe { + libc::pread64( + fd, + buf[total..].as_mut_ptr() as *mut libc::c_void, + buf.len() - total, + (offset + total as u64) as libc::off_t, + ) + }; + if ret < 0 { + return Err(io::Error::last_os_error()); + } + if ret == 0 { + return Err(io::Error::from(io::ErrorKind::UnexpectedEof)); + } + total += ret as usize; + } + Ok(()) +} + +/// Write all bytes to fd at offset, looping on short writes. +fn pwrite_all(fd: RawFd, buf: &[u8], offset: u64) -> io::Result<()> { + let mut total = 0usize; + while total < buf.len() { + // SAFETY: buf and fd are valid for the lifetime of the call. + let ret = unsafe { + libc::pwrite64( + fd, + buf[total..].as_ptr() as *const libc::c_void, + buf.len() - total, + (offset + total as u64) as libc::off_t, + ) + }; + if ret < 0 { + return Err(io::Error::last_os_error()); + } + if ret == 0 { + return Err(io::Error::other("pwrite64 wrote 0 bytes")); + } + total += ret as usize; + } + Ok(()) +} + +// -- iovec helper functions -- +// +// Operate on the iovec array as a flat byte stream. + +/// Copy data into iovecs starting at the given byte offset. +/// +/// # Safety +/// Caller must ensure iovecs point to valid, writable memory of sufficient size. +unsafe fn scatter_to_iovecs(iovecs: &[libc::iovec], start: usize, data: &[u8]) { + let mut remaining = data; + let mut pos = 0usize; + for iov in iovecs { + let iov_end = pos + iov.iov_len; + if iov_end <= start || remaining.is_empty() { + pos = iov_end; + continue; + } + let iov_start = start.saturating_sub(pos); + let available = iov.iov_len - iov_start; + let count = min(available, remaining.len()); + // SAFETY: iov_base is valid for iov_len bytes per caller contract. + unsafe { + let dst = (iov.iov_base as *mut u8).add(iov_start); + ptr::copy_nonoverlapping(remaining.as_ptr(), dst, count); + } + remaining = &remaining[count..]; + if remaining.is_empty() { + break; + } + pos = iov_end; + } +} + +/// Zero fill iovecs starting at the given byte offset for the given length. +/// +/// # Safety +/// Caller must ensure iovecs point to valid, writable memory of sufficient size. +unsafe fn zero_fill_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) { + let mut remaining = len; + let mut pos = 0usize; + for iov in iovecs { + let iov_end = pos + iov.iov_len; + if iov_end <= start || remaining == 0 { + pos = iov_end; + continue; + } + let iov_start = start.saturating_sub(pos); + let available = iov.iov_len - iov_start; + let count = min(available, remaining); + // SAFETY: iov_base is valid for iov_len bytes per caller contract. + unsafe { + let dst = (iov.iov_base as *mut u8).add(iov_start); + ptr::write_bytes(dst, 0, count); + } + remaining -= count; + if remaining == 0 { + break; + } + pos = iov_end; + } +} + +/// Gather bytes from iovecs starting at the given byte offset into a Vec. +/// +/// # Safety +/// Caller must ensure iovecs point to valid, readable memory of sufficient size. +unsafe fn gather_from_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) -> Vec { + let mut result = Vec::with_capacity(len); + let mut remaining = len; + let mut pos = 0usize; + for iov in iovecs { + let iov_end = pos + iov.iov_len; + if iov_end <= start || remaining == 0 { + pos = iov_end; + continue; + } + let iov_start = start.saturating_sub(pos); + let available = iov.iov_len - iov_start; + let count = min(available, remaining); + // SAFETY: iov_base is valid for iov_len bytes per caller contract. + unsafe { + let src = (iov.iov_base as *const u8).add(iov_start); + result.extend_from_slice(slice::from_raw_parts(src, count)); + } + remaining -= count; + if remaining == 0 { + break; + } + pos = iov_end; + } + result +} impl AsyncIo for QcowSync { fn notifier(&self) -> &EventFd { @@ -121,13 +440,61 @@ impl AsyncIo for QcowSync { iovecs: &[libc::iovec], user_data: u64, ) -> AsyncIoResult<()> { - self.qcow_file.lock().unwrap().read_vectored_sync( - offset, - iovecs, - user_data, - &self.eventfd, - &mut self.completion_list, - ) + let address = offset as u64; + let total_len: usize = iovecs.iter().map(|v| v.iov_len).sum(); + + let has_backing = self.backing_file.is_some(); + let mappings = self + .metadata + .map_clusters_for_read(address, total_len, has_backing) + .map_err(AsyncIoError::ReadVectored)?; + + let mut buf_offset = 0usize; + for mapping in mappings { + match mapping { + ClusterReadMapping::Zero { length } => { + // SAFETY: iovecs point to valid guest memory buffers + unsafe { zero_fill_iovecs(iovecs, buf_offset, length as usize) }; + buf_offset += length as usize; + } + ClusterReadMapping::Allocated { + offset: host_offset, + length, + } => { + let mut buf = vec![0u8; length as usize]; + pread_exact(self.data_file.as_raw_fd(), &mut buf, host_offset) + .map_err(AsyncIoError::ReadVectored)?; + // SAFETY: iovecs point to valid guest memory buffers + unsafe { scatter_to_iovecs(iovecs, buf_offset, &buf) }; + buf_offset += length as usize; + } + ClusterReadMapping::Compressed { data } => { + let len = data.len(); + // SAFETY: iovecs point to valid guest memory buffers + unsafe { scatter_to_iovecs(iovecs, buf_offset, &data) }; + buf_offset += len; + } + ClusterReadMapping::Backing { + offset: backing_offset, + length, + } => { + let mut buf = vec![0u8; length as usize]; + self.backing_file + .as_ref() + .unwrap() + .read_at(backing_offset, &mut buf) + .map_err(AsyncIoError::ReadVectored)?; + // SAFETY: iovecs point to valid guest memory buffers + unsafe { scatter_to_iovecs(iovecs, buf_offset, &buf) }; + buf_offset += length as usize; + } + } + } + + self.completion_list + .push_back((user_data, total_len as i32)); + self.eventfd.write(1).unwrap(); + Ok(()) } fn write_vectored( @@ -136,21 +503,65 @@ impl AsyncIo for QcowSync { iovecs: &[libc::iovec], user_data: u64, ) -> AsyncIoResult<()> { - self.qcow_file.lock().unwrap().write_vectored_sync( - offset, - iovecs, - user_data, - &self.eventfd, - &mut self.completion_list, - ) + let address = offset as u64; + let total_len: usize = iovecs.iter().map(|v| v.iov_len).sum(); + let mut buf_offset = 0usize; + + while buf_offset < total_len { + let curr_addr = address + buf_offset as u64; + let cluster_size = self.metadata.cluster_size(); + let intra_offset = self.metadata.cluster_offset(curr_addr); + let remaining_in_cluster = (cluster_size - intra_offset) as usize; + let count = min(total_len - buf_offset, remaining_in_cluster); + + // Read backing data for COW if this is a partial cluster + // write to an unallocated cluster with a backing file. + let backing_data = if let Some(backing) = self + .backing_file + .as_ref() + .filter(|_| intra_offset != 0 || count < cluster_size as usize) + { + let cluster_begin = curr_addr - intra_offset; + let mut data = vec![0u8; cluster_size as usize]; + backing + .read_at(cluster_begin, &mut data) + .map_err(AsyncIoError::WriteVectored)?; + Some(data) + } else { + None + }; + + let mapping = self + .metadata + .map_cluster_for_write(curr_addr, backing_data) + .map_err(AsyncIoError::WriteVectored)?; + + match mapping { + ClusterWriteMapping::Allocated { + offset: host_offset, + } => { + // SAFETY: iovecs point to valid guest memory buffers + let buf = unsafe { gather_from_iovecs(iovecs, buf_offset, count) }; + pwrite_all(self.data_file.as_raw_fd(), &buf, host_offset) + .map_err(AsyncIoError::WriteVectored)?; + } + } + buf_offset += count; + } + + self.completion_list + .push_back((user_data, total_len as i32)); + self.eventfd.write(1).unwrap(); + Ok(()) } fn fsync(&mut self, user_data: Option) -> AsyncIoResult<()> { - self.qcow_file.lock().unwrap().fsync_sync( - user_data, - &self.eventfd, - &mut self.completion_list, - ) + self.metadata.flush().map_err(AsyncIoError::Fsync)?; + if let Some(user_data) = user_data { + self.completion_list.push_back((user_data, 0)); + self.eventfd.write(1).unwrap(); + } + Ok(()) } fn next_completed_request(&mut self) -> Option<(u64, i32)> { @@ -158,26 +569,49 @@ impl AsyncIo for QcowSync { } fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { - // For QCOW2, punch_hole calls deallocate_cluster + let virtual_size = self.metadata.virtual_size(); + let cluster_size = self.metadata.cluster_size(); + let result = self - .qcow_file - .lock() - .unwrap() - .punch_hole(offset, length) - .map(|_| 0i32) + .metadata + .deallocate_bytes( + offset, + length as usize, + self.sparse, + virtual_size, + cluster_size, + self.backing_file.as_deref(), + ) .map_err(AsyncIoError::PunchHole); match result { - Ok(res) => { - self.completion_list.push_back((user_data, res)); + Ok(actions) => { + for action in actions { + match action { + DeallocAction::PunchHole { + host_offset, + length, + } => { + let _ = self.data_file.file_mut().punch_hole(host_offset, length); + } + DeallocAction::WriteZeroes { + host_offset, + length, + } => { + let _ = self + .data_file + .file_mut() + .write_zeroes_at(host_offset, length); + } + } + } + self.completion_list.push_back((user_data, 0)); self.eventfd.write(1).unwrap(); Ok(()) } Err(e) => { - // CRITICAL: Always signal completion even on error to avoid hangs - let errno = if let AsyncIoError::PunchHole(io_err) = &e { - let err = io_err.raw_os_error().unwrap_or(libc::EIO); - -err + let errno = if let AsyncIoError::PunchHole(ref io_err) = e { + -io_err.raw_os_error().unwrap_or(libc::EIO) } else { -libc::EIO }; @@ -189,85 +623,90 @@ impl AsyncIo for QcowSync { } fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { - // For QCOW2, write_zeroes is implemented by deallocating clusters via punch_hole. - // This is more efficient than writing actual zeros and reduces disk usage. + // For QCOW2 write_zeroes uses cluster deallocation, same as punch_hole. // Unallocated clusters inherently read as zero in the QCOW2 format. - let result = self - .qcow_file - .lock() - .unwrap() - .punch_hole(offset, length) - .map(|_| 0i32) - .map_err(AsyncIoError::WriteZeroes); - - match result { - Ok(res) => { - self.completion_list.push_back((user_data, res)); - self.eventfd.write(1).unwrap(); - Ok(()) - } - Err(e) => { - // Always signal completion even on error to avoid hangs - let errno = if let AsyncIoError::WriteZeroes(io_err) = &e { - let err = io_err.raw_os_error().unwrap_or(libc::EIO); - -err - } else { - -libc::EIO - }; - self.completion_list.push_back((user_data, errno)); - self.eventfd.write(1).unwrap(); - Ok(()) - } - } + self.punch_hole(offset, length, user_data) } } #[cfg(test)] mod unit_tests { - use std::io::{Read, Seek, SeekFrom, Write}; + use std::io::{Seek, SeekFrom, Write}; use vmm_sys_util::tempfile::TempFile; use super::*; - use crate::qcow::{QcowFile, QcowHeader, RawFile}; + use crate::async_io::DiskFile; + use crate::qcow::{QcowFile, RawFile}; + + fn create_disk_with_data( + file_size: u64, + data: &[u8], + offset: u64, + sparse: bool, + ) -> (TempFile, QcowDiskSync) { + let temp_file = TempFile::new().unwrap(); + { + let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + let mut qcow_file = QcowFile::new(raw_file, 3, file_size, sparse).unwrap(); + qcow_file.seek(SeekFrom::Start(offset)).unwrap(); + qcow_file.write_all(data).unwrap(); + qcow_file.flush().unwrap(); + } + let disk = QcowDiskSync::new( + temp_file.as_file().try_clone().unwrap(), + false, + false, + sparse, + ) + .unwrap(); + (temp_file, disk) + } + + fn async_read(disk: &QcowDiskSync, offset: u64, len: usize) -> Vec { + let mut async_io = disk.new_async_io(1).unwrap(); + let mut buf = vec![0xFFu8; len]; + let iovec = libc::iovec { + iov_base: buf.as_mut_ptr() as *mut libc::c_void, + iov_len: buf.len(), + }; + async_io + .read_vectored(offset as libc::off_t, &[iovec], 1) + .unwrap(); + let (user_data, result) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 1); + assert_eq!(result as usize, len, "read should return requested length"); + buf + } + + fn async_write(disk: &QcowDiskSync, offset: u64, data: &[u8]) { + let mut async_io = disk.new_async_io(1).unwrap(); + let iovec = libc::iovec { + iov_base: data.as_ptr() as *mut libc::c_void, + iov_len: data.len(), + }; + async_io + .write_vectored(offset as libc::off_t, &[iovec], 1) + .unwrap(); + let (user_data, result) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 1); + assert_eq!(result as usize, data.len()); + } #[test] fn test_qcow_async_punch_hole_completion() { - // Create a QCOW2 image with valid header - let temp_file = TempFile::new().unwrap(); - let raw_file = RawFile::new(temp_file.into_file(), false); - let file_size = 1024 * 1024 * 100; // 100MB - let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); - - // Write some data - let data = vec![0xDD; 128 * 1024]; // 128KB - let offset = 0; - qcow_file.seek(SeekFrom::Start(offset)).unwrap(); - qcow_file.write_all(&data).unwrap(); - qcow_file.flush().unwrap(); - - // Create async wrapper - let qcow_file = Arc::new(Mutex::new(qcow_file)); - let mut async_qcow = QcowSync::new(qcow_file.clone()); - - // Punch hole - async_qcow - .punch_hole(offset, data.len() as u64, 100) - .unwrap(); + let data = vec![0xDD; 128 * 1024]; + let offset = 0u64; + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); - // Verify completion event was generated - let (user_data, result) = async_qcow.next_completed_request().unwrap(); + let mut async_io = disk.new_async_io(1).unwrap(); + async_io.punch_hole(offset, data.len() as u64, 100).unwrap(); + let (user_data, result) = async_io.next_completed_request().unwrap(); assert_eq!(user_data, 100); assert_eq!(result, 0, "punch_hole should succeed"); + drop(async_io); - // Verify data reads as zeros - let mut read_buf = vec![0; data.len()]; - qcow_file - .lock() - .unwrap() - .seek(SeekFrom::Start(offset)) - .unwrap(); - qcow_file.lock().unwrap().read_exact(&mut read_buf).unwrap(); + let read_buf = async_read(&disk, offset, data.len()); assert!( read_buf.iter().all(|&b| b == 0), "Punched hole should read as zeros" @@ -276,41 +715,20 @@ mod unit_tests { #[test] fn test_qcow_async_write_zeroes_completion() { - // Create a QCOW2 image with valid header - let temp_file = TempFile::new().unwrap(); - let raw_file = RawFile::new(temp_file.into_file(), false); - let file_size = 1024 * 1024 * 100; // 100MB - let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); - - // Write some data - let data = vec![0xEE; 256 * 1024]; // 256KB - let offset = 64 * 1024; // Start at 64KB offset - qcow_file.seek(SeekFrom::Start(offset)).unwrap(); - qcow_file.write_all(&data).unwrap(); - qcow_file.flush().unwrap(); - - // Create async wrapper - let qcow_file = Arc::new(Mutex::new(qcow_file)); - let mut async_qcow = QcowSync::new(qcow_file.clone()); - - // Write zeros - async_qcow + let data = vec![0xEE; 256 * 1024]; + let offset = 64 * 1024u64; + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); + + let mut async_io = disk.new_async_io(1).unwrap(); + async_io .write_zeroes(offset, data.len() as u64, 200) .unwrap(); - - // Verify completion event was generated - let (user_data, result) = async_qcow.next_completed_request().unwrap(); + let (user_data, result) = async_io.next_completed_request().unwrap(); assert_eq!(user_data, 200); assert_eq!(result, 0, "write_zeroes should succeed"); + drop(async_io); - // Verify data reads as zeros - let mut read_buf = vec![0; data.len()]; - qcow_file - .lock() - .unwrap() - .seek(SeekFrom::Start(offset)) - .unwrap(); - qcow_file.lock().unwrap().read_exact(&mut read_buf).unwrap(); + let read_buf = async_read(&disk, offset, data.len()); assert!( read_buf.iter().all(|&b| b == 0), "Zeroed region should read as zeros" @@ -319,186 +737,139 @@ mod unit_tests { #[test] fn test_qcow_async_multiple_operations() { - // Create a QCOW2 image with valid header - let temp_file = TempFile::new().unwrap(); - let raw_file = RawFile::new(temp_file.into_file(), false); - let file_size = 1024 * 1024 * 100; // 100MB - let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); - - // Write data at multiple offsets - let data = vec![0xFF; 64 * 1024]; // 64KB chunks - for i in 0..4 { - let offset = i * 128 * 1024; // 128KB spacing - qcow_file.seek(SeekFrom::Start(offset)).unwrap(); - qcow_file.write_all(&data).unwrap(); - } - qcow_file.flush().unwrap(); - - // Create async wrapper - let qcow_file = Arc::new(Mutex::new(qcow_file)); - let mut async_qcow = QcowSync::new(qcow_file.clone()); - - // Queue multiple punch_hole operations - async_qcow.punch_hole(0, 64 * 1024, 1).unwrap(); - async_qcow.punch_hole(128 * 1024, 64 * 1024, 2).unwrap(); - async_qcow.punch_hole(256 * 1024, 64 * 1024, 3).unwrap(); - - // Verify all completions - let (user_data, result) = async_qcow.next_completed_request().unwrap(); - assert_eq!(user_data, 1); - assert_eq!(result, 0); + let data = vec![0xFF; 64 * 1024]; + let (_temp, _) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); - let (user_data, result) = async_qcow.next_completed_request().unwrap(); - assert_eq!(user_data, 2); - assert_eq!(result, 0); - - let (user_data, result) = async_qcow.next_completed_request().unwrap(); - assert_eq!(user_data, 3); - assert_eq!(result, 0); + // Write data at multiple offsets via QcowFile first, then punch + { + let temp_file = _temp.as_file().try_clone().unwrap(); + let raw_file = RawFile::new(temp_file, false); + let mut qcow_file = QcowFile::from(raw_file).unwrap(); + for i in 0..4u64 { + let off = i * 128 * 1024; + qcow_file.seek(SeekFrom::Start(off)).unwrap(); + qcow_file.write_all(&data).unwrap(); + } + qcow_file.flush().unwrap(); + } - // Verify no more completions - assert!(async_qcow.next_completed_request().is_none()); + let disk = + QcowDiskSync::new(_temp.as_file().try_clone().unwrap(), false, false, true).unwrap(); + + let mut async_io = disk.new_async_io(1).unwrap(); + + async_io.punch_hole(0, 64 * 1024, 1).unwrap(); + async_io.punch_hole(128 * 1024, 64 * 1024, 2).unwrap(); + async_io.punch_hole(256 * 1024, 64 * 1024, 3).unwrap(); + + let (ud, res) = async_io.next_completed_request().unwrap(); + assert_eq!(ud, 1); + assert_eq!(res, 0); + let (ud, res) = async_io.next_completed_request().unwrap(); + assert_eq!(ud, 2); + assert_eq!(res, 0); + let (ud, res) = async_io.next_completed_request().unwrap(); + assert_eq!(ud, 3); + assert_eq!(res, 0); + assert!(async_io.next_completed_request().is_none()); } #[test] - fn test_qcow_punch_hole_with_shared_instance() { - // This test verifies that with Arc>, multiple async I/O operations - // share the same QcowFile instance and see each other's changes. + fn test_qcow_punch_hole_then_read() { + // Verify that after punch_hole, a second async_io sees zeros. + let data = vec![0xAB; 128 * 1024]; + let offset = 0u64; + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); - // Create a QCOW2 image - let temp_file = TempFile::new().unwrap(); - let raw_file = RawFile::new(temp_file.into_file(), false); - let file_size = 1024 * 1024 * 100; // 100MB - let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); - - // Write some data at offset 0 - let data = vec![0xAB; 128 * 1024]; // 128KB of 0xAB pattern - let offset = 0; - qcow_file.seek(SeekFrom::Start(offset)).unwrap(); - qcow_file.write_all(&data).unwrap(); - qcow_file.flush().unwrap(); - - let qcow_shared = Arc::new(Mutex::new(qcow_file)); - - // First async I/O: punch hole - let mut async_qcow1 = QcowSync::new(qcow_shared.clone()); - async_qcow1 + let mut async_io1 = disk.new_async_io(1).unwrap(); + async_io1 .punch_hole(offset, data.len() as u64, 100) .unwrap(); - - // Verify punch_hole completed - let (user_data, result) = async_qcow1.next_completed_request().unwrap(); + let (user_data, result) = async_io1.next_completed_request().unwrap(); assert_eq!(user_data, 100); - assert_eq!(result, 0, "punch_hole should succeed"); - - // Second async I/O: read from same shared instance - // This should see the deallocated cluster because they share the same QcowFile - let mut read_buf = vec![0xFF; data.len()]; - qcow_shared - .lock() - .unwrap() - .seek(SeekFrom::Start(offset)) - .unwrap(); - qcow_shared - .lock() - .unwrap() - .read_exact(&mut read_buf) - .unwrap(); + assert_eq!(result, 0); + drop(async_io1); - // The read should return zeros because the cluster was deallocated + // Read via second async_io, should see zeros + let read_buf = async_read(&disk, offset, data.len()); assert!( read_buf.iter().all(|&b| b == 0), - "After punch_hole, shared QcowFile instance should read zeros from deallocated cluster" + "After punch_hole, read should return zeros" ); } #[test] fn test_qcow_disk_sync_punch_hole_with_new_async_io() { - // This test simulates the EXACT real usage pattern: QcowDiskSync.new_async_io() - // creates a new QcowSync with a cloned QcowFile for each I/O operation. - - use std::io::Write; - - use crate::async_io::DiskFile; - - // Create a QCOW2 image - let temp_file = TempFile::new().unwrap(); - let file_size = 1024 * 1024 * 100; // 100MB - - { - let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); - let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap(); - - // Write data at offset 1MB - use single cluster (64KB) to simplify test - let data = vec![0xCD; 64 * 1024]; // 64KB (one cluster) - let offset = 1024 * 1024u64; - qcow_file.seek(SeekFrom::Start(offset)).unwrap(); - qcow_file.write_all(&data).unwrap(); - qcow_file.flush().unwrap(); - } - - // Open with QcowDiskSync (like real code does) - let disk = - QcowDiskSync::new(temp_file.as_file().try_clone().unwrap(), false, true, true).unwrap(); + // Simulates the real usage pattern of write data, punch hole, then read back. + let data = vec![0xCD; 64 * 1024]; // one cluster + let offset = 1024 * 1024u64; // 1MB offset + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); - // First async I/O: punch hole (simulates DISCARD command) + // Punch hole to simulate DISCARD let mut async_io1 = disk.new_async_io(1).unwrap(); - let offset = 1024 * 1024u64; - let length = 64 * 1024u64; // Single cluster - async_io1.punch_hole(offset, length, 1).unwrap(); + async_io1.punch_hole(offset, data.len() as u64, 1).unwrap(); let (user_data, result) = async_io1.next_completed_request().unwrap(); assert_eq!(user_data, 1); assert_eq!(result, 0, "punch_hole should succeed"); drop(async_io1); - // Second async I/O: read from the same location (simulates READ command) - let mut async_io2 = disk.new_async_io(1).unwrap(); - let mut read_buf = vec![0xFF; length as usize]; - let iovec = libc::iovec { - iov_base: read_buf.as_mut_ptr() as *mut libc::c_void, - iov_len: read_buf.len(), - }; + // Read from the same location to verify + let read_buf = async_read(&disk, offset, data.len()); + assert!( + read_buf.iter().all(|&b| b == 0), + "After punch_hole via new_async_io, read should return zeros" + ); + } - // These assertions are critical to prevent compiler optimization bugs - // that can reorder operations. Without them, the test can fail even - // though the QCOW2 implementation is correct. - assert_eq!(iovec.iov_base as *const u8, read_buf.as_ptr()); - assert_eq!(iovec.iov_len, read_buf.len()); + #[test] + fn test_qcow_async_read_write_roundtrip() { + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); - async_io2 - .read_vectored(offset as libc::off_t, &[iovec], 2) - .unwrap(); + let data = vec![0x42u8; 64 * 1024]; + let offset = 0u64; - let (user_data, result) = async_io2.next_completed_request().unwrap(); - assert_eq!(user_data, 2); - assert_eq!( - result as usize, length as usize, - "read should complete successfully" - ); + async_write(&disk, offset, &data); + + let mut async_io = disk.new_async_io(1).unwrap(); + async_io.fsync(Some(10)).unwrap(); + let (ud, res) = async_io.next_completed_request().unwrap(); + assert_eq!(ud, 10); + assert_eq!(res, 0); + drop(async_io); - // Verify the data is all zeros + let read_buf = async_read(&disk, offset, data.len()); + assert_eq!(read_buf, data, "Read-back should match written data"); + } + + #[test] + fn test_qcow_async_read_unallocated() { + // Reading from an unallocated region should return zeros. + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); + let read_buf = async_read(&disk, 0, 64 * 1024); assert!( read_buf.iter().all(|&b| b == 0), - "After punch_hole via new_async_io, read should return zeros" + "Unallocated region should read as zeros" ); } #[test] - fn backing_files_disabled_error() { - let header = - QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/path/to/backing/file")) - .expect("Failed to create header."); - let temp_file = TempFile::new().unwrap(); - let mut raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); - header - .write_to(&mut raw_file) - .expect("Failed to write header."); - - let file = temp_file.into_file(); - match QcowDiskSync::new(file, false, false, true) { - Err(QcowError::BackingFilesDisabled) => {} - Err(other) => panic!("Expected BackingFilesDisabled, got: {other:?}"), - Ok(_) => panic!("Expected BackingFilesDisabled error, but succeeded"), - } + fn test_qcow_async_cross_cluster_read_write() { + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); + + // Default cluster size is 64KB. Write 96KB starting at 32KB to cross the boundary. + let data: Vec = (0..96 * 1024).map(|i| (i % 251) as u8).collect(); + let offset = 32 * 1024u64; + + async_write(&disk, offset, &data); + + let mut async_io = disk.new_async_io(1).unwrap(); + async_io.fsync(Some(99)).unwrap(); + drop(async_io); + + let read_buf = async_read(&disk, offset, data.len()); + assert_eq!( + read_buf, data, + "Cross-cluster read should match written data" + ); } } From fd6891db626024a5d3237e129530d80730ab5ae4 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 15 Feb 2026 00:56:00 +0100 Subject: [PATCH 0675/1893] 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 --- block/src/qcow_sync.rs | 836 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 834 insertions(+), 2 deletions(-) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 22d361adad..0aeaaef44d 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -632,12 +632,13 @@ impl AsyncIo for QcowSync { #[cfg(test)] mod unit_tests { use std::io::{Seek, SeekFrom, Write}; + use std::thread; use vmm_sys_util::tempfile::TempFile; use super::*; use crate::async_io::DiskFile; - use crate::qcow::{QcowFile, RawFile}; + use crate::qcow::{BackingFileConfig, ImageType, QcowFile, RawFile}; fn create_disk_with_data( file_size: u64, @@ -869,7 +870,838 @@ mod unit_tests { let read_buf = async_read(&disk, offset, data.len()); assert_eq!( read_buf, data, - "Cross-cluster read should match written data" + "Cross cluster read should match written data" + ); + } + + #[test] + fn test_backing_file_read() { + let backing_temp = TempFile::new().unwrap(); + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 4; + let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); + backing_temp.as_file().write_all(&pattern).unwrap(); + backing_temp.as_file().sync_all().unwrap(); + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Raw), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + // Read first cluster - should come from backing file + let buf = async_read(&disk, 0, cluster_size as usize); + assert_eq!( + &buf[..], + &pattern[..cluster_size as usize], + "First cluster should match backing file data" + ); + + let buf = async_read(&disk, cluster_size, cluster_size as usize); + assert_eq!( + &buf[..], + &pattern[cluster_size as usize..2 * cluster_size as usize], + "Second cluster should match backing file data" + ); + + // Read a partial range spanning cluster boundary + let mid = cluster_size - 512; + let len = 1024usize; + let buf = async_read(&disk, mid, len); + assert_eq!( + &buf[..], + &pattern[mid as usize..mid as usize + len], + "Cross cluster read from backing should match" + ); + + let buf = async_read(&disk, 0, file_size as usize); + assert_eq!( + &buf[..], + &pattern[..], + "Full file read from backing should match" + ); + } + + #[test] + fn test_backing_file_read_qcow2_backing() { + let backing_temp = TempFile::new().unwrap(); + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 4; + let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); + { + let raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw, 3, file_size, true).unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + qcow.write_all(&pattern).unwrap(); + qcow.flush().unwrap(); + } + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Qcow2), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + // Read first cluster - should come from QCOW2 backing + let buf = async_read(&disk, 0, cluster_size as usize); + assert_eq!( + &buf[..], + &pattern[..cluster_size as usize], + "First cluster from QCOW2 backing should match" + ); + + let buf = async_read(&disk, 0, file_size as usize); + assert_eq!( + &buf[..], + &pattern[..], + "Full file from QCOW2 backing should match" + ); + + // Write to first cluster, then verify second cluster still reads from backing + let new_data = vec![0xAB; cluster_size as usize]; + async_write(&disk, 0, &new_data); + { + let mut async_io = disk.new_async_io(1).unwrap(); + async_io.fsync(Some(99)).unwrap(); + } + + let buf = async_read(&disk, 0, cluster_size as usize); + assert_eq!( + &buf[..], + &new_data[..], + "Written cluster should be new data" + ); + + let buf = async_read(&disk, cluster_size, cluster_size as usize); + assert_eq!( + &buf[..], + &pattern[cluster_size as usize..2 * cluster_size as usize], + "Unwritten cluster should still come from backing" + ); + } + + #[test] + fn test_multi_queue_concurrent_reads() { + // Verify that multiple queues (threads) can read simultaneously. + // This exercises the RwLock + pread64 design: concurrent L2 cache hits + // proceed in parallel and data reads are position independent. + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 16; + let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); + let (_temp, disk) = create_disk_with_data(file_size, &pattern, 0, true); + let disk = Arc::new(disk); + + let threads: Vec<_> = (0..8) + .map(|t| { + let disk = Arc::clone(&disk); + let pattern = pattern.clone(); + thread::spawn(move || { + for i in 0..16u64 { + // Each thread reads clusters in a different order + let cluster_idx = (i + t * 2) % 16; + let offset = cluster_idx * cluster_size; + let buf = async_read(&disk, offset, cluster_size as usize); + assert_eq!( + &buf[..], + &pattern[offset as usize..(offset + cluster_size) as usize], + "Thread {t} cluster {cluster_idx} mismatch" + ); + } + }) + }) + .collect(); + + for t in threads { + t.join().unwrap(); + } + } + + #[test] + fn test_multi_queue_concurrent_reads_qcow2_backing() { + // Same as above but reads go through a Qcow2MetadataBacking, + // exercising concurrent metadata resolution + pread64 in the backing. + let backing_temp = TempFile::new().unwrap(); + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 16; + let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); + { + let raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw, 3, file_size, true).unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + qcow.write_all(&pattern).unwrap(); + qcow.flush().unwrap(); + } + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Qcow2), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = Arc::new(QcowDiskSync::new(file, false, true, true).unwrap()); + + let threads: Vec<_> = (0..8) + .map(|t| { + let disk = Arc::clone(&disk); + let pattern = pattern.clone(); + thread::spawn(move || { + for i in 0..16u64 { + let cluster_idx = (i + t * 2) % 16; + let offset = cluster_idx * cluster_size; + let buf = async_read(&disk, offset, cluster_size as usize); + assert_eq!( + &buf[..], + &pattern[offset as usize..(offset + cluster_size) as usize], + "Thread {t} cluster {cluster_idx} mismatch (qcow2 backing)" + ); + } + }) + }) + .collect(); + + for t in threads { + t.join().unwrap(); + } + } + + #[test] + fn test_three_layer_backing_chain() { + // raw base -> qcow2 mid -> qcow2 overlay + // Tests recursive shared_backing_from() with nested backing. + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 4; + let base_pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); + + // Layer 0: raw base + let base_temp = TempFile::new().unwrap(); + base_temp.as_file().write_all(&base_pattern).unwrap(); + base_temp.as_file().sync_all().unwrap(); + let base_path = base_temp.as_path().to_str().unwrap().to_string(); + + // Layer 1: qcow2 mid pointing at raw base, write to cluster 0 only + let mid_temp = TempFile::new().unwrap(); + let mid_pattern = vec![0xBBu8; cluster_size as usize]; + { + let raw = RawFile::new(mid_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: base_path, + format: Some(ImageType::Raw), + }; + let mut mid = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + mid.seek(SeekFrom::Start(0)).unwrap(); + mid.write_all(&mid_pattern).unwrap(); + mid.flush().unwrap(); + } + let mid_path = mid_temp.as_path().to_str().unwrap().to_string(); + + // Layer 2: qcow2 overlay pointing at qcow2 mid, write to cluster 1 only + let overlay_temp = TempFile::new().unwrap(); + let overlay_pattern = vec![0xCCu8; cluster_size as usize]; + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: mid_path, + format: Some(ImageType::Qcow2), + }; + let mut overlay = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + overlay.seek(SeekFrom::Start(cluster_size)).unwrap(); + overlay.write_all(&overlay_pattern).unwrap(); + overlay.flush().unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + // Cluster 0: mid wrote 0xBB + let buf = async_read(&disk, 0, cluster_size as usize); + assert!( + buf.iter().all(|&b| b == 0xBB), + "Cluster 0 should come from mid layer" + ); + + // Cluster 1: overlay wrote 0xCC + let buf = async_read(&disk, cluster_size, cluster_size as usize); + assert!( + buf.iter().all(|&b| b == 0xCC), + "Cluster 1 should come from overlay" + ); + + // Cluster 2: falls through mid (unwritten) to raw base + let buf = async_read(&disk, cluster_size * 2, cluster_size as usize); + let expected_start = (cluster_size * 2) as usize; + assert_eq!( + &buf[..], + &base_pattern[expected_start..expected_start + cluster_size as usize], + "Cluster 2 should come from raw base" + ); + + // Cluster 3: also falls through to raw base + let buf = async_read(&disk, cluster_size * 3, cluster_size as usize); + let expected_start = (cluster_size * 3) as usize; + assert_eq!( + &buf[..], + &base_pattern[expected_start..expected_start + cluster_size as usize], + "Cluster 3 should come from raw base" + ); + } + + #[test] + fn test_backing_cow_preserves_all_unwritten_clusters() { + // Write to specific clusters in the overlay, verify all others still + // read from the qcow2 backing correctly. + let cluster_size = 1u64 << 16; + let num_clusters = 8u64; + let file_size = cluster_size * num_clusters; + let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); + + let backing_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw, 3, file_size, true).unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + qcow.write_all(&pattern).unwrap(); + qcow.flush().unwrap(); + } + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Qcow2), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + let written = vec![0xFFu8; cluster_size as usize]; + for &idx in &[0u64, 3, 7] { + async_write(&disk, idx * cluster_size, &written); + } + { + let mut async_io = disk.new_async_io(1).unwrap(); + async_io.fsync(Some(99)).unwrap(); + } + + for &idx in &[0u64, 3, 7] { + let buf = async_read(&disk, idx * cluster_size, cluster_size as usize); + assert!( + buf.iter().all(|&b| b == 0xFF), + "Cluster {idx} should be written data" + ); + } + + // Verify unwritten clusters read from backing + for idx in 0..num_clusters { + if idx == 0 || idx == 3 || idx == 7 { + continue; + } + let offset = idx * cluster_size; + let buf = async_read(&disk, offset, cluster_size as usize); + assert_eq!( + &buf[..], + &pattern[offset as usize..(offset + cluster_size) as usize], + "Cluster {idx} should come from backing" + ); + } + } + + #[test] + fn test_qcow2_backing_read_beyond_virtual_size() { + // Read starting past the backing file virtual_size should return zeros. + let cluster_size = 1u64 << 16; + let backing_size = cluster_size * 2; + let overlay_size = cluster_size * 4; // overlay is larger than backing + + let backing_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw, 3, backing_size, true).unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + qcow.write_all(&vec![0xAA; backing_size as usize]).unwrap(); + qcow.flush().unwrap(); + } + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Qcow2), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, overlay_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + // Read cluster 2 (past backing virtual_size) - should be zeros + let buf = async_read(&disk, backing_size, cluster_size as usize); + assert!( + buf.iter().all(|&b| b == 0), + "Read beyond backing virtual_size should return zeros" + ); + } + + #[test] + fn test_qcow2_backing_read_spanning_virtual_size() { + // Read that starts within backing bounds but extends past virtual_size. + // First part should have backing data, remainder should be zeros. + let cluster_size = 1u64 << 16; + let backing_size = cluster_size * 2; + let overlay_size = cluster_size * 4; + + let backing_temp = TempFile::new().unwrap(); + let backing_data = vec![0xBBu8; backing_size as usize]; + { + let raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw, 3, backing_size, true).unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + qcow.write_all(&backing_data).unwrap(); + qcow.flush().unwrap(); + } + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Qcow2), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, overlay_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + // Read 2 clusters starting at cluster 1 (spans backing boundary) + let read_len = cluster_size as usize * 2; + let buf = async_read(&disk, cluster_size, read_len); + + // First cluster should be backing data + assert!( + buf[..cluster_size as usize].iter().all(|&b| b == 0xBB), + "First half should come from backing" + ); + + // Second cluster is past backing virtual_size - zeros + assert!( + buf[cluster_size as usize..].iter().all(|&b| b == 0), + "Second half should be zeros (past backing virtual_size)" + ); + } + + #[test] + fn test_raw_backing_read_beyond_virtual_size() { + // Read past raw backing file virtual_size should return zeros. + let cluster_size = 1u64 << 16; + let backing_size = cluster_size * 2; + let overlay_size = cluster_size * 4; + + let backing_temp = TempFile::new().unwrap(); + let backing_data = vec![0xDD; backing_size as usize]; + backing_temp.as_file().write_all(&backing_data).unwrap(); + backing_temp.as_file().sync_all().unwrap(); + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Raw), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, overlay_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + // Read cluster 2 (past backing size) - should be zeros + let buf = async_read(&disk, backing_size, cluster_size as usize); + assert!( + buf.iter().all(|&b| b == 0), + "Read beyond raw backing virtual_size should return zeros" + ); + + // Read spanning boundary: cluster 1 has data, cluster 2 zeros + let read_len = cluster_size as usize * 2; + let buf = async_read(&disk, cluster_size, read_len); + assert!( + buf[..cluster_size as usize].iter().all(|&b| b == 0xDD), + "First half should come from raw backing" + ); + assert!( + buf[cluster_size as usize..].iter().all(|&b| b == 0), + "Second half should be zeros (past raw backing size)" + ); + } + + #[test] + fn test_qcow2_backing_cross_cluster_read() { + // Read spanning a cluster boundary through qcow2 backing. + // Exercises the read_clusters loop in Qcow2MetadataBacking. + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 4; + let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); + + let backing_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw, 3, file_size, true).unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + qcow.write_all(&pattern).unwrap(); + qcow.flush().unwrap(); + } + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Qcow2), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + // Read spanning clusters 1-2 boundary: 512 bytes before + 512 after + let mid = cluster_size - 512; + let len = 1024usize; + let buf = async_read(&disk, mid, len); + assert_eq!( + &buf[..], + &pattern[mid as usize..mid as usize + len], + "Cross cluster read through qcow2 backing should match" + ); + + // Read spanning clusters 0-1-2 (3 clusters worth) + let start = cluster_size / 2; + let len = cluster_size as usize * 2; + let buf = async_read(&disk, start, len); + assert_eq!( + &buf[..], + &pattern[start as usize..start as usize + len], + "Multi cluster read through qcow2 backing should match" + ); + } + + #[test] + fn test_punch_hole_with_backing_fallthrough() { + // Write to overlay, then punch hole. After punch, the cluster should + // fall through to backing data (not zeros). + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 4; + let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); + + let backing_temp = TempFile::new().unwrap(); + backing_temp.as_file().write_all(&pattern).unwrap(); + backing_temp.as_file().sync_all().unwrap(); + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Raw), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + let written = vec![0xFFu8; cluster_size as usize]; + async_write(&disk, 0, &written); + { + let mut async_io = disk.new_async_io(1).unwrap(); + async_io.fsync(Some(99)).unwrap(); + } + + let buf = async_read(&disk, 0, cluster_size as usize); + assert!(buf.iter().all(|&b| b == 0xFF), "Should read written data"); + + // Punch hole on cluster 0 - should deallocate and fall through to backing + { + let mut async_io = disk.new_async_io(1).unwrap(); + async_io.punch_hole(0, cluster_size, 42).unwrap(); + let (ud, res) = async_io.next_completed_request().unwrap(); + assert_eq!(ud, 42); + assert_eq!(res, 0); + } + + // Now read should return backing data, not zeros + let buf = async_read(&disk, 0, cluster_size as usize); + assert_eq!( + &buf[..], + &pattern[..cluster_size as usize], + "After punch_hole with backing, should read backing data" + ); + + // Cluster 1 should still be backing data throughout + let buf = async_read(&disk, cluster_size, cluster_size as usize); + assert_eq!( + &buf[..], + &pattern[cluster_size as usize..2 * cluster_size as usize], + "Untouched cluster should read from backing" + ); + } + + #[test] + fn test_rewrite_allocated_cluster() { + // Write to a cluster, then overwrite it. The second write should hit + // the already allocated path in map_write (no new cluster allocation). + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); + let cluster_size = 1u64 << 16; + + let data1 = vec![0xAAu8; cluster_size as usize]; + async_write(&disk, 0, &data1); + { + let mut aio = disk.new_async_io(1).unwrap(); + aio.fsync(Some(1)).unwrap(); + } + let buf = async_read(&disk, 0, cluster_size as usize); + assert!(buf.iter().all(|&b| b == 0xAA), "First write should stick"); + + let data2 = vec![0xBBu8; cluster_size as usize]; + async_write(&disk, 0, &data2); + { + let mut aio = disk.new_async_io(1).unwrap(); + aio.fsync(Some(2)).unwrap(); + } + let buf = async_read(&disk, 0, cluster_size as usize); + assert!( + buf.iter().all(|&b| b == 0xBB), + "Overwrite should replace data" + ); + } + + #[test] + fn test_partial_cluster_write_with_backing_cow() { + // Partial cluster write to an overlay with a backing file triggers COW. + // The unwritten part of the cluster must be copied from backing. + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 4; + let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); + + let backing_temp = TempFile::new().unwrap(); + backing_temp.as_file().write_all(&pattern).unwrap(); + backing_temp.as_file().sync_all().unwrap(); + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Raw), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + // Write 4KB at offset 4KB within cluster 0 (partial cluster) + let write_offset = 4096u64; + let write_len = 4096usize; + let write_data = vec![0xEEu8; write_len]; + async_write(&disk, write_offset, &write_data); + { + let mut aio = disk.new_async_io(1).unwrap(); + aio.fsync(Some(1)).unwrap(); + } + + let buf = async_read(&disk, 0, cluster_size as usize); + + // Before the write: should be COW'd from backing + assert_eq!( + &buf[..write_offset as usize], + &pattern[..write_offset as usize], + "Pre write region should be COW from backing" + ); + + assert_eq!( + &buf[write_offset as usize..write_offset as usize + write_len], + &write_data[..], + "Written region should be new data" + ); + + // After the write: should be COW'd from backing + let after_offset = write_offset as usize + write_len; + assert_eq!( + &buf[after_offset..cluster_size as usize], + &pattern[after_offset..cluster_size as usize], + "Post write region should be COW from backing" + ); + } + + #[test] + fn test_partial_cluster_deallocate() { + // Punch hole on a partial cluster range. The deallocate_bytes path + // should produce WriteZeroes actions for partial clusters. + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 4; + + let data: Vec = (0..2 * cluster_size as usize) + .map(|i| (i % 251) as u8) + .collect(); + let (_temp, disk) = create_disk_with_data(file_size, &data, 0, true); + + // Punch a partial range: last 4KB of cluster 0 + first 4KB of cluster 1 + let punch_offset = cluster_size - 4096; + let punch_len = 8192u64; + { + let mut aio = disk.new_async_io(1).unwrap(); + aio.punch_hole(punch_offset, punch_len, 10).unwrap(); + let (ud, res) = aio.next_completed_request().unwrap(); + assert_eq!(ud, 10); + assert_eq!(res, 0); + } + + let buf = async_read(&disk, 0, 2 * cluster_size as usize); + + // Before punch: unchanged + assert_eq!( + &buf[..punch_offset as usize], + &data[..punch_offset as usize], + "Data before punch should be unchanged" + ); + + // Punched region: zeros + assert!( + buf[punch_offset as usize..(punch_offset + punch_len) as usize] + .iter() + .all(|&b| b == 0), + "Punched region should be zeros" + ); + + // After punch: unchanged + let after = (punch_offset + punch_len) as usize; + assert_eq!( + &buf[after..2 * cluster_size as usize], + &data[after..2 * cluster_size as usize], + "Data after punch should be unchanged" + ); + } + + #[test] + fn test_resize_grow() { + let cluster_size = 1u64 << 16; + let initial_size = cluster_size * 4; + let data = vec![0xAA; cluster_size as usize]; + let (_temp, mut disk) = create_disk_with_data(initial_size, &data, 0, true); + + assert_eq!(disk.logical_size().unwrap(), initial_size); + + let new_size = cluster_size * 8; + disk.resize(new_size).unwrap(); + assert_eq!(disk.logical_size().unwrap(), new_size); + + // Original data intact + let buf = async_read(&disk, 0, cluster_size as usize); + assert!( + buf.iter().all(|&b| b == 0xAA), + "Original data should survive resize" + ); + + // New region reads as zeros + let buf = async_read(&disk, initial_size, cluster_size as usize); + assert!( + buf.iter().all(|&b| b == 0), + "Newly grown region should read as zeros" + ); + + // Can write to newly grown region + let new_data = vec![0xBB; cluster_size as usize]; + async_write(&disk, initial_size, &new_data); + { + let mut aio = disk.new_async_io(1).unwrap(); + aio.fsync(Some(1)).unwrap(); + } + let buf = async_read(&disk, initial_size, cluster_size as usize); + assert!( + buf.iter().all(|&b| b == 0xBB), + "Write to grown region should work" + ); + } + + #[test] + fn test_resize_with_backing_file_rejected() { + let backing_temp = TempFile::new().unwrap(); + let cluster_size = 1u64 << 16; + let file_size = cluster_size * 4; + backing_temp + .as_file() + .write_all(&vec![0u8; file_size as usize]) + .unwrap(); + backing_temp.as_file().sync_all().unwrap(); + let backing_path = backing_temp.as_path().to_str().unwrap().to_string(); + + let overlay_temp = TempFile::new().unwrap(); + { + let raw = RawFile::new(overlay_temp.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing_path, + format: Some(ImageType::Raw), + }; + let _overlay = + QcowFile::new_from_backing(raw, 3, file_size, &backing_config, true).unwrap(); + } + + let file = overlay_temp.as_file().try_clone().unwrap(); + let mut disk = QcowDiskSync::new(file, false, true, true).unwrap(); + + assert_eq!(disk.logical_size().unwrap(), file_size); + let result = disk.resize(file_size * 2); + assert!(result.is_err(), "resize with backing file should fail"); + assert_eq!( + disk.logical_size().unwrap(), + file_size, + "size should be unchanged after failed resize" ); } } From 26fac58a7c4a21ac2bd74e545f3b10102fc7bc1c Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Mon, 9 Mar 2026 18:47:42 +0000 Subject: [PATCH 0676/1893] 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 --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 157098201c..ac65550c05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,7 +58,7 @@ 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 From a179f67b203bdb31693f9d1daba1e6e1da3fa06d Mon Sep 17 00:00:00 2001 From: sohakpt Date: Sun, 8 Mar 2026 23:21:47 +0800 Subject: [PATCH 0677/1893] main: Fix formatting issue in arguments Signed-off-by: sohakpt --- cloud-hypervisor/src/main.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 80cd502914..6680483d6d 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -199,11 +199,10 @@ fn get_cli_options_sorted( .long("cmdline") .help("Kernel command line") .num_args(1) - .group("vm-config"), Arg::new("console") + .group("vm-config"), + Arg::new("console") .long("console") - .help( - "Control (virtio) console: \"off|null|pty|tty|file=,iommu=on|off\"", - ) + .help("Control (virtio) console: \"off|null|pty|tty|file=,iommu=on|off\"") .default_value("tty") .group("vm-config"), Arg::new("cpus") From 40768086b973bf01ba2eb8ae308e3c3fa93d407c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 00:06:36 +0000 Subject: [PATCH 0678/1893] 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] --- Cargo.lock | 114 ++++++++++++++++---------------------------- Cargo.toml | 6 +-- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 90 +++++++++++++++++----------------- fuzz/Cargo.toml | 2 +- net_util/Cargo.toml | 2 +- 6 files changed, 92 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 83ef1dc8c8..1833fda938 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,7 +394,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures", - "rand_core 0.10.0", + "rand_core", ] [[package]] @@ -936,20 +936,20 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", - "rand_core 0.10.0", + "r-efi 6.0.0", + "rand_core", "wasip2", "wasip3", ] @@ -1136,9 +1136,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819b44bc7c87d9117eb522f14d46e918add69ff12713c475946b0a29363ed1c2" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "log", @@ -1149,9 +1149,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470252db18ecc35fd766c0891b1e3ec6cbbcd62507e85276c01bf75d8e94d4a1" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", @@ -1226,9 +1226,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libredox" @@ -1379,7 +1379,7 @@ name = "net_util" version = "0.1.0" dependencies = [ "epoll", - "getrandom 0.4.1", + "getrandom 0.4.2", "libc", "log", "net_gen", @@ -1730,15 +1730,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "prettyplease" version = "0.2.37" @@ -1751,9 +1742,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -1769,9 +1760,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1783,14 +1774,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "rand" -version = "0.9.2" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" @@ -1799,27 +1786,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" dependencies = [ "chacha20", - "getrandom 0.4.1", - "rand_core 0.10.0", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", + "rand_core", ] [[package]] @@ -2132,7 +2100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.60.2", @@ -2155,7 +2123,7 @@ dependencies = [ "dirs", "epoll", "libc", - "rand 0.10.0", + "rand", "serde_json", "ssh2", "thiserror 2.0.18", @@ -2211,18 +2179,18 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.0.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", "toml_datetime", @@ -2294,13 +2262,13 @@ dependencies = [ [[package]] name = "uds_windows" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" dependencies = [ "memoffset", "tempfile", - "winapi", + "windows-sys 0.60.2", ] [[package]] @@ -2323,13 +2291,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", - "rand 0.9.2", + "rand", "serde_core", "wasm-bindgen", ] @@ -2868,9 +2836,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -3026,18 +2994,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a3e76a797e..93765ba718 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,13 +84,13 @@ env_logger = "0.11.8" epoll = "4.4.0" flume = "0.12.0" itertools = "0.14.0" -libc = "0.2.182" +libc = "0.2.183" log = "0.4.29" signal-hook = "0.4.3" thiserror = "2.0.18" -uuid = { version = "1.21.0" } +uuid = { version = "1.22.0" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.40", default-features = false } +zerocopy = { version = "0.8.42", default-features = false } [workspace.lints.clippy] # Any clippy lint (group) in alphabetical order: diff --git a/devices/Cargo.toml b/devices/Cargo.toml index d9ce839882..06c99a1674 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.40", features = [ +zerocopy = { version = "0.8.42", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 6d98900f31..5d5f5e7a45 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -189,6 +189,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + [[package]] name = "clap" version = "4.5.60" @@ -258,6 +269,15 @@ dependencies = [ "syn", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc-any" version = "2.5.0" @@ -530,6 +550,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", + "rand_core", "wasip2", "wasip3", ] @@ -704,9 +725,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libfuzzer-sys" @@ -916,15 +937,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "prettyplease" version = "0.2.37" @@ -937,9 +949,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -955,9 +967,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -970,32 +982,20 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" dependencies = [ - "ppv-lite86", + "chacha20", + "getrandom 0.4.1", "rand_core", ] [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" [[package]] name = "rate_limiter" @@ -1221,18 +1221,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.0.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", "toml_datetime", @@ -1291,9 +1291,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.1", "js-sys", @@ -1681,9 +1681,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -1778,18 +1778,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 888928af06..88d31a152a 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.182" +libc = "0.2.183" libfuzzer-sys = "0.4.12" linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml index e9a8f5badf..9f124cf613 100644 --- a/net_util/Cargo.toml +++ b/net_util/Cargo.toml @@ -6,7 +6,7 @@ version = "0.1.0" [dependencies] epoll = { workspace = true } -getrandom = "0.4.1" +getrandom = "0.4.2" libc = { workspace = true } log = { workspace = true } net_gen = { path = "../net_gen" } From 19d019629177aa48f13a75293263aabbf7dc5f06 Mon Sep 17 00:00:00 2001 From: wuxinyue Date: Tue, 2 Dec 2025 11:45:32 +0800 Subject: [PATCH 0679/1893] 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 --- virtio-devices/src/block.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 9bb97d31c3..bf7d9123c1 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -607,17 +607,14 @@ impl EpollHelperHandler for BlockEpollHandler { )) })?; + self.try_signal_used_queue()?; + let rate_limit_reached = self.rate_limiter.as_ref().is_some_and(|r| r.is_blocked()); // Process the queue only when the rate limit is not reached if !rate_limit_reached { - self.process_queue_submit().map_err(|e| { - EpollHelperError::HandleEvent(anyhow!( - "Failed to process queue (submit): {e:?}" - )) - })?; + self.process_queue_submit_and_signal()?; } - self.try_signal_used_queue()?; } RATE_LIMITER_EVENT => { if let Some(rate_limiter) = &mut self.rate_limiter { From f211170fa2c773437f3c8bcee8087828e5cdc472 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 11 Mar 2026 07:28:02 -0700 Subject: [PATCH 0680/1893] 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 --- vmm/src/api/openapi/cloud-hypervisor.yaml | 44 +++++++++++++++-------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 77b16e97ff..cdba385c74 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -520,6 +520,10 @@ components: type: string description: Virtual Machine Monitor information + VmState: + type: string + enum: [Created, Running, Shutdown, Paused] + VmInfo: required: - config @@ -529,8 +533,8 @@ components: config: $ref: "#/components/schemas/VmConfig" state: - type: string - enum: [Created, Running, Shutdown, Paused] + $ref: "#/components/schemas/VmState" + memory_actual_size: type: integer format: int64 @@ -710,6 +714,11 @@ components: packages: type: integer + CoreSchedulingMode: + type: string + enum: ["Vm", "Vcpu", "Off"] + default: "Vm" + CpusConfig: required: - boot_vcpus @@ -739,9 +748,8 @@ components: features: $ref: "#/components/schemas/CpuFeatures" core_scheduling: - type: string - enum: ["Vm", "Vcpu", "Off"] - default: "Vm" + $ref: "#/components/schemas/CoreSchedulingMode" + PciSegmentConfig: required: @@ -931,6 +939,15 @@ components: items: type: integer + ImageType: + type: string + enum: ["FixedVhd", "Qcow2", "Raw", "Vhdx", "Unknown"] + + LockGranularity: + type: string + enum: [byte-range, full] + default: byte-range + DiskConfig: type: object properties: @@ -978,12 +995,9 @@ components: type: boolean default: true image_type: - type: string - enum: [FixedVhd, Qcow2, Raw, Vhdx, Unknown] + $ref: "#/components/schemas/ImageType" lock_granularity: - type: string - enum: [byte-range, full] - default: byte-range + $ref: "#/components/schemas/LockGranularity" NetConfig: type: object @@ -1132,6 +1146,10 @@ components: id: type: string + ConsoleMode: + type: string + enum: ["Off", "Pty", "Tty", "File", "Socket", "Null"] + ConsoleConfig: required: - mode @@ -1142,8 +1160,7 @@ components: socket: type: string mode: - type: string - enum: ["Off", "Pty", "Tty", "File", "Socket", "Null"] + $ref: "#/components/schemas/ConsoleMode" iommu: type: boolean default: false @@ -1156,8 +1173,7 @@ components: file: type: string mode: - type: string - enum: ["Off", "Pty", "Tty", "File", "Null"] + $ref: "#/components/schemas/ConsoleMode" iobase: type: integer From 0a4fe0e41eae99fbe361ba41f46f45c379519bdd Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 11 Mar 2026 08:16:13 -0700 Subject: [PATCH 0681/1893] 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 --- block/src/fcntl.rs | 1 - vmm/src/api/openapi/cloud-hypervisor.yaml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/block/src/fcntl.rs b/block/src/fcntl.rs index 23c6f9f167..98084748cf 100644 --- a/block/src/fcntl.rs +++ b/block/src/fcntl.rs @@ -147,7 +147,6 @@ impl LockGranularity { /// image. Without a byte-range lock, some NFS implementations may treat the /// entire file as exclusively locked and prevent such operations (e.g. NetApp). #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "kebab-case")] pub enum LockGranularityChoice { /// Byte-range lock covering [0, size). #[default] diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index cdba385c74..efdcf7a678 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -945,8 +945,8 @@ components: LockGranularity: type: string - enum: [byte-range, full] - default: byte-range + enum: [ByteRange, Full] + default: ByteRange DiskConfig: type: object From d90f852ddf5d3bacc7afe373df1764a45fc12b01 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 10 Mar 2026 13:58:40 +0100 Subject: [PATCH 0682/1893] 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] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7728/changes/6ecdf90e22adeecbaf1ce311b1abd358a4788d48 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- virtio-devices/src/block.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index bf7d9123c1..0f6ca08820 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -186,6 +186,9 @@ impl BlockEpollHandler { } if request_type == RequestType::Out && disable_sector0_writes && request.sector == 0 { + warn!( + "Attempting to write to sector 0 on a raw disk without specifying image_type=raw" + ); return Err(ExecuteError::ReadOnly); } From 5b55286099a3e55ec85f93218cb372148b0f89e4 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 10 Mar 2026 13:58:49 +0100 Subject: [PATCH 0683/1893] virtio-devices: block: add another helpful log message Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- virtio-devices/src/block.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 0f6ca08820..22b8ef31e6 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -182,6 +182,9 @@ impl BlockEpollHandler { // For virtio spec compliance // "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request // if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data." + warn!( + "Rejecting block request {request_type:?}: device is read-only (VIRTIO_BLK_F_RO negotiated)" + ); return Err(ExecuteError::ReadOnly); } From 57e64a0848ca63546b12159bfd29bac9897bdde2 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 11 Mar 2026 20:45:31 +0100 Subject: [PATCH 0684/1893] 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 --- performance-metrics/src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index c48e5a906d..34986395ae 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -1214,7 +1214,6 @@ fn run_test_with_timeout( let _ = sender.send(output); }); - // Todo: Need to cleanup/kill all hanging child processes let test_timeout = test.calc_timeout(&test_iterations, &test_timeout); receiver .recv_timeout(Duration::from_secs(test_timeout)) @@ -1223,10 +1222,18 @@ fn run_test_with_timeout( "[Error] Test '{}' time-out after {} seconds", test.name, test_timeout ); + cleanup_stale_processes(); Error::TestTimeout })? } +fn cleanup_stale_processes() { + for proc in &["cloud-hypervisor", "iperf3", "ethr"] { + let _ = Command::new("pkill").args(["-9", "-f", proc]).status(); + } + thread::sleep(Duration::from_secs(2)); +} + fn date() -> String { let output = test_infra::exec_host_command_output("date"); String::from_utf8_lossy(&output.stdout).trim().to_string() From c0a81bc903021784d2d117470c6091e52dc8382e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 11 Mar 2026 20:54:11 +0100 Subject: [PATCH 0685/1893] 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 --- performance-metrics/src/main.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 34986395ae..bef0b74ab1 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -1234,6 +1234,14 @@ fn cleanup_stale_processes() { thread::sleep(Duration::from_secs(2)); } +fn settle_host() { + let _ = Command::new("sync").status(); + let _ = Command::new("bash") + .args(["-c", "echo 3 > /proc/sys/vm/drop_caches"]) + .status(); + thread::sleep(Duration::from_secs(1)); +} + fn date() -> String { let output = test_infra::exec_host_command_output("date"); String::from_utf8_lossy(&output.stdout).trim().to_string() @@ -1334,6 +1342,7 @@ fn main() { for test in test_list.iter() { if test_filter.is_empty() || test_filter.iter().any(|&s| test.name.contains(s)) { + settle_host(); match run_test_with_timeout(test, &overrides) { Ok(r) => { metrics_report.results.push(r); From 766f4206c9a8a127fcdfb71c74e5a77f21ab48fd Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 11 Mar 2026 23:02:31 +0100 Subject: [PATCH 0686/1893] 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 --- performance-metrics/src/performance_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 7eb07f368a..c3787f4da5 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -442,6 +442,8 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { format!("path={test_file},queue_size={queue_size},num_queues={num_queues}"); if test_file == OVERLAY_WITH_QCOW2_BACKING || test_file == OVERLAY_WITH_RAW_BACKING { test_disk_arg.push_str(",image_type=qcow2,backing_files=on"); + } else if test_file == BLK_IO_TEST_IMG { + test_disk_arg.push_str(",image_type=raw"); } let mut child = GuestCommand::new(&guest) From 6850b04fa61954cf7ba45d1b164922c3e9e62fa6 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 11 Mar 2026 23:40:09 +0100 Subject: [PATCH 0687/1893] 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 --- performance-metrics/src/performance_tests.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index c3787f4da5..1022aac19e 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -440,8 +440,16 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { let mut test_disk_arg = format!("path={test_file},queue_size={queue_size},num_queues={num_queues}"); - if test_file == OVERLAY_WITH_QCOW2_BACKING || test_file == OVERLAY_WITH_RAW_BACKING { - test_disk_arg.push_str(",image_type=qcow2,backing_files=on"); + if test_file == OVERLAY_WITH_QCOW2_BACKING + || test_file == OVERLAY_WITH_RAW_BACKING + || test_file == QCOW2_UNCOMPRESSED_IMG + || test_file == QCOW2_ZLIB_IMG + || test_file == QCOW2_ZSTD_IMG + { + test_disk_arg.push_str(",image_type=qcow2"); + if test_file == OVERLAY_WITH_QCOW2_BACKING || test_file == OVERLAY_WITH_RAW_BACKING { + test_disk_arg.push_str(",backing_files=on"); + } } else if test_file == BLK_IO_TEST_IMG { test_disk_arg.push_str(",image_type=raw"); } From 8671e193ff331a80c16d54b23c6364890ef48fb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 23:55:26 +0000 Subject: [PATCH 0688/1893] 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] --- .github/workflows/docker-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 0f6a51f2d4..f077b51738 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -36,7 +36,7 @@ jobs: - 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 From 623af62743222277b637a77f4fd90397f38ab551 Mon Sep 17 00:00:00 2001 From: Emir Beganovic Date: Tue, 10 Mar 2026 13:02:21 +0100 Subject: [PATCH 0689/1893] 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 --- block/src/raw_async_aio.rs | 240 +++++++++++++++++++++++++- cloud-hypervisor/tests/integration.rs | 64 ++++++- 2 files changed, 292 insertions(+), 12 deletions(-) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index fe7196ebba..20fb26c2c4 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -5,6 +5,7 @@ // Copyright © 2023 Crusoe Energy Systems LLC // +use std::collections::VecDeque; use std::fs::File; use std::io::{Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; @@ -73,6 +74,7 @@ pub struct RawFileAsyncAio { ctx: aio::IoContext, eventfd: EventFd, alignment: u64, + completion_list: VecDeque<(u64, i32)>, } impl RawFileAsyncAio { @@ -85,6 +87,7 @@ impl RawFileAsyncAio { ctx, eventfd, alignment: SECTOR_SIZE, + completion_list: VecDeque::new(), }) } } @@ -168,6 +171,11 @@ impl AsyncIo for RawFileAsyncAio { } fn next_completed_request(&mut self) -> Option<(u64, i32)> { + // Drain synchronous completions first (from punch_hole/write_zeroes). + if let Some(completed) = self.completion_list.pop_front() { + return Some(completed); + } + let mut events: [aio::IoEvent; 1] = [aio::IoEvent::default()]; let rc = self.ctx.get_events(0, &mut events, None).unwrap(); if rc == 0 { @@ -177,15 +185,231 @@ impl AsyncIo for RawFileAsyncAio { } } - fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { - Err(AsyncIoError::PunchHole(std::io::Error::other( - "punch_hole not supported with AIO backend", - ))) + 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 (RawFileSync). + const FALLOC_FL_PUNCH_HOLE: i32 = 0x02; + const FALLOC_FL_KEEP_SIZE: i32 = 0x01; + let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; + + // SAFETY: FFI call with valid arguments + let result = unsafe { + libc::fallocate( + self.fd as libc::c_int, + mode, + offset as libc::off_t, + length as libc::off_t, + ) + }; + if result < 0 { + return Err(AsyncIoError::PunchHole(std::io::Error::last_os_error())); + } + + self.completion_list.push_back((user_data, result)); + self.eventfd.write(1).unwrap(); + + Ok(()) + } + + fn write_zeroes(&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 (RawFileSync). + const FALLOC_FL_ZERO_RANGE: i32 = 0x10; + const FALLOC_FL_KEEP_SIZE: i32 = 0x01; + let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE; + + // SAFETY: FFI call with valid arguments + let result = unsafe { + libc::fallocate( + self.fd as libc::c_int, + mode, + offset as libc::off_t, + length as libc::off_t, + ) + }; + if result < 0 { + return Err(AsyncIoError::WriteZeroes(std::io::Error::last_os_error())); + } + + self.completion_list.push_back((user_data, result)); + self.eventfd.write(1).unwrap(); + + Ok(()) } +} + +#[cfg(test)] +mod unit_tests { + use std::io::{Read, Seek, SeekFrom, Write}; + + use vmm_sys_util::tempfile::TempFile; + + use super::*; + + #[test] + fn test_punch_hole() { + let temp_file = TempFile::new().unwrap(); + let mut file = temp_file.into_file(); + + // Write 4MB of data + let data = vec![0xAA; 4 * 1024 * 1024]; + file.write_all(&data).unwrap(); + file.sync_all().unwrap(); + + // Create async IO instance + let mut async_io = RawFileAsyncAio::new(file.as_raw_fd(), 128).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) = async_io.next_completed_request().unwrap(); + 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" + ); + } + + #[test] + fn test_write_zeroes() { + let temp_file = TempFile::new().unwrap(); + let mut file = temp_file.into_file(); + + // Write 4MB of data + let data = vec![0xBB; 4 * 1024 * 1024]; + file.write_all(&data).unwrap(); + file.sync_all().unwrap(); + + // Create async IO instance + let mut async_io = RawFileAsyncAio::new(file.as_raw_fd(), 128).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) = async_io.next_completed_request().unwrap(); + 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" + ); + } + + #[test] + fn test_punch_hole_multiple_operations() { + let temp_file = TempFile::new().unwrap(); + let mut file = temp_file.into_file(); + + // Write 8MB of data + let data = vec![0xCC; 8 * 1024 * 1024]; + file.write_all(&data).unwrap(); + file.sync_all().unwrap(); + + // Create async IO instance + let mut async_io = RawFileAsyncAio::new(file.as_raw_fd(), 128).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) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 10); + assert_eq!(result, 0); + + let (user_data, result) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 11); + assert_eq!(result, 0); + + let (user_data, result) = async_io.next_completed_request().unwrap(); + 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)); - fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> { - Err(AsyncIoError::WriteZeroes(std::io::Error::other( - "write_zeroes not supported with AIO backend", - ))) + file.seek(SeekFrom::Start(5 * 1024 * 1024)).unwrap(); + file.read_exact(&mut read_buf).unwrap(); + assert!(read_buf.iter().all(|&b| b == 0)); } } diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 96963bc4ed..1d198828e9 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7532,6 +7532,24 @@ mod common_parallel { extra_create_args: &[&str], expect_discard_success: bool, verify_disk: bool, + ) { + _test_virtio_block_discard_with_backend( + format_name, + qemu_img_format, + extra_create_args, + expect_discard_success, + verify_disk, + false, + ); + } + + fn _test_virtio_block_discard_with_backend( + format_name: &str, + qemu_img_format: &str, + extra_create_args: &[&str], + expect_discard_success: bool, + verify_disk: bool, + disable_io_uring: bool, ) { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); @@ -7574,9 +7592,14 @@ mod common_parallel { ) .as_str(), format!( - "path={},num_queues=4,image_type={}", + "path={},num_queues=4,image_type={}{}", test_disk_path.to_str().unwrap(), - format_name.to_lowercase() + format_name.to_lowercase(), + if disable_io_uring { + ",_disable_io_uring=on" + } else { + "" + } ) .as_str(), ]) @@ -7754,6 +7777,11 @@ mod common_parallel { _test_virtio_block_discard("raw", "raw", &[], true, false); } + #[test] + fn test_virtio_block_discard_raw_aio() { + _test_virtio_block_discard_with_backend("raw", "raw", &[], true, false, true); + } + #[test] fn test_virtio_block_discard_unsupported_vhd() { _test_virtio_block_discard("vhd", "vpc", &["-o", "subformat=fixed"], false, false); @@ -8056,6 +8084,24 @@ mod common_parallel { extra_create_args: &[&str], expect_fstrim_success: bool, verify_disk: bool, + ) { + _test_virtio_block_fstrim_with_backend( + format_name, + qemu_img_format, + extra_create_args, + expect_fstrim_success, + verify_disk, + false, + ); + } + + fn _test_virtio_block_fstrim_with_backend( + format_name: &str, + qemu_img_format: &str, + extra_create_args: &[&str], + expect_fstrim_success: bool, + verify_disk: bool, + disable_io_uring: bool, ) { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); @@ -8101,9 +8147,14 @@ mod common_parallel { ) .as_str(), format!( - "path={},num_queues=4,image_type={}", + "path={},num_queues=4,image_type={}{}", test_disk_path.to_str().unwrap(), - format_name.to_lowercase() + format_name.to_lowercase(), + if disable_io_uring { + ",_disable_io_uring=on" + } else { + "" + } ) .as_str(), ]) @@ -8242,6 +8293,11 @@ mod common_parallel { _test_virtio_block_fstrim("raw", "raw", &[], true, false); } + #[test] + fn test_virtio_block_fstrim_raw_aio() { + _test_virtio_block_fstrim_with_backend("raw", "raw", &[], true, false, true); + } + #[test] fn test_virtio_block_fstrim_unsupported_vhd() { _test_virtio_block_fstrim("vhd", "vpc", &["-o", "subformat=fixed"], false, false); From f184a0f0f3b909592228c19ab81196793310be7d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 4 Mar 2026 22:26:12 +0100 Subject: [PATCH 0690/1893] 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 --- block/src/error.rs | 17 +++++++++++++++++ block/src/lib.rs | 1 + 2 files changed, 18 insertions(+) create mode 100644 block/src/error.rs diff --git a/block/src/error.rs b/block/src/error.rs new file mode 100644 index 0000000000..a8dc2c1ad2 --- /dev/null +++ b/block/src/error.rs @@ -0,0 +1,17 @@ +// 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. +//! ``` diff --git a/block/src/lib.rs b/block/src/lib.rs index f8d56cf102..1b739beafc 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -9,6 +9,7 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause pub mod async_io; +pub mod error; pub mod fcntl; pub mod fixed_vhd; #[cfg(feature = "io_uring")] From e4e74a9d9331d25159dd8176f09e5ae679ddb263 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 4 Mar 2026 22:28:01 +0100 Subject: [PATCH 0691/1893] 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 --- block/src/error.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/block/src/error.rs b/block/src/error.rs index a8dc2c1ad2..3b7ae1f291 100644 --- a/block/src/error.rs +++ b/block/src/error.rs @@ -15,3 +15,42 @@ //! |-- VhdError / RawError / ... //! +-- io::Error / etc. //! ``` + +use std::fmt::{self, Display, Formatter}; + +/// 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"), + } + } +} From 55504177cbd4519e1a43a534508adbad177f3eb5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 4 Mar 2026 22:28:52 +0100 Subject: [PATCH 0692/1893] 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 --- block/src/error.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/block/src/error.rs b/block/src/error.rs index 3b7ae1f291..b94ddda068 100644 --- a/block/src/error.rs +++ b/block/src/error.rs @@ -17,6 +17,7 @@ //! ``` use std::fmt::{self, Display, Formatter}; +use std::path::PathBuf; /// Small, stable classification of block errors. /// @@ -54,3 +55,57 @@ impl Display for BlockErrorKind { } } } + +/// 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, +} + +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"), + } + } +} + +/// Optional diagnostic context attached to a [`BlockError`]. +#[derive(Debug, Default, Clone)] +pub struct ErrorContext { + pub path: Option, + pub offset: Option, + pub op: Option, +} + +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(()) + } +} From d5467dca8e10c5940820f218b8b418fd3146783d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 4 Mar 2026 22:29:54 +0100 Subject: [PATCH 0693/1893] 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 --- block/src/error.rs | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/block/src/error.rs b/block/src/error.rs index b94ddda068..13d9716854 100644 --- a/block/src/error.rs +++ b/block/src/error.rs @@ -16,6 +16,7 @@ //! +-- io::Error / etc. //! ``` +use std::error::Error as StdError; use std::fmt::{self, Display, Formatter}; use std::path::PathBuf; @@ -46,12 +47,12 @@ 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"), + 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"), } } } @@ -109,3 +110,36 @@ impl Display for ErrorContext { 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>, + ctx: Option, +} + +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)) + } +} From 4c295487364d9575f1689d56432794c55e245dda Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 4 Mar 2026 22:31:15 +0100 Subject: [PATCH 0694/1893] 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 --- block/src/error.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/block/src/error.rs b/block/src/error.rs index 13d9716854..9ea710c006 100644 --- a/block/src/error.rs +++ b/block/src/error.rs @@ -126,6 +126,82 @@ pub struct BlockError { ctx: Option, } +impl BlockError { + /// Create a new `BlockError` from a kind and a source error. + pub fn new(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(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 + } + + /// 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) -> 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(&self) -> Option<&T> { + self.source.as_ref()?.downcast_ref::() + } +} + impl Display for BlockError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.kind)?; From 9be154b03cd79c2debe9a089059149ef602c046b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 15:20:45 +0100 Subject: [PATCH 0695/1893] block: Add BlockResult, From Add the public BlockResult type alias and a From impl so that bare I/O errors automatically convert into BlockError with BlockErrorKind::Io via the ? operator. Signed-off-by: Anatol Belski --- block/src/error.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/block/src/error.rs b/block/src/error.rs index 9ea710c006..4b89bbb212 100644 --- a/block/src/error.rs +++ b/block/src/error.rs @@ -18,6 +18,7 @@ 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. @@ -219,3 +220,12 @@ impl StdError for BlockError { .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } + +/// Convenience: wrap an `io::Error` as `BlockErrorKind::Io`. +impl From for BlockError { + fn from(e: io::Error) -> Self { + Self::new(BlockErrorKind::Io, e) + } +} + +pub type BlockResult = Result; From 8c2794533dd69b4c77182385ae080130d8da35a1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 11 Mar 2026 00:05:19 +0100 Subject: [PATCH 0696/1893] 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 Signed-off-by: Anatol Belski --- block/src/qcow/qcow_raw_file.rs | 8 +++++++- block/src/qcow/raw_file.rs | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/block/src/qcow/qcow_raw_file.rs b/block/src/qcow/qcow_raw_file.rs index 92a569d347..232f6b5a5c 100644 --- a/block/src/qcow/qcow_raw_file.rs +++ b/block/src/qcow/qcow_raw_file.rs @@ -7,7 +7,7 @@ use std::fmt::Debug; use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write}; use std::mem::size_of; -use std::os::fd::{AsRawFd, RawFd}; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use vmm_sys_util::write_zeroes::WriteZeroes; @@ -361,3 +361,9 @@ impl AsRawFd for QcowRawFile { self.file.as_raw_fd() } } + +impl AsFd for QcowRawFile { + fn as_fd(&self) -> BorrowedFd<'_> { + self.file.as_fd() + } +} diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index eda7751c3f..06ec4975f4 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -11,6 +11,7 @@ use std::alloc::{Layout, alloc_zeroed, dealloc}; use std::fs::{File, Metadata}; use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::io::{AsRawFd, RawFd}; use std::slice; @@ -397,3 +398,9 @@ impl AsRawFd for RawFile { self.file.as_raw_fd() } } + +impl AsFd for RawFile { + fn as_fd(&self) -> BorrowedFd<'_> { + self.file.as_fd() + } +} From 58bdfaee3ab689e9eb624c4355fab2091c17774f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 15:21:06 +0100 Subject: [PATCH 0697/1893] 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 --- block/src/qcow_sync.rs | 58 +++++++++++++++++++++++---------------- vmm/src/device_manager.rs | 7 ++++- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 0aeaaef44d..d2f17b0599 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -5,7 +5,7 @@ use std::cmp::min; use std::collections::VecDeque; use std::fs::File; -use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; use std::sync::Arc; use std::{io, ptr, slice}; @@ -15,13 +15,13 @@ use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; +use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, }; use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{ - BackingFile, BackingKind, Error as QcowError, MAX_NESTING_DEPTH, RawFile, Result as QcowResult, - parse_qcow, + BackingFile, BackingKind, Error as QcowError, MAX_NESTING_DEPTH, RawFile, parse_qcow, }; /// Raw backing file using pread64 on a duplicated fd. @@ -139,27 +139,23 @@ impl Drop for Qcow2MetadataBacking { } /// Construct a thread safe backing file reader. -fn shared_backing_from(bf: BackingFile) -> QcowResult> { +fn shared_backing_from(bf: BackingFile) -> BlockResult> { let (kind, virtual_size) = bf.into_kind(); + + let dup_fd = |fd: BorrowedFd<'_>| -> BlockResult { + fd.try_clone_to_owned().map_err(|e| { + BlockError::new(BlockErrorKind::Io, QcowError::BackingFileIo(e)) + .with_op(ErrorOp::DupBackingFd) + }) + }; + match kind { BackingKind::Raw(raw_file) => { - // SAFETY: raw_file holds a valid open fd. - let dup_fd = unsafe { libc::dup(raw_file.as_raw_fd()) }; - if dup_fd < 0 { - return Err(QcowError::BackingFileIo(io::Error::last_os_error())); - } - // SAFETY: dup_fd is a freshly duplicated valid fd. - let fd = unsafe { OwnedFd::from_raw_fd(dup_fd) }; + let fd = dup_fd(raw_file.as_fd())?; Ok(Arc::new(RawBacking { fd, virtual_size })) } BackingKind::Qcow { inner, backing } => { - // SAFETY: inner.raw_file holds a valid open fd. - let dup_fd = unsafe { libc::dup(inner.raw_file.as_raw_fd()) }; - if dup_fd < 0 { - return Err(QcowError::BackingFileIo(io::Error::last_os_error())); - } - // SAFETY: dup_fd is a freshly duplicated valid fd. - let data_fd = unsafe { OwnedFd::from_raw_fd(dup_fd) }; + let data_fd = dup_fd(inner.raw_file.as_fd())?; Ok(Arc::new(Qcow2MetadataBacking { metadata: Arc::new(QcowMetadata::new(*inner)), data_fd, @@ -182,17 +178,33 @@ pub struct QcowDiskSync { } impl QcowDiskSync { - pub fn new(file: File, direct_io: bool, backing_files: bool, sparse: bool) -> QcowResult { + pub fn new( + file: File, + direct_io: bool, + backing_files: bool, + sparse: bool, + ) -> BlockResult { let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 }; let (inner, backing_file, sparse) = - parse_qcow(RawFile::new(file, direct_io), max_nesting_depth, sparse).map_err(|e| { - match e { + parse_qcow(RawFile::new(file, direct_io), max_nesting_depth, sparse) + .map_err(|e| match e { QcowError::MaxNestingDepthExceeded if !backing_files => { QcowError::BackingFilesDisabled } other => other, - } - })?; + }) + .map_err(|e| { + let kind = match &e { + QcowError::InvalidMagic | QcowError::UnsupportedVersion(_) => { + BlockErrorKind::InvalidFormat + } + QcowError::UnsupportedFeature(_) | QcowError::BackingFilesDisabled => { + BlockErrorKind::UnsupportedFeature + } + _ => BlockErrorKind::Io, + }; + BlockError::new(kind, e).with_op(ErrorOp::Open) + })?; let data_raw_file = inner.raw_file.clone(); Ok(QcowDiskSync { metadata: Arc::new(QcowMetadata::new(inner)), diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index c3c5618bda..aa02f479eb 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -33,6 +33,7 @@ use arch::layout::{APIC_START, IOAPIC_SIZE, IOAPIC_START}; use arch::{DeviceType, MmioDeviceInfo}; use arch::{NumaNodes, layout}; use block::async_io::DiskFile; +use block::error::BlockError; use block::fixed_vhd_sync::FixedVhdDiskSync; use block::qcow_sync::QcowDiskSync; use block::raw_async_aio::RawFileDiskAio; @@ -575,7 +576,7 @@ pub enum DeviceManagerError { /// Failed to create QcowDiskSync #[error("Failed to create QcowDiskSync")] - CreateQcowDiskSync(#[source] qcow::Error), + CreateQcowDiskSync(#[source] BlockError), /// Failed to create FixedVhdxDiskSync #[error("Failed to create FixedVhdxDiskSync")] @@ -2776,6 +2777,10 @@ impl DeviceManager { disk_cfg.backing_files, disk_cfg.sparse, ) + .map_err(|e| match &disk_cfg.path { + Some(p) => e.with_path(p), + None => e, + }) .map_err(DeviceManagerError::CreateQcowDiskSync)?, ) as Box } From b1bc376c9145666c24bec432b93db85b8fee7240 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 16:03:19 +0100 Subject: [PATCH 0698/1893] 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 --- block/src/lib.rs | 10 +++++++--- vmm/src/device_manager.rs | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 1b739beafc..6507076a74 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -59,6 +59,7 @@ use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::{aio, ioctl_io_nr}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; +use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::vhdx::VhdxError; const SECTOR_SHIFT: u8 = 9; @@ -1065,13 +1066,16 @@ pub fn read_aligned_block_size(f: &mut File) -> std::io::Result> { } /// Determine image type through file parsing. -pub fn detect_image_type(f: &mut File) -> std::io::Result { - let block = read_aligned_block_size(f)?; +pub fn detect_image_type(f: &mut File) -> BlockResult { + let block = read_aligned_block_size(f) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e).with_op(ErrorOp::DetectImageType))?; // Check 4 first bytes to get the header value and determine the image type let image_type = if u32::from_be_bytes(block[0..4].try_into().unwrap()) == QCOW_MAGIC { ImageType::Qcow2 - } else if vhd::is_fixed_vhd(f)? { + } else if vhd::is_fixed_vhd(f) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e).with_op(ErrorOp::DetectImageType))? + { ImageType::FixedVhd } else if u64::from_le_bytes(block[0..8].try_into().unwrap()) == VHDX_SIGN { ImageType::Vhdx diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index aa02f479eb..3250843951 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -41,7 +41,7 @@ use block::raw_sync::RawFileDiskSync; use block::vhdx_sync::VhdxDiskSync; use block::{ ImageType, block_aio_is_supported, block_io_uring_is_supported, detect_image_type, - preallocate_disk, qcow, vhdx, + preallocate_disk, vhdx, }; #[cfg(feature = "io_uring")] use block::{fixed_vhd_async::FixedVhdDiskAsync, raw_async::RawFileDisk}; @@ -266,7 +266,7 @@ pub enum DeviceManagerError { /// Failed to parse disk image format #[error("Failed to parse disk image format")] - DetectImageType(#[source] io::Error), + DetectImageType(#[source] BlockError), /// Cannot create serial manager #[error("Cannot create serial manager")] From 2bcbe25539d2d329fe359a364c6b8805ec243f50 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 16:57:20 +0100 Subject: [PATCH 0699/1893] 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 --- block/src/qcow/mod.rs | 18 ++++++++++-------- block/src/qcow_sync.rs | 7 +++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index c0b4e8c720..9c9d561527 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -59,10 +59,10 @@ use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache}; #[sorted] #[derive(Debug, Error)] pub enum Error { - #[error("Backing file io error")] - BackingFileIo(#[source] io::Error), - #[error("Backing file open error")] - BackingFileOpen(#[source] Box), + #[error("Backing file I/O error: {0}")] + BackingFileIo(String /* path */, #[source] io::Error), + #[error("Backing file open error: {0}")] + BackingFileOpen(String /* path */, #[source] Box), #[error("Backing file support is disabled")] BackingFilesDisabled, #[error("Backing file name is too long: {0} bytes over")] @@ -201,7 +201,7 @@ impl BackingFile { let backing_raw_file = OpenOptions::new() .read(true) .open(&config.path) - .map_err(Error::BackingFileIo)?; + .map_err(|e| Error::BackingFileIo(config.path.clone(), e))?; let mut raw_file = RawFile::new(backing_raw_file, direct_io); @@ -215,14 +215,16 @@ impl BackingFile { ImageType::Raw => { let size = raw_file .seek(SeekFrom::End(0)) - .map_err(Error::BackingFileIo)?; - raw_file.rewind().map_err(Error::BackingFileIo)?; + .map_err(|e| Error::BackingFileIo(config.path.clone(), e))?; + raw_file + .rewind() + .map_err(|e| Error::BackingFileIo(config.path.clone(), e))?; (BackingKind::Raw(raw_file), size) } ImageType::Qcow2 => { let (inner, nested_backing, _sparse) = parse_qcow(raw_file, max_nesting_depth - 1, sparse) - .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; + .map_err(|e| Error::BackingFileOpen(config.path.clone(), Box::new(e)))?; let size = inner.header.size; ( BackingKind::Qcow { diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index d2f17b0599..e1ad08f4ab 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -144,8 +144,11 @@ fn shared_backing_from(bf: BackingFile) -> BlockResult> { let dup_fd = |fd: BorrowedFd<'_>| -> BlockResult { fd.try_clone_to_owned().map_err(|e| { - BlockError::new(BlockErrorKind::Io, QcowError::BackingFileIo(e)) - .with_op(ErrorOp::DupBackingFd) + BlockError::new( + BlockErrorKind::Io, + QcowError::BackingFileIo(String::new(), e), + ) + .with_op(ErrorOp::DupBackingFd) }) }; From 4fea912d181d41da3699bebb94f62536dc063ad8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 5 Mar 2026 18:26:54 +0100 Subject: [PATCH 0700/1893] 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 --- block/src/lib.rs | 12 +++++++++++- vmm/src/device_manager.rs | 19 ++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 6507076a74..f477cd36c6 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -32,7 +32,7 @@ pub mod vhdx_sync; use std::alloc::{Layout, alloc_zeroed, dealloc}; use std::collections::VecDeque; use std::fmt::{self, Debug}; -use std::fs::File; +use std::fs::{File, OpenOptions}; use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; use std::os::linux::fs::MetadataExt; use std::os::unix::io::AsRawFd; @@ -1065,6 +1065,16 @@ pub fn read_aligned_block_size(f: &mut File) -> std::io::Result> { Ok(data) } +/// Open a disk image file, returning a [`BlockError`] with path context +/// on failure. +pub fn open_disk_image(path: &Path, options: &OpenOptions) -> BlockResult { + options.open(path).map_err(|e| { + BlockError::new(BlockErrorKind::Io, e) + .with_op(ErrorOp::Open) + .with_path(path) + }) +} + /// Determine image type through file parsing. pub fn detect_image_type(f: &mut File) -> BlockResult { let block = read_aligned_block_size(f) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 3250843951..04b9e3b44b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -41,7 +41,7 @@ use block::raw_sync::RawFileDiskSync; use block::vhdx_sync::VhdxDiskSync; use block::{ ImageType, block_aio_is_supported, block_io_uring_is_supported, detect_image_type, - preallocate_disk, vhdx, + open_disk_image, preallocate_disk, vhdx, }; #[cfg(feature = "io_uring")] use block::{fixed_vhd_async::FixedVhdDiskAsync, raw_async::RawFileDisk}; @@ -177,7 +177,7 @@ pub enum DeviceManagerError { /// Cannot open disk path #[error("Cannot open disk path")] - Disk(#[source] io::Error), + Disk(#[source] BlockError), /// Cannot create vhost-user-net device #[error("Cannot create vhost-user-net device")] @@ -2663,15 +2663,12 @@ impl DeviceManager { options.custom_flags(libc::O_DIRECT); } // Open block device path - let mut file: File = options - .open( - disk_cfg - .path - .as_ref() - .ok_or(DeviceManagerError::NoDiskPath)? - .clone(), - ) - .map_err(DeviceManagerError::Disk)?; + let disk_path = disk_cfg + .path + .as_ref() + .ok_or(DeviceManagerError::NoDiskPath)?; + let mut file: File = + open_disk_image(disk_path, &options).map_err(DeviceManagerError::Disk)?; let detected_image_type = detect_image_type(&mut file).map_err(DeviceManagerError::DetectImageType)?; From 732cddb8b33c63edb979258b718d1a38187406ef Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 9 Mar 2026 23:07:10 +0100 Subject: [PATCH 0701/1893] 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 --- block/src/qcow/header.rs | 18 +++++++++++------- block/src/qcow/mod.rs | 4 +++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/block/src/qcow/header.rs b/block/src/qcow/header.rs index 4549668418..22a5492b19 100644 --- a/block/src/qcow/header.rs +++ b/block/src/qcow/header.rs @@ -20,6 +20,7 @@ use super::decoder::{Decoder, ZlibDecoder, ZstdDecoder}; use super::qcow_raw_file::BeUint; use super::raw_file::RawFile; use super::{Error, Result, div_round_up_u32, div_round_up_u64}; +use crate::error::{BlockError, BlockErrorKind, BlockResult}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ImageType { @@ -511,13 +512,14 @@ impl QcowHeader { } /// Write only the incompatible_features field to the file at its fixed offset. - fn write_incompatible_features(&self, file: &mut F) -> Result<()> { + fn write_incompatible_features(&self, file: &mut F) -> BlockResult<()> { if self.version != 3 { return Ok(()); } file.seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64)) - .map_err(Error::WritingHeader)?; - u64::write_be(file, self.incompatible_features).map_err(Error::WritingHeader)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::WritingHeader(e)))?; + u64::write_be(file, self.incompatible_features) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::WritingHeader(e)))?; Ok(()) } @@ -529,7 +531,7 @@ impl QcowHeader { &mut self, file: &mut F, dirty: bool, - ) -> Result<()> { + ) -> BlockResult<()> { if self.version == 3 { if dirty { self.incompatible_features |= IncompatFeatures::DIRTY.bits(); @@ -537,7 +539,8 @@ impl QcowHeader { self.incompatible_features &= !IncompatFeatures::DIRTY.bits(); } self.write_incompatible_features(file)?; - file.fsync().map_err(Error::SyncingHeader)?; + file.fsync() + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?; } Ok(()) } @@ -546,11 +549,12 @@ impl QcowHeader { /// /// 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 F) -> Result<()> { + pub fn set_corrupt_bit(&mut self, file: &mut F) -> BlockResult<()> { if self.version == 3 { self.incompatible_features |= IncompatFeatures::CORRUPT.bits(); self.write_incompatible_features(file)?; - file.fsync().map_err(Error::SyncingHeader)?; + file.fsync() + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?; } Ok(()) } diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 9c9d561527..af118d6f7a 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -508,7 +508,9 @@ pub(crate) fn parse_qcow( if !IncompatFeatures::from_bits_truncate(header.incompatible_features) .contains(IncompatFeatures::DIRTY) { - header.set_dirty_bit(raw_file.file_mut(), true)?; + header + .set_dirty_bit(raw_file.file_mut(), true) + .map_err(|e| Error::WritingHeader(io::Error::other(e)))?; } header.clear_autoclear_features(raw_file.file_mut())?; From c59c5687d26e5979bc8225a5e009592f99364339 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 9 Mar 2026 23:15:05 +0100 Subject: [PATCH 0702/1893] 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 --- block/src/qcow/mod.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index af118d6f7a..2f6ddf4416 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -51,6 +51,7 @@ use vmm_sys_util::seek_hole::SeekHole; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::BlockBackend; +use crate::error::{BlockError, BlockErrorKind, BlockResult}; use crate::qcow::qcow_raw_file::{BeUint, QcowRawFile}; pub use crate::qcow::raw_file::RawFile; use crate::qcow::refcount::RefCount; @@ -2028,7 +2029,7 @@ impl BlockBackend for QcowFile { } } -fn convert_copy(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()> +fn convert_copy(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> BlockResult<()> where R: Read + Seek, W: Write + Seek, @@ -2038,16 +2039,18 @@ where let mut read_count = 0; reader .seek(SeekFrom::Start(offset)) - .map_err(Error::SeekingFile)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; writer .seek(SeekFrom::Start(offset)) - .map_err(Error::SeekingFile)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; loop { let this_count = min(CHUNK_SIZE as u64, size - read_count) as usize; let nread = reader .read(&mut buf[..this_count]) - .map_err(Error::ReadingData)?; - writer.write(&buf[..nread]).map_err(Error::WritingData)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingData(e)))?; + writer + .write(&buf[..nread]) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::WritingData(e)))?; read_count += nread as u64; if nread == 0 || read_count == size { break; @@ -2081,7 +2084,8 @@ where } }; let count = next_hole - next_data; - convert_copy(reader, writer, next_data, count)?; + convert_copy(reader, writer, next_data, count) + .map_err(|e| Error::ReadingData(io::Error::other(e)))?; offset = next_hole; } From 748666fe4d642f17eeaaa36f56c9f252c600d5c0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 9 Mar 2026 23:18:34 +0100 Subject: [PATCH 0703/1893] 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 --- block/src/qcow/mod.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 2f6ddf4416..b00523da8d 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -2060,7 +2060,7 @@ where Ok(()) } -fn convert_reader_writer(reader: &mut R, writer: &mut W, size: u64) -> Result<()> +fn convert_reader_writer(reader: &mut R, writer: &mut W, size: u64) -> BlockResult<()> where R: Read + Seek + SeekHole, W: Write + Seek, @@ -2068,24 +2068,32 @@ where let mut offset = 0; while offset < size { // Find the next range of data. - let next_data = match reader.seek_data(offset).map_err(Error::SeekingFile)? { + let next_data = match reader + .seek_data(offset) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))? + { Some(o) => o, None => { // No more data in the file. break; } }; - let next_hole = match reader.seek_hole(next_data).map_err(Error::SeekingFile)? { + let next_hole = match reader + .seek_hole(next_data) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))? + { Some(o) => o, None => { // This should not happen - there should always be at least one hole // after any data. - return Err(Error::SeekingFile(io::Error::from_raw_os_error(EINVAL))); + return Err(BlockError::new( + BlockErrorKind::Io, + Error::SeekingFile(io::Error::from_raw_os_error(EINVAL)), + )); } }; let count = next_hole - next_data; - convert_copy(reader, writer, next_data, count) - .map_err(|e| Error::ReadingData(io::Error::other(e)))?; + convert_copy(reader, writer, next_data, count)?; offset = next_hole; } @@ -2106,6 +2114,7 @@ where ImageType::Qcow2 => { let mut dst_writer = QcowFile::new(dst_file, 3, src_size, true)?; convert_reader_writer(reader, &mut dst_writer, src_size) + .map_err(|e| Error::WritingData(io::Error::other(e))) } ImageType::Raw => { let mut dst_writer = dst_file; @@ -2115,6 +2124,7 @@ where .set_len(src_size) .map_err(Error::SettingFileSize)?; convert_reader_writer(reader, &mut dst_writer, src_size) + .map_err(|e| Error::WritingData(io::Error::other(e))) } } } From 4930d93090ff8ab816159819960b83e88b1f156c Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 9 Mar 2026 23:19:50 +0100 Subject: [PATCH 0704/1893] 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 --- block/src/qcow/mod.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index b00523da8d..974e6ec410 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -2100,21 +2100,27 @@ where Ok(()) } -fn convert_reader(reader: &mut R, dst_file: RawFile, dst_type: ImageType) -> Result<()> +fn convert_reader(reader: &mut R, dst_file: RawFile, dst_type: ImageType) -> BlockResult<()> where R: Read + Seek + SeekHole, { - let src_size = reader.seek(SeekFrom::End(0)).map_err(Error::SeekingFile)?; - reader.rewind().map_err(Error::SeekingFile)?; + let src_size = reader + .seek(SeekFrom::End(0)) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; + reader + .rewind() + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; // Ensure the destination file is empty before writing to it. - dst_file.set_len(0).map_err(Error::SettingFileSize)?; + dst_file + .set_len(0) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SettingFileSize(e)))?; match dst_type { ImageType::Qcow2 => { - let mut dst_writer = QcowFile::new(dst_file, 3, src_size, true)?; + let mut dst_writer = QcowFile::new(dst_file, 3, src_size, true) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; convert_reader_writer(reader, &mut dst_writer, src_size) - .map_err(|e| Error::WritingData(io::Error::other(e))) } ImageType::Raw => { let mut dst_writer = dst_file; @@ -2122,9 +2128,8 @@ where // of the desired size. dst_writer .set_len(src_size) - .map_err(Error::SettingFileSize)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SettingFileSize(e)))?; convert_reader_writer(reader, &mut dst_writer, src_size) - .map_err(|e| Error::WritingData(io::Error::other(e))) } } } @@ -2144,11 +2149,13 @@ pub fn convert( let mut src_reader = QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth, true)?; convert_reader(&mut src_reader, dst_file, dst_type) + .map_err(|e| Error::ReadingData(io::Error::other(e))) } ImageType::Raw => { // src_file is a raw file. let mut src_reader = src_file; convert_reader(&mut src_reader, dst_file, dst_type) + .map_err(|e| Error::ReadingData(io::Error::other(e))) } } } From 1accf47db4510ae9345b62b83b0d229fb9b5f2d0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 9 Mar 2026 23:22:38 +0100 Subject: [PATCH 0705/1893] 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 --- block/src/qcow/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 974e6ec410..a5714badd7 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -2142,20 +2142,20 @@ pub fn convert( dst_file: RawFile, dst_type: ImageType, src_max_nesting_depth: u32, -) -> Result<()> { - let src_type = detect_image_type(&mut src_file)?; +) -> BlockResult<()> { + let src_type = + detect_image_type(&mut src_file).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; match src_type { ImageType::Qcow2 => { let mut src_reader = - QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth, true)?; + QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth, true) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; convert_reader(&mut src_reader, dst_file, dst_type) - .map_err(|e| Error::ReadingData(io::Error::other(e))) } ImageType::Raw => { // src_file is a raw file. let mut src_reader = src_file; convert_reader(&mut src_reader, dst_file, dst_type) - .map_err(|e| Error::ReadingData(io::Error::other(e))) } } } From 32d339c59edd90566ca0c36fe2cadc350f7b0ad0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 9 Mar 2026 23:51:32 +0100 Subject: [PATCH 0706/1893] 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 --- block/src/qcow/mod.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index a5714badd7..0ed4cd858d 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -678,8 +678,11 @@ impl QcowFile { } /// Returns an L2_table of cluster addresses, only used for debugging. - pub fn l2_table(&mut self, l1_index: usize) -> Result> { - let l2_addr_disk = *self.l1_table.get(l1_index).ok_or(Error::InvalidIndex)?; + pub fn l2_table(&mut self, l1_index: usize) -> BlockResult> { + let l2_addr_disk = *self + .l1_table + .get(l1_index) + .ok_or_else(|| BlockError::new(BlockErrorKind::OutOfBounds, Error::InvalidIndex))?; if l2_addr_disk == 0 { // Reading from an unallocated cluster will return zeros. @@ -690,7 +693,7 @@ impl QcowFile { // Not in the cache. let table = VecCache::from_vec( Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk) - .map_err(Error::ReadingPointers)?, + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingPointers(e)))?, ); let l1_table = &self.l1_table; let raw_file = &mut self.raw_file; @@ -698,7 +701,7 @@ impl QcowFile { .insert(l1_index, table, |index, evicted| { raw_file.write_pointer_table_direct(l1_table[index], evicted.iter()) }) - .map_err(Error::EvictingCache)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::EvictingCache(e)))?; } // The index must exist as it was just inserted if it didn't already. @@ -711,19 +714,19 @@ impl QcowFile { } /// Returns the `index`th refcount block from the file. - pub fn refcount_block(&mut self, index: usize) -> Result> { + pub fn refcount_block(&mut self, index: usize) -> BlockResult> { self.refcounts .refcount_block(&mut self.raw_file, index) - .map_err(Error::ReadingRefCountBlock) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingRefCountBlock(e))) } /// Returns the first cluster in the file with a 0 refcount. Used for testing. - pub fn first_zero_refcount(&mut self) -> Result> { + pub fn first_zero_refcount(&mut self) -> BlockResult> { let file_size = self .raw_file .file_mut() .metadata() - .map_err(Error::GettingFileSize)? + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))? .len(); let cluster_size = 0x01u64 << self.header.cluster_bits; @@ -732,7 +735,7 @@ impl QcowFile { let cluster_refcount = self .refcounts .get_cluster_refcount(&mut self.raw_file, cluster_addr) - .map_err(Error::GettingRefcount)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingRefcount(e)))?; if cluster_refcount == 0 { return Ok(Some(cluster_addr)); } From 84b8d25bb631b5dc2a0c9788918625498f6e9f5e Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Thu, 12 Mar 2026 16:24:31 +0100 Subject: [PATCH 0707/1893] 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 --- vm-migration/src/protocol.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index 3ae226ece2..4dfec4f625 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -332,19 +332,19 @@ impl MemoryRangeTable { pub fn read_from(fd: &mut dyn Read, length: u64) -> Result { assert!((length as usize).is_multiple_of(size_of::())); - let mut data: Vec = Vec::new(); - data.resize_with( - length as usize / (std::mem::size_of::()), - Default::default, - ); - // SAFETY: the slice is constructed with the correct arguments - fd.read_exact(unsafe { - std::slice::from_raw_parts_mut( - data.as_ptr() as *mut MemoryRange as *mut u8, - length as usize, - ) - }) - .map_err(MigratableError::MigrateSocket)?; + let mut data: Vec = + vec![MemoryRange::default(); length as usize / size_of::()]; + + // SAFETY: The pointer points to the just created vector data. + // `MemoryRange` can be read from and written to bytes since it's `[repr(C)]`. + // The vector data was initialized with `length as usize / size_of::()` valid + // `MemoryRange`s so the memory is valid for `length` bytes. + // During the lifetime of the slice, neither the backing vector nor the pointed to memory are accessed. + let data_slice_bytes = + unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr().cast(), length as usize) }; + + fd.read_exact(data_slice_bytes) + .map_err(MigratableError::MigrateSocket)?; Ok(Self { data }) } From 2df41986b792fc102cf1542ade5335aaeb3bd06c Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Tue, 3 Mar 2026 18:19:40 +0100 Subject: [PATCH 0708/1893] main: add `.action(ArgAction::Append)` to all `.num_args(1..)` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- cloud-hypervisor/src/main.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 6680483d6d..9966711820 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -246,11 +246,13 @@ fn get_cli_options_sorted( .long("device") .help(DeviceConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("disk") .long("disk") .help(DiskConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("event-monitor") .long("event-monitor") @@ -266,6 +268,7 @@ fn get_cli_options_sorted( .long("fs") .help(FsConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), #[cfg(feature = "fw_cfg")] Arg::new("fw-cfg-config") @@ -283,6 +286,7 @@ fn get_cli_options_sorted( .long("generic-vhost-user") .help(GenericVhostUserConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), #[cfg(feature = "igvm")] Arg::new("igvm") @@ -328,6 +332,7 @@ fn get_cli_options_sorted( .long("landlock-rules") .help(LandlockConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("log-file") .long("log-file") @@ -360,21 +365,25 @@ fn get_cli_options_sorted( prefault=on|off\"", ) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("net") .long("net") .help(NetConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("numa") .long("numa") .help(NumaConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("pci-segment") .long("pci-segment") .help(PciSegmentConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("platform") .long("platform") @@ -387,6 +396,7 @@ fn get_cli_options_sorted( .long("pmem") .help(PmemConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), #[cfg(feature = "pvmemcontrol")] Arg::new("pvmemcontrol") @@ -405,6 +415,7 @@ fn get_cli_options_sorted( .long("rate-limit-group") .help(RateLimiterGroupConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("restore") .long("restore") @@ -437,6 +448,7 @@ fn get_cli_options_sorted( .long("user-device") .help(UserDeviceConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("v") .short('v') @@ -447,6 +459,7 @@ fn get_cli_options_sorted( .long("vdpa") .help(VdpaConfig::SYNTAX) .num_args(1..) + .action(ArgAction::Append) .group("vm-config"), Arg::new("version") .short('V') From 4e7f9595c8e5b15f22ae67682f0a1edef47a8112 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 12 Mar 2026 12:52:16 -0700 Subject: [PATCH 0709/1893] 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 --- vmm/src/config.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index b4c04570f5..42cbcfdbc0 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -717,14 +717,6 @@ impl CpusConfig { .map_err(Error::ParseCpus)? .is_none_or(|toggle| toggle.0); - // Nested virtualization is always turned on for aarch64 and riscv64 - // TODO: revisit this when nested support can be turned of on these architectures - #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] - if !nested { - return Err(Error::ParseCpus(OptionParserError::InvalidValue( - "nested=off is not supported on aarch64 and riscv64 architectures".to_string(), - ))); - } let core_scheduling = parser .convert("core_scheduling") .map_err(Error::ParseCpus)? From a747e2b72a2183938b1728c4ceefc3385eaf7728 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 11 Mar 2026 21:38:50 +0100 Subject: [PATCH 0710/1893] hypervisor: kvm: cleanup unneeded Arc Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- hypervisor/src/kvm/mod.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 75073ec8d5..1c1abd4b68 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -427,10 +427,10 @@ struct KvmDirtyLogSlot { /// Wrapper over KVM VM ioctls. pub struct KvmVm { - fd: Arc, + fd: VmFd, #[cfg(target_arch = "x86_64")] msrs: Vec, - dirty_log_slots: Arc>>, + dirty_log_slots: RwLock>, } impl KvmVm { @@ -1238,8 +1238,6 @@ impl hypervisor::Hypervisor for KvmHypervisor { break; } - let vm_fd = Arc::new(fd); - #[cfg(target_arch = "x86_64")] { let msr_list = self.get_msr_list()?; @@ -1256,17 +1254,17 @@ impl hypervisor::Hypervisor for KvmHypervisor { } Ok(Arc::new(KvmVm { - fd: vm_fd, + fd, msrs, - dirty_log_slots: Arc::new(RwLock::new(HashMap::new())), + dirty_log_slots: RwLock::new(HashMap::new()), })) } #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] { Ok(Arc::new(KvmVm { - fd: vm_fd, - dirty_log_slots: Arc::new(RwLock::new(HashMap::new())), + fd, + dirty_log_slots: RwLock::new(HashMap::new()), })) } } From 001adbe15a3b6db7413d9124003fa55736fa5756 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 12 Mar 2026 09:32:28 +0100 Subject: [PATCH 0711/1893] hypervisor: mshv: cleanup unneeded Arc Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- hypervisor/src/mshv/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 613c4dc77e..00cc4a6844 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -346,7 +346,7 @@ impl hypervisor::Hypervisor for MshvHypervisor { Ok(Arc::new(MshvVm { fd: vm_fd, msrs: ArcSwap::new(Vec::::new().into()), - dirty_log_slots: Arc::new(RwLock::new(HashMap::new())), + dirty_log_slots: RwLock::new(HashMap::new()), #[cfg(feature = "sev_snp")] sev_snp_enabled: mshv_vm_type == VmType::Snp, #[cfg(feature = "sev_snp")] @@ -364,7 +364,7 @@ impl hypervisor::Hypervisor for MshvHypervisor { { Ok(Arc::new(MshvVm { fd: vm_fd, - dirty_log_slots: Arc::new(RwLock::new(HashMap::new())), + dirty_log_slots: RwLock::new(HashMap::new()), })) } } @@ -1716,7 +1716,7 @@ pub struct MshvVm { fd: Arc, #[cfg(target_arch = "x86_64")] msrs: ArcSwap>, - dirty_log_slots: Arc>>, + dirty_log_slots: RwLock>, #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, #[cfg(feature = "sev_snp")] From f630694bb0a709052e7067c8c37f2e7fd7c828ea Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 11 Feb 2026 20:08:45 -0500 Subject: [PATCH 0712/1893] 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 --- virtio-devices/src/transport/pci_device.rs | 32 +++++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 408611e29a..bf1d169c83 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -231,28 +231,46 @@ impl PciSubclass for PciVirtioSubclass { } } +/// Max number of virtio queues Cloud Hypervisor supports. +/// This is set by the current size of the notification BAR. +const MAX_QUEUES: u64 = 0x400; + +// Automatically compute the position of the next entry in the BAR. +// This handles alignment properly and is much less error-prone than +// manual calculation. +const fn next_bar_addr_align(offset: u64, size: u64, align: u64) -> u64 { + assert!(align >= 0x2000, "too small alignment for structure in BAR"); + assert!(align.is_power_of_two(), "alignment must be a power of 2"); + (offset + size).next_multiple_of(align) +} +// Same as next_bar_addr_align(), but with the default alignment (8K). +const fn next_bar_addr(offset: u64, size: u64) -> u64 { + next_bar_addr_align(offset, size, 0x2000) +} + // Allocate one bar for the structs pointed to by the capability structures. // As per the PCI specification, because the same BAR shares MSI-X and non // MSI-X structures, it is recommended to use 8KiB alignment for all those // structures. const COMMON_CONFIG_BAR_OFFSET: u64 = 0x0000; const COMMON_CONFIG_SIZE: u64 = 56; -const ISR_CONFIG_BAR_OFFSET: u64 = 0x2000; +const ISR_CONFIG_BAR_OFFSET: u64 = next_bar_addr(COMMON_CONFIG_BAR_OFFSET, COMMON_CONFIG_SIZE); const ISR_CONFIG_SIZE: u64 = 1; -const DEVICE_CONFIG_BAR_OFFSET: u64 = 0x4000; +const DEVICE_CONFIG_BAR_OFFSET: u64 = next_bar_addr(ISR_CONFIG_BAR_OFFSET, ISR_CONFIG_SIZE); const DEVICE_CONFIG_SIZE: u64 = 0x1000; -const NOTIFICATION_BAR_OFFSET: u64 = 0x6000; -const NOTIFICATION_SIZE: u64 = 0x1000; -const MSIX_TABLE_BAR_OFFSET: u64 = 0x8000; +const NOTIFICATION_BAR_OFFSET: u64 = next_bar_addr(DEVICE_CONFIG_BAR_OFFSET, DEVICE_CONFIG_SIZE); +const NOTIFICATION_SIZE: u64 = MAX_QUEUES * NOTIFY_OFF_MULTIPLIER as u64; +const MSIX_TABLE_BAR_OFFSET: u64 = next_bar_addr(NOTIFICATION_BAR_OFFSET, NOTIFICATION_SIZE); + // The size is 256KiB because the table can hold up to 2048 entries, with each // entry being 128 bits (4 DWORDS). const MSIX_TABLE_SIZE: u64 = 0x40000; -const MSIX_PBA_BAR_OFFSET: u64 = 0x48000; +const MSIX_PBA_BAR_OFFSET: u64 = next_bar_addr(MSIX_TABLE_BAR_OFFSET, MSIX_TABLE_SIZE); // The size is 2KiB because the Pending Bit Array has one bit per vector and it // can support up to 2048 vectors. const MSIX_PBA_SIZE: u64 = 0x800; // The BAR size must be a power of 2. -const CAPABILITY_BAR_SIZE: u64 = 0x80000; +const CAPABILITY_BAR_SIZE: u64 = (MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE).next_power_of_two(); const VIRTIO_COMMON_BAR_INDEX: usize = 0; const VIRTIO_SHM_BAR_INDEX: usize = 2; From e265543e3c283c7ef8fc381968fbebcc592d9883 Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Thu, 12 Mar 2026 17:00:57 +0100 Subject: [PATCH 0713/1893] 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 --- Cargo.toml | 8 ++++++++ api_client/Cargo.toml | 1 + arch/Cargo.toml | 1 + block/Cargo.toml | 1 + cloud-hypervisor/Cargo.toml | 9 +-------- devices/Cargo.toml | 1 + event_monitor/Cargo.toml | 1 + hypervisor/Cargo.toml | 1 + net_gen/Cargo.toml | 1 + net_util/Cargo.toml | 1 + option_parser/Cargo.toml | 1 + pci/Cargo.toml | 1 + rate_limiter/Cargo.toml | 1 + serial_buffer/Cargo.toml | 1 + test_infra/Cargo.toml | 1 + tpm/Cargo.toml | 1 + tracer/Cargo.toml | 1 + virtio-devices/Cargo.toml | 1 + vm-allocator/Cargo.toml | 1 + vm-device/Cargo.toml | 1 + vm-migration/Cargo.toml | 1 + vm-virtio/Cargo.toml | 1 + vmm/Cargo.toml | 1 + 23 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 93765ba718..4c4ad78e8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,14 @@ 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] diff --git a/api_client/Cargo.toml b/api_client/Cargo.toml index 93a7836fcc..1ab0e5862e 100644 --- a/api_client/Cargo.toml +++ b/api_client/Cargo.toml @@ -3,6 +3,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true license = "Apache-2.0" name = "api_client" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/arch/Cargo.toml b/arch/Cargo.toml index 804be793d0..2e30b9e532 100644 --- a/arch/Cargo.toml +++ b/arch/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Chromium OS Authors"] edition.workspace = true name = "arch" +rust-version.workspace = true version = "0.1.0" [features] diff --git a/block/Cargo.toml b/block/Cargo.toml index 70a731a731..ab62c2308c 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"] edition.workspace = true name = "block" +rust-version.workspace = true version = "0.1.0" [features] diff --git a/cloud-hypervisor/Cargo.toml b/cloud-hypervisor/Cargo.toml index d69773e743..8259b716fa 100644 --- a/cloud-hypervisor/Cargo.toml +++ b/cloud-hypervisor/Cargo.toml @@ -7,15 +7,8 @@ edition = "2024" homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" license = "Apache-2.0 AND BSD-3-Clause" name = "cloud-hypervisor" +rust-version.workspace = true version = "51.0.0" -# 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. -rust-version = "1.89.0" [dependencies] anyhow = { workspace = true } diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 06c99a1674..af4d7b73ce 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Chromium OS Authors"] edition.workspace = true name = "devices" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/event_monitor/Cargo.toml b/event_monitor/Cargo.toml index 18ac2567c3..ee52a7fd3d 100644 --- a/event_monitor/Cargo.toml +++ b/event_monitor/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "event_monitor" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index e18f9ba390..707779fefc 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -3,6 +3,7 @@ authors = ["Microsoft Authors"] edition.workspace = true license = "Apache-2.0 OR BSD-3-Clause" name = "hypervisor" +rust-version.workspace = true version = "0.1.0" [features] diff --git a/net_gen/Cargo.toml b/net_gen/Cargo.toml index a99c7c995d..dfbcee2af9 100644 --- a/net_gen/Cargo.toml +++ b/net_gen/Cargo.toml @@ -3,6 +3,7 @@ authors = ["The Chromium OS Authors"] edition = "2021" #edition.workspace = true name = "net_gen" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml index 9f124cf613..fccb89320f 100644 --- a/net_util/Cargo.toml +++ b/net_util/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Chromium OS Authors"] edition.workspace = true name = "net_util" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/option_parser/Cargo.toml b/option_parser/Cargo.toml index 3d76690b41..54c77e296b 100644 --- a/option_parser/Cargo.toml +++ b/option_parser/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "option_parser" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/pci/Cargo.toml b/pci/Cargo.toml index 760baae03d..c1b69f9854 100644 --- a/pci/Cargo.toml +++ b/pci/Cargo.toml @@ -2,6 +2,7 @@ authors = ["Samuel Ortiz "] edition.workspace = true name = "pci" +rust-version.workspace = true version = "0.1.0" [features] diff --git a/rate_limiter/Cargo.toml b/rate_limiter/Cargo.toml index 206ec7b7f8..2d32e8a25b 100644 --- a/rate_limiter/Cargo.toml +++ b/rate_limiter/Cargo.toml @@ -1,6 +1,7 @@ [package] edition.workspace = true name = "rate_limiter" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/serial_buffer/Cargo.toml b/serial_buffer/Cargo.toml index 767c8a97ff..89766d5e86 100644 --- a/serial_buffer/Cargo.toml +++ b/serial_buffer/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "serial_buffer" +rust-version.workspace = true version = "0.1.0" [lints] diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index 6c53e9ca0f..b5854521ad 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "test_infra" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/tpm/Cargo.toml b/tpm/Cargo.toml index 82dc8f79be..5d6bba1a04 100644 --- a/tpm/Cargo.toml +++ b/tpm/Cargo.toml @@ -3,6 +3,7 @@ authors = ["Microsoft Authors"] edition = "2021" license = "Apache-2.0" name = "tpm" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/tracer/Cargo.toml b/tracer/Cargo.toml index 1ac9f4e393..64f3399902 100644 --- a/tracer/Cargo.toml +++ b/tracer/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "tracer" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index 5cbfe145f4..41b9da8e0a 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "virtio-devices" +rust-version.workspace = true version = "0.1.0" [features] diff --git a/vm-allocator/Cargo.toml b/vm-allocator/Cargo.toml index a4996d6dc3..3826479313 100644 --- a/vm-allocator/Cargo.toml +++ b/vm-allocator/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Chromium OS Authors"] edition.workspace = true name = "vm-allocator" +rust-version.workspace = true version = "0.1.0" [features] diff --git a/vm-device/Cargo.toml b/vm-device/Cargo.toml index a57ea57f5b..358ffc7435 100644 --- a/vm-device/Cargo.toml +++ b/vm-device/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "vm-device" +rust-version.workspace = true version = "0.1.0" [features] diff --git a/vm-migration/Cargo.toml b/vm-migration/Cargo.toml index b17475065c..66b4e4f6a9 100644 --- a/vm-migration/Cargo.toml +++ b/vm-migration/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "vm-migration" +rust-version.workspace = true version = "0.1.0" [dependencies] diff --git a/vm-virtio/Cargo.toml b/vm-virtio/Cargo.toml index 228f552416..de90b209d0 100644 --- a/vm-virtio/Cargo.toml +++ b/vm-virtio/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "vm-virtio" +rust-version.workspace = true version = "0.1.0" [features] diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 43b1de14b4..35fe314299 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The Cloud Hypervisor Authors"] edition.workspace = true name = "vmm" +rust-version.workspace = true version = "0.1.0" [features] From 7d582ec0f68a71f6232658939de02fc0949b8aed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:53:56 +0000 Subject: [PATCH 0714/1893] 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] --- .github/workflows/docker-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index f077b51738..8636d35f00 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v6 - 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@v4 From 4ba2d770d150bc725a33770f053f46c8fa2381d9 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 12 Mar 2026 19:34:54 -0700 Subject: [PATCH 0715/1893] 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 --- scripts/run_integration_tests_cvm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_integration_tests_cvm.sh b/scripts/run_integration_tests_cvm.sh index edf543fc88..37e2cc74e7 100755 --- a/scripts/run_integration_tests_cvm.sh +++ b/scripts/run_integration_tests_cvm.sh @@ -27,7 +27,7 @@ popd || exit cargo build --features $build_features --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 -cargo nextest run $test_features "common_cvm::$test_filter" -- ${test_binary_args[*]} +time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "common_cvm::$test_filter" -- ${test_binary_args[*]} RES=$? exit $RES From f6a1d821d7a070fc00c4f6b1b6ded2a567dbf0dc Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 12 Mar 2026 18:52:34 -0700 Subject: [PATCH 0716/1893] 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 --- test_infra/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index fba95ed977..22a1ea99dd 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -76,7 +76,7 @@ pub struct GuestNetworkConfig { pub const DEFAULT_TCP_LISTENER_MESSAGE: &str = "booted"; pub const DEFAULT_TCP_LISTENER_PORT: u16 = 8000; pub const DEFAULT_TCP_LISTENER_TIMEOUT: u32 = 120; -pub const DEFAULT_CVM_TCP_LISTENER_TIMEOUT: u32 = 120; +pub const DEFAULT_CVM_TCP_LISTENER_TIMEOUT: u32 = 140; #[derive(Error, Debug)] pub enum WaitForBootError { From 3cbbce353e770dab2109e04d5c56f96c22980670 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 13 Mar 2026 09:25:16 +0100 Subject: [PATCH 0717/1893] 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 --- cloud-hypervisor/tests/integration.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 1d198828e9..bbc1999335 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14402,11 +14402,11 @@ mod rate_limiter { let test_blk_params = if bandwidth { format!( - "path={blk_rate_limiter_test_img},num_queues={num_queues},bw_size={bw_size},bw_refill_time={bw_refill_time}" + "path={blk_rate_limiter_test_img},num_queues={num_queues},bw_size={bw_size},bw_refill_time={bw_refill_time},image_type=raw" ) } else { format!( - "path={blk_rate_limiter_test_img},num_queues={num_queues},ops_size={bw_size},ops_refill_time={bw_refill_time}" + "path={blk_rate_limiter_test_img},num_queues={num_queues},ops_size={bw_size},ops_refill_time={bw_refill_time},image_type=raw" ) }; @@ -14512,7 +14512,7 @@ mod rate_limiter { ); disk_args.push(format!( - "path={test_img_path},num_queues={num_queues},rate_limit_group=group0" + "path={test_img_path},num_queues={num_queues},rate_limit_group=group0,image_type=raw" )); } From b5169ff4198833301ecfab1aaf277ae934c102ec Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 10 Mar 2026 09:26:28 +0100 Subject: [PATCH 0718/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 63 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 5c75a2db0e..cea3d2fa46 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2364,41 +2364,42 @@ impl RequestHandler for Vmm { ))); } - if let Some(vm) = self.vm.as_mut() { - Self::send_migration( - vm, - #[cfg(all(feature = "kvm", target_arch = "x86_64"))] - self.hypervisor.as_ref(), - &send_data_migration, - ) - .map_err(|migration_err| { - error!("Migration failed: {migration_err:?}"); + let vm = self + .vm + .as_mut() + .ok_or_else(|| MigratableError::MigrateSend(anyhow!("VM is not running")))?; - // Stop logging dirty pages only for non-local migrations - if !send_data_migration.local - && let Err(e) = vm.stop_dirty_log() - { - return e; - } + Self::send_migration( + vm, + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + self.hypervisor.as_ref(), + &send_data_migration, + ) + .map_err(|migration_err| { + error!("Migration failed: {migration_err:?}"); - if vm.get_state() == VmState::Paused - && let Err(e) = vm.resume() - { - return e; - } + // Stop logging dirty pages only for non-local migrations + if !send_data_migration.local + && let Err(e) = vm.stop_dirty_log() + { + return e; + } - migration_err - })?; + if vm.get_state() == VmState::Paused + && let Err(e) = vm.resume() + { + return e; + } - // Shutdown the VM after the migration succeeded - self.exit_evt.write(1).map_err(|e| { - MigratableError::MigrateSend(anyhow!( - "Failed shutting down the VM after migration: {e:?}" - )) - }) - } else { - Err(MigratableError::MigrateSend(anyhow!("VM is not running"))) - } + migration_err + })?; + + // Shutdown the VM after the migration succeeded + self.exit_evt.write(1).map_err(|e| { + MigratableError::MigrateSend(anyhow!( + "Failed shutting down the VM after migration: {e:?}" + )) + }) } } From 5c93bcf2d708b5dd5abbc19776bc1eab9e3b4b7d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 9 Mar 2026 15:32:30 +0100 Subject: [PATCH 0719/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index cea3d2fa46..96331fffa9 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2369,6 +2369,16 @@ impl RequestHandler for Vmm { .as_mut() .ok_or_else(|| MigratableError::MigrateSend(anyhow!("VM is not running")))?; + // Only running VMs can be migrated: Future work can fix this to allow + // also the migration of paused VMs while preserving the state in success + // and error case. See #7815. + if vm.get_state() != VmState::Running { + return Err(MigratableError::MigrateSend(anyhow!( + "VM is not in running state: {:?}", + vm.get_state() + ))); + } + Self::send_migration( vm, #[cfg(all(feature = "kvm", target_arch = "x86_64"))] From b90634a88714066eeb9da446fa7063a2f6623735 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 5 Mar 2026 21:02:43 +0100 Subject: [PATCH 0720/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 96331fffa9..bcfa41e27e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2305,6 +2305,8 @@ impl RequestHandler for Vmm { // Accept the connection and get the socket let mut socket = Vmm::receive_migration_socket(&receive_data_migration.receiver_url)?; + event!("vm", "migration-receive-started"); + let mut state = ReceiveMigrationState::Established; while !state.finished() { @@ -2334,8 +2336,11 @@ impl RequestHandler for Vmm { } if let ReceiveMigrationState::Aborted = state { + event!("vm", "migration-receive-failed"); self.vm = None; self.vm_config = None; + } else { + event!("vm", "migration-receive-finished"); } Ok(()) @@ -2379,6 +2384,7 @@ impl RequestHandler for Vmm { ))); } + event!("vm", "migration-started"); Self::send_migration( vm, #[cfg(all(feature = "kvm", target_arch = "x86_64"))] @@ -2387,6 +2393,7 @@ impl RequestHandler for Vmm { ) .map_err(|migration_err| { error!("Migration failed: {migration_err:?}"); + event!("vm", "migration-failed"); // Stop logging dirty pages only for non-local migrations if !send_data_migration.local @@ -2404,6 +2411,8 @@ impl RequestHandler for Vmm { migration_err })?; + event!("vm", "migration-finished"); + // Shutdown the VM after the migration succeeded self.exit_evt.write(1).map_err(|e| { MigratableError::MigrateSend(anyhow!( From d4b5502472d706c8e9c2241eb856fcac72740f62 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 6 Mar 2026 13:28:59 +0100 Subject: [PATCH 0721/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/memory_manager.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 03b7b8a837..2cb735ecd1 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -2673,11 +2673,11 @@ impl Migratable for MemoryManager { let sub_table = MemoryRangeTable::from_dirty_bitmap(dirty_bitmap, r.gpa, 4096); if sub_table.regions().is_empty() { - info!("Dirty Memory Range Table is empty"); + debug!("Dirty Memory Range Table is empty"); } else { - info!("Dirty Memory Range Table:"); + debug!("Dirty Memory Range Table:"); for range in sub_table.regions() { - info!("GPA: {:x} size: {} (KiB)", range.gpa, range.length / 1024); + debug!("GPA: {:x} size: {} (KiB)", range.gpa, range.length / 1024); } } From 1f5c5093e44089b13a68747c14a73bcd7a8b0d84 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 12 Mar 2026 07:09:42 +0100 Subject: [PATCH 0722/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 120 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 42 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index bcfa41e27e..4186386787 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -27,7 +27,7 @@ use console_devices::{ConsoleInfo, pre_create_console_devices}; use event_monitor::event; use landlock::LandlockError; use libc::{EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW, tcsetattr, termios}; -use log::{error, info, trace, warn}; +use log::{debug, error, info, trace, warn}; use memory_manager::MemoryManagerSnapshotData; use pci::PciBdf; use seccompiler::{SeccompAction, apply_filter}; @@ -1216,29 +1216,94 @@ impl Vmm { } } - // Returns true if there were dirty pages to send - fn vm_maybe_send_dirty_pages( + /// Transmits the given [`MemoryRangeTable`] over the wire if there is at + /// least one region. + /// + /// Sends a memory migration request, the range table, and the corresponding + /// guest memory regions over the given socket. Waits for acknowledgment + /// from the destination. + fn vm_send_dirty_pages( vm: &mut Vm, socket: &mut SocketStream, - ) -> result::Result { - // Send (dirty) memory table - let table = vm.dirty_log()?; - - // But if there are no regions go straight to pause + table: &MemoryRangeTable, + ) -> result::Result<(), MigratableError> { if table.regions().is_empty() { - return Ok(false); + return Ok(()); } - Request::memory(table.length()).write_to(socket).unwrap(); + Request::memory(table.length()).write_to(socket)?; table.write_to(socket)?; // And then the memory itself - vm.send_memory_regions(&table, socket)?; + vm.send_memory_regions(table, socket)?; Response::read_from(socket)?.ok_or_abandon( socket, MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")), )?; - Ok(true) + Ok(()) + } + + /// Performs the initial memory transmission (iteration zero) plus a + /// variable number of memory iterations with the goal to eventually migrate + /// the VM in a reasonably small downtime. + /// + /// This returns as soon as the precopy migration indicates it is converged + /// (e.g., reasonably small downtime) is reached. + fn do_memory_iterations( + vm: &mut Vm, + socket: &mut SocketStream, + iteration_counter: &mut u64, + is_converged: impl Fn(u64) -> bool, + ) -> result::Result { + loop { + let iteration_table = if *iteration_counter == 0 { + vm.memory_range_table()? + } else { + // TODO do this in a thread #7816 + vm.dirty_log()? + }; + + if is_converged(*iteration_counter) { + debug!("Precopy converged: iter={iteration_counter}"); + break Ok(iteration_table); + } + + // Send the current dirty pages + Self::vm_send_dirty_pages(vm, socket, &iteration_table)?; + + // Prepare next iteration. + *iteration_counter += 1; + } + } + + /// Performs the memory migration including multiple iterations. + /// + /// This includes: + /// - initial memory - VM is running + /// - multiple memory delta transmissions - VM is running + /// - final memory iteration - VM is paused + fn do_memory_migration( + vm: &mut Vm, + socket: &mut SocketStream, + ) -> result::Result<(), MigratableError> { + const MAX_ITERATIONS: u64 = 5; + + let mut iteration_counter = 0; + let is_converged = |iteration_counter: u64| iteration_counter >= MAX_ITERATIONS; + + vm.start_dirty_log()?; + let remaining = + Self::do_memory_iterations(vm, socket, &mut iteration_counter, is_converged)?; + vm.pause()?; + + // Send last batch of dirty pages + let mut final_table = vm.dirty_log()?; + final_table.extend(remaining); + Vmm::vm_send_dirty_pages(vm, socket, &final_table)?; + + info!("Memory migration complete"); + + Ok(()) } fn send_migration( @@ -1323,36 +1388,7 @@ impl Vmm { // Now pause VM vm.pause()?; } else { - // Start logging dirty pages - vm.start_dirty_log()?; - - // Send memory table - let table = vm.memory_range_table()?; - Request::memory(table.length()) - .write_to(&mut socket) - .unwrap(); - table.write_to(&mut socket)?; - // And then the memory itself - vm.send_memory_regions(&table, &mut socket)?; - Response::read_from(&mut socket)?.ok_or_abandon( - &mut socket, - MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")), - )?; - - // Try at most 5 passes of dirty memory sending - const MAX_DIRTY_MIGRATIONS: usize = 5; - for i in 0..MAX_DIRTY_MIGRATIONS { - info!("Dirty memory migration {i} of {MAX_DIRTY_MIGRATIONS}"); - if !Self::vm_maybe_send_dirty_pages(vm, &mut socket)? { - break; - } - } - - // Now pause VM - vm.pause()?; - - // Send last batch of dirty pages - Self::vm_maybe_send_dirty_pages(vm, &mut socket)?; + Self::do_memory_migration(vm, &mut socket)?; } // We release the locks early to enable locking them on the destination host. From 447a4c236b9ae8140b813511bfffec45e84be5fa Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 12 Mar 2026 07:09:51 +0100 Subject: [PATCH 0723/1893] vmm: migration: add code comment Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/vm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 3f793cd807..a094803ee4 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2850,6 +2850,8 @@ impl Vm { Ok(()) } + /// Writes the contents of the given guest memory regions to the provided sink. + /// Used, for example, during VM live migration to transfer memory to a socket. pub fn send_memory_regions( &mut self, ranges: &MemoryRangeTable, From beb58084065d16d73307a5faf423e05080c77592 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 10 Mar 2026 09:19:58 +0100 Subject: [PATCH 0724/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vm-migration/src/context.rs | 406 +++++++++++++++++++++++++++++++++++ vm-migration/src/lib.rs | 2 + vm-migration/src/protocol.rs | 5 + 3 files changed, 413 insertions(+) create mode 100644 vm-migration/src/context.rs diff --git a/vm-migration/src/context.rs b/vm-migration/src/context.rs new file mode 100644 index 0000000000..7dfa5b7d9e --- /dev/null +++ b/vm-migration/src/context.rs @@ -0,0 +1,406 @@ +// Copyright © 2026 Cyberus Technology GmbH +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! Module for [`MemoryMigrationContext`]. + +use std::fmt; +use std::fmt::Display; +use std::time::{Duration, Instant}; + +use crate::protocol::MemoryRangeTable; + +/// Internal metrics for the precopy migration phase. +/// +/// The context aggregates runtime statistics such as iteration count, +/// transferred bytes, durations, bandwidth, and estimated downtime. +/// These metrics allow the migration logic to make decisions based on +/// observed runtime behavior, for example terminating further iterations +/// once the expected downtime falls below a configured threshold. +/// +/// The structure is updated both between iterations and during an +/// iteration so that it always reflects the most recent state. +#[derive(Debug, PartialEq)] +pub struct MemoryMigrationContext { + /// Current iteration: 0 initial total transmission, >0 delta transmission. + pub iteration: usize, + /// Total bytes sent across all iterations. + total_sent_bytes: u64, + /// Total bytes to send in the current iteration. + pub current_iteration_total_bytes: u64, + /// The currently measured bandwidth. + /// + /// This is updated (at least) after each completed iteration. + bandwidth_bytes_per_second: f64, + /// Calculated downtime in milliseconds regarding the current bandwidth and + /// the remaining memory. + /// + /// This is only `None` for iteration 0. + /// + /// Please note that this ignores any additional migration overhead and + /// only looks at the memory transfer itself. + estimated_downtime: Option, + /// Begin of the memory migration. + migration_begin: Instant, + /// Duration of the memory migration. + /// + /// This is only `None` until the last iteration is finished. + migration_duration: Option, + /// Begin of the current iteration. + iteration_begin: Instant, + /// Duration of the current iteration. + /// + /// This includes the transmission, all logging, and update of any metrics. + /// + /// This is only `None` for iteration 0. + iteration_duration: Option, + /// Begin of the current transfer. + transfer_begin: Instant, + /// Duration of the current transfer. + /// + /// This is only `None` for iteration 0. + transfer_duration: Option, +} + +impl MemoryMigrationContext { + /// Creates a new context. + /// + /// Please note that you should create this struct right before the precopy + /// memory migration starts, as the field `migration_begin` is set to + /// [`Instant::now`]. + pub fn new() -> Self { + Self { + iteration: 0, + total_sent_bytes: 0, + current_iteration_total_bytes: 0, + bandwidth_bytes_per_second: 0.0, + estimated_downtime: None, + migration_begin: Instant::now(), + migration_duration: None, + // Will be updated soon -> so this value is never read + iteration_begin: Instant::now(), + iteration_duration: None, + // Will be updated soon -> so this value is never read + transfer_begin: Instant::now(), + transfer_duration: None, + } + } + + /// Updates the metrics right before the transfer over the wire. + /// + /// Supposed to be called once per precopy memory iteration. + /// + /// This helps to feed the "is converged?" with fresh metrics to + /// potentially stop the precopy phase. + pub fn update_metrics_before_transfer( + &mut self, + iteration_begin: Instant, + iteration_table: &MemoryRangeTable, + ) { + self.iteration_begin = iteration_begin; + self.current_iteration_total_bytes = iteration_table.effective_size(); + self.estimated_downtime = if self.current_iteration_total_bytes == 0 { + Some(Duration::ZERO) + } else if self.bandwidth_bytes_per_second == 0.0 { + // Only happens on the very first iteration + None + } else { + let calculated_downtime_s = + self.current_iteration_total_bytes as f64 / (self.bandwidth_bytes_per_second); + Some(Duration::from_secs_f64(calculated_downtime_s)) + } + } + + /// Updates the metrics right after the transfer over the wire. + /// + /// Supposed to be called once per precopy memory iteration. + /// + /// This updates the bandwidth and ensures that + /// [`Self::update_metrics_before_transfer`] operates on fresh metrics on + /// the new iteration. + /// + /// # Panics + /// + /// If the transfer duration is longer than the iteration duration, this + /// function panics. This can never happen with real-world data but in + /// artificial unit test scenarios. + pub fn update_metrics_after_transfer( + &mut self, + transfer_begin: Instant, + transfer_duration: Duration, + ) { + self.transfer_begin = transfer_begin; + self.transfer_duration = Some(transfer_duration); + self.total_sent_bytes += self.current_iteration_total_bytes; + self.bandwidth_bytes_per_second = + Self::calculate_bandwidth(self.current_iteration_total_bytes, transfer_duration); + + // We might have a few operations after that before the loop starts + // (e.g., logging) again, but practically, this is negligible for this + // metric. + self.iteration_duration = Some(self.iteration_begin.elapsed()); + + // Catch programming errors: + // unwrap is fine as both values are set by now + assert!( + self.iteration_duration.unwrap() >= self.transfer_duration.unwrap(), + "iteration_duration must be larger than transfer_duration: {}ms < {}ms", + self.iteration_duration.unwrap().as_millis(), + self.transfer_duration.unwrap().as_millis(), + ); + } + + /// Finalizes the metrics. + /// + /// From now on, the metrics are considered finalized and should not be + /// modified. They can be stored for further analysis. + #[inline] + pub fn finalize(&mut self) { + // Any overhead from the function call is negligible. + self.migration_duration = Some(self.migration_begin.elapsed()); + } + + /// Returns the average bandwidth over the whole duration of the migration. + #[inline] + pub fn average_bandwidth(&self) -> f64 { + Self::calculate_bandwidth(self.total_sent_bytes, self.migration_begin.elapsed()) + } + + /// Calculates the bandwidth in bytes per second. + /// + /// Returns `0.0` if the duration is zero to avoid division by zero. + #[inline] + fn calculate_bandwidth(bytes: u64, duration: Duration) -> f64 { + if duration == Duration::ZERO { + 0.0 + } else { + bytes as f64 / duration.as_secs_f64() + } + } +} + +impl Default for MemoryMigrationContext { + fn default() -> Self { + Self::new() + } +} + +// The display format must be a compact one-liner to enable concise log messages per iteration. +impl Display for MemoryMigrationContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let curr_mib = self.current_iteration_total_bytes.div_ceil(1024 * 1024); + let total_mib = self.total_sent_bytes.div_ceil(1024 * 1024); + + // Current bandwidth in MiB/s + let curr_bw_mib_s = self.bandwidth_bytes_per_second / 1024.0 / 1024.0; + + // Time elapsed since memory migration start. + let elapsed = self + .migration_duration + .unwrap_or_else(|| Instant::now() - self.migration_begin) + .as_secs_f64(); + + // Internally, this again evaluates `self.migration_begin.elapsed()` + // but this is negligible. + let avg_bw_mib_s = self.average_bandwidth() / 1024.0 / 1024.0; + + // Transfer duration and iteration overhead + let transfer_s = self.transfer_duration.map_or(0.0, |d| d.as_secs_f64()); + let iteration_overhead_ms = self + .iteration_duration + .and_then(|iter| { + self.transfer_duration.map(|tr| { + // This is guaranteed by update_metrics_after_transfer() + assert!(iter >= tr); + (iter - tr).as_millis() + }) + }) + .unwrap_or(0); + + let est_downtime_ms = self.estimated_downtime.map_or(0, |d| d.as_millis()); + + write!( + f, + "iter={} \ + curr={curr_mib}MiB \ + total={total_mib}MiB \ + bw={curr_bw_mib_s:.2}MiB/s \ + transfer={transfer_s:.2}s \ + overhead={iteration_overhead_ms}ms \ + est_downtime={est_downtime_ms}ms \ + elapsed={elapsed:.2}s \ + avg_bw={avg_bw_mib_s:.2}MiB/s", + self.iteration, + ) + } +} + +#[cfg(test)] +mod unit_tests { + use std::time::{Duration, Instant}; + + use super::*; + use crate::protocol::MemoryRange; + + fn make_table(bytes: u64) -> MemoryRangeTable { + let mut table = MemoryRangeTable::default(); + if bytes > 0 { + table.push(MemoryRange { + gpa: 0, + length: bytes, + }); + } + table + } + + /// A controlled migration scenario with fixed timing offsets. + /// + /// ```text + /// migration_begin + /// + 1.0s -> iteration_begin + /// + 1.1s -> transfer_begin + /// + 2.0s -> transfer ends (transfer_duration = 0.9s) + /// + 2.1s -> iteration ends (iteration_duration = 1.1s, overhead = 0.2s) + /// ``` + struct Scenario { + migration_begin: Instant, + iteration_begin: Instant, + transfer_begin: Instant, + transfer_duration: Duration, + } + + impl Scenario { + /// We use a fixed point in the past so all offsets are in the past too, + /// meaning elapsed() calls in the code under test will be >= our durations. + const FIXPOINT_PAST: Duration = Duration::from_secs(10); + + fn new() -> Self { + // Use a fixed point in the past so all offsets are in the past too, + // meaning elapsed() calls in the code under test will be >= our durations. + let migration_begin = Instant::now() - Self::FIXPOINT_PAST; + Self { + migration_begin, + iteration_begin: migration_begin + Duration::from_millis(1000), + transfer_begin: migration_begin + Duration::from_millis(1100), + transfer_duration: Duration::from_millis(900), + } + } + + fn make_ctx(&self) -> MemoryMigrationContext { + let mut ctx = MemoryMigrationContext::new(); + // Override migration_begin with our controlled value. + ctx.migration_begin = self.migration_begin; + ctx + } + } + + #[test] + fn before_transfer_updates_begin_and_bytes() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(4096)); + + assert_eq!(ctx.iteration_begin, s.iteration_begin); + assert_eq!(ctx.current_iteration_total_bytes, 4096); + } + + #[test] + fn before_transfer_estimated_downtime() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + // Empty table -> zero downtime regardless of bandwidth + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(0)); + assert_eq!(ctx.estimated_downtime, Some(Duration::ZERO)); + + // No bandwidth yet -> None + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + assert_eq!(ctx.estimated_downtime, None); + + // 1024 B/s, 1024 bytes -> 1s + ctx.bandwidth_bytes_per_second = 1024.0; + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + assert_eq!(ctx.estimated_downtime, Some(Duration::from_secs(1))); + } + + #[test] + fn after_transfer_updates_timing_and_bandwidth() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); + + assert_eq!(ctx.transfer_begin, s.transfer_begin); + assert_eq!(ctx.transfer_duration, Some(s.transfer_duration)); + // 1024 bytes / 0.9s + assert_eq!(ctx.bandwidth_bytes_per_second, 1024.0 / 0.9); + // iteration_duration = time from iteration_begin until now (>= transfer_duration) + assert!(ctx.iteration_duration.unwrap() >= s.transfer_duration); + // Zero transfer_duration -> bandwidth is 0.0, no division by zero + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, Duration::ZERO); + assert_eq!(ctx.bandwidth_bytes_per_second, 0.0); + + // Check finalize() sets migration duration + assert_eq!(ctx.migration_duration, None); + ctx.finalize(); + assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); + } + + #[test] + fn two_iterations_accumulate_bytes_and_feed_downtime_estimate() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + // Iteration 0: no bandwidth yet -> downtime is None + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + assert_eq!(ctx.estimated_downtime, None); + ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); + assert_eq!(ctx.total_sent_bytes, 1024); + + // Iteration 1: bandwidth now known -> downtime is Some + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(2048)); + assert!(ctx.estimated_downtime.is_some()); + ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); + assert_eq!(ctx.total_sent_bytes, 1024 + 2048); + + // Check finalize() sets migration duration + assert_eq!(ctx.migration_duration, None); + ctx.finalize(); + assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); + } + + #[test] + /// The display format is specifically crafted to be very insightful in logs. + /// Therefore, we have a dedicated test for that format. + fn display_format() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + // Iteration 0: 1 MiB in 1s + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024 * 1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); + ctx.iteration += 1; + + // Iteration 1: 512 KiB in 1s; fix migration_duration for deterministic elapsed/avg_bw + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(512 * 1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); + + ctx.migration_duration = Some(Duration::from_secs(2)); + let out = ctx.to_string(); + + assert_eq!( + out, + "iter=1 curr=1MiB total=2MiB bw=0.50MiB/s transfer=1.00s overhead=8000ms est_downtime=500ms elapsed=2.00s avg_bw=0.15MiB/s" + ); + + // Should change elapsed() time! + // Since this is at least 10s, we never face timing issues in CI! + ctx.finalize(); + let out2 = ctx.to_string(); + assert_ne!(out2, out, "elapsed time should have changed! is={out2}"); + } +} diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 921ae5b3db..22da8df7f4 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -4,12 +4,14 @@ // use anyhow::anyhow; +pub use context::MemoryMigrationContext; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::protocol::MemoryRangeTable; mod bitpos_iterator; +mod context; pub mod protocol; #[derive(Error, Debug)] diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index 4dfec4f625..ab5975d4e5 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -376,6 +376,11 @@ impl MemoryRangeTable { } Self { data } } + + /// Returns the effective size in bytes. + pub fn effective_size(&self) -> u64 { + self.data.iter().map(|r| r.length).sum() + } } #[cfg(test)] From 39768704f3227afad4a77f4dd36ce526d5bf686d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 10 Mar 2026 09:20:03 +0100 Subject: [PATCH 0725/1893] 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: 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: 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: 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: 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: 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: 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: 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: 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: 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 On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 62 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 4186386787..007db270e6 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -39,7 +39,10 @@ use tracer::trace_scoped; use vm_memory::bitmap::{AtomicBitmap, BitmapSlice}; use vm_memory::{ReadVolatile, VolatileMemoryError, VolatileSlice, WriteVolatile}; use vm_migration::protocol::*; -use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; +use vm_migration::{ + MemoryMigrationContext, Migratable, MigratableError, Pausable, Snapshot, Snapshottable, + Transportable, +}; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::signal::unblock_signal; use vmm_sys_util::sock_ctrl_msg::ScmSocket; @@ -1252,27 +1255,37 @@ impl Vmm { fn do_memory_iterations( vm: &mut Vm, socket: &mut SocketStream, - iteration_counter: &mut u64, - is_converged: impl Fn(u64) -> bool, + ctx: &mut MemoryMigrationContext, + is_converged: impl Fn(&MemoryMigrationContext) -> bool, ) -> result::Result { loop { - let iteration_table = if *iteration_counter == 0 { + let iteration_begin = Instant::now(); + + let iteration_table = if ctx.iteration == 0 { vm.memory_range_table()? } else { // TODO do this in a thread #7816 vm.dirty_log()? }; - if is_converged(*iteration_counter) { - debug!("Precopy converged: iter={iteration_counter}"); + ctx.update_metrics_before_transfer(iteration_begin, &iteration_table); + if is_converged(ctx) { + debug!("Precopy converged: {ctx}"); break Ok(iteration_table); } // Send the current dirty pages + let transfer_begin = Instant::now(); Self::vm_send_dirty_pages(vm, socket, &iteration_table)?; + let transfer_duration = transfer_begin.elapsed(); + ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); - // Prepare next iteration. - *iteration_counter += 1; + // Log progress of the current iteration + debug!("Precopy: {ctx}"); + + // Increment iteration last: This way we ensure that the logging + // above matches the actual iteration. + ctx.iteration += 1; } } @@ -1286,22 +1299,35 @@ impl Vmm { vm: &mut Vm, socket: &mut SocketStream, ) -> result::Result<(), MigratableError> { - const MAX_ITERATIONS: u64 = 5; + const MAX_ITERATIONS: usize = 5; - let mut iteration_counter = 0; - let is_converged = |iteration_counter: u64| iteration_counter >= MAX_ITERATIONS; + let mut ctx = MemoryMigrationContext::new(); + let is_converged = |ctx: &MemoryMigrationContext| { + // TODO: Add check for configurable downtime and max migration time #7111 + ctx.iteration >= MAX_ITERATIONS || ctx.current_iteration_total_bytes == 0 + }; vm.start_dirty_log()?; - let remaining = - Self::do_memory_iterations(vm, socket, &mut iteration_counter, is_converged)?; + let remaining = Self::do_memory_iterations(vm, socket, &mut ctx, is_converged)?; vm.pause()?; - // Send last batch of dirty pages - let mut final_table = vm.dirty_log()?; - final_table.extend(remaining); - Vmm::vm_send_dirty_pages(vm, socket, &final_table)?; + // Send last batch of dirty pages: final iteration + { + let iteration_begin = Instant::now(); + + let mut final_table = vm.dirty_log()?; + final_table.extend(remaining); + + ctx.update_metrics_before_transfer(iteration_begin, &final_table); + let transfer_begin = Instant::now(); + Vmm::vm_send_dirty_pages(vm, socket, &final_table)?; + let transfer_duration = transfer_begin.elapsed(); + ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); + ctx.iteration += 1; + } + ctx.finalize(); - info!("Memory migration complete"); + info!("Precopy complete: {ctx}"); Ok(()) } From fcdb10373bfc954711e9cdd915f09231f8690942 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 6 Mar 2026 13:16:45 +0100 Subject: [PATCH 0726/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 007db270e6..f03927ce3e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1283,6 +1283,14 @@ impl Vmm { // Log progress of the current iteration debug!("Precopy: {ctx}"); + // Enables management software (e.g., libvirt) to easily track forward progress. + event!( + "vm", + "migration-memory-iteration", + "id", + ctx.iteration.to_string() + ); + // Increment iteration last: This way we ensure that the logging // above matches the actual iteration. ctx.iteration += 1; From de37f2794503546dbde292f9c8c240640107aab5 Mon Sep 17 00:00:00 2001 From: Shayon Mukherjee Date: Fri, 13 Mar 2026 05:47:16 -0700 Subject: [PATCH 0727/1893] 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 --- vmm/src/userfaultfd.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 vmm/src/userfaultfd.rs diff --git a/vmm/src/userfaultfd.rs b/vmm/src/userfaultfd.rs new file mode 100644 index 0000000000..3f8447a327 --- /dev/null +++ b/vmm/src/userfaultfd.rs @@ -0,0 +1,25 @@ +// Copyright © 2026 Cloud Hypervisor Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// See include/uapi/linux/userfaultfd.h in the kernel code. +pub const UFFDIO_API: u64 = 0xc018_aa3f; // _IOWR(0xAA, 0x3F, struct uffdio_api) +pub const UFFDIO_REGISTER: u64 = 0xc020_aa00; // _IOWR(0xAA, 0x00, struct uffdio_register) +pub const UFFDIO_COPY: u64 = 0xc028_aa03; // _IOWR(0xAA, 0x03, struct uffdio_copy) +pub const UFFDIO_WAKE: u64 = 0x4010_aa02; // _IOW(0xAA, 0x02, struct uffdio_range) + +// Seccomp compares these as Dword (u32); ensure they fit. +const _: () = assert!(UFFDIO_API <= u32::MAX as u64); +const _: () = assert!(UFFDIO_REGISTER <= u32::MAX as u64); +const _: () = assert!(UFFDIO_COPY <= u32::MAX as u64); +const _: () = assert!(UFFDIO_WAKE <= u32::MAX as u64); + +pub const UFFD_API: u64 = 0xAA; +pub const UFFDIO_REGISTER_MODE_MISSING: u64 = 1; +pub const UFFD_EVENT_PAGEFAULT: u8 = 0x12; +pub const UFFD_FEATURE_MISSING_SHMEM: u64 = 1 << 5; +pub const UFFD_FEATURE_MISSING_HUGETLBFS: u64 = 1 << 6; + +const _UFFDIO_COPY: u64 = 0x03; +const _UFFDIO_WAKE: u64 = 0x02; +pub const UFFD_API_RANGE_IOCTLS_BASIC: u64 = (1 << _UFFDIO_WAKE) | (1 << _UFFDIO_COPY); From 8340307ace21305ad76757159dac88432dd4c8d6 Mon Sep 17 00:00:00 2001 From: Shayon Mukherjee Date: Fri, 13 Mar 2026 05:47:16 -0700 Subject: [PATCH 0728/1893] 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 --- vmm/src/uffd.rs | 167 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 vmm/src/uffd.rs diff --git a/vmm/src/uffd.rs b/vmm/src/uffd.rs new file mode 100644 index 0000000000..eb73f46592 --- /dev/null +++ b/vmm/src/uffd.rs @@ -0,0 +1,167 @@ +// Copyright © 2026 Cloud Hypervisor Authors +// +// SPDX-License-Identifier: Apache-2.0 + +//! Minimal userfaultfd bindings for demand-paged snapshot restore. +//! +//! Uses the `userfaultfd(2)` syscall (available since Linux 4.3) to create a +//! fault descriptor, then `UFFDIO_API` / `UFFDIO_REGISTER` / `UFFDIO_COPY` +//! ioctls to handle page faults from a background thread. +//! +//! Unlike an mmap(MAP_PRIVATE) overlay approach, UFFD does not replace the +//! original memory mapping, so it remains compatible with VFIO device +//! passthrough and shared-memory-backed guest RAM. + +use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd}; + +use crate::userfaultfd; + +#[repr(C)] +pub(crate) struct UffdioApi { + pub api: u64, + pub features: u64, + pub ioctls: u64, +} + +#[repr(C)] +pub(crate) struct UffdioRegister { + pub range_start: u64, + pub range_len: u64, + pub mode: u64, + pub ioctls: u64, +} + +#[repr(C)] +pub(crate) struct UffdioCopy { + pub dst: u64, + pub src: u64, + pub len: u64, + pub mode: u64, + pub copy: i64, +} + +/// Flat representation of `struct uffd_msg` (32 bytes). +/// +/// The kernel struct contains an 8-byte header followed by a 24-byte +/// union (`arg`). We only use the `arg.pagefault` variant, so the +/// union is flattened into its pagefault fields here. The trailing +/// 8 bytes (`arg.pagefault.feat` + padding) are unused. +#[repr(C)] +pub(crate) struct UffdMsg { + pub event: u8, + _reserved1: u8, + _reserved2: u16, + _reserved3: u32, + pub pf_flags: u64, + pub pf_address: u64, + _pad: [u8; 8], +} + +const _: () = assert!(std::mem::size_of::() == 32); + +/// Create a userfaultfd file descriptor and perform the API handshake. +pub(crate) fn create(required_features: u64) -> Result { + // SAFETY: `userfaultfd` syscall with O_CLOEXEC | O_NONBLOCK flags. + let fd = unsafe { libc::syscall(libc::SYS_userfaultfd, libc::O_CLOEXEC | libc::O_NONBLOCK) }; + if fd < 0 { + return Err(std::io::Error::last_os_error()); + } + // SAFETY: the syscall returned a valid fd above. + let fd = unsafe { OwnedFd::from_raw_fd(fd as std::os::unix::io::RawFd) }; + + let mut api = UffdioApi { + api: userfaultfd::UFFD_API, + features: required_features, + ioctls: 0, + }; + // SAFETY: `api` is a valid, correctly-sized struct for this ioctl. + let ret = unsafe { + libc::ioctl( + fd.as_raw_fd(), + userfaultfd::UFFDIO_API as libc::Ioctl, + &mut api, + ) + }; + if ret < 0 { + return Err(std::io::Error::last_os_error()); + } + + Ok(fd) +} + +/// Register a memory range for missing-page fault handling. +pub(crate) fn register(fd: BorrowedFd<'_>, addr: u64, len: u64) -> Result { + let mut reg = UffdioRegister { + range_start: addr, + range_len: len, + mode: userfaultfd::UFFDIO_REGISTER_MODE_MISSING, + ioctls: 0, + }; + // SAFETY: `reg` is a valid, correctly-sized struct for this ioctl. + let ret = unsafe { + libc::ioctl( + fd.as_raw_fd(), + userfaultfd::UFFDIO_REGISTER as libc::Ioctl, + &mut reg, + ) + }; + if ret < 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(reg.ioctls) +} + +/// Resolve a page fault by copying data into the faulted address. +pub(crate) fn copy( + fd: BorrowedFd<'_>, + dst: u64, + src: *const u8, + len: u64, +) -> Result<(), std::io::Error> { + let mut cp = UffdioCopy { + dst, + src: src as u64, + len, + mode: 0, + copy: 0, + }; + // SAFETY: `cp` is a valid, correctly-sized struct for this ioctl. + let ret = unsafe { + libc::ioctl( + fd.as_raw_fd(), + userfaultfd::UFFDIO_COPY as libc::Ioctl, + &mut cp, + ) + }; + if ret < 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) +} + +#[repr(C)] +struct UffdioRange { + start: u64, + len: u64, +} + +/// Wake threads waiting on a fault in the given range without copying data. +/// +/// Needed after UFFDIO_COPY returns EEXIST: the page was already resolved +/// by a concurrent fault, but any additional threads blocked on that page +/// may not have been woken. +pub(crate) fn wake(fd: BorrowedFd<'_>, addr: u64, len: u64) -> Result<(), std::io::Error> { + let mut range = UffdioRange { start: addr, len }; + // SAFETY: `range` is a valid, correctly-sized struct for this ioctl. + let ret = unsafe { + libc::ioctl( + fd.as_raw_fd(), + userfaultfd::UFFDIO_WAKE as libc::Ioctl, + &mut range, + ) + }; + if ret < 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) +} From bf85af907e000b02aa6327a59f5fdc6b3ac22b33 Mon Sep 17 00:00:00 2001 From: Shayon Mukherjee Date: Fri, 13 Mar 2026 05:47:16 -0700 Subject: [PATCH 0729/1893] 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 --- docs/snapshot_restore.md | 22 ++++++ vmm/src/api/openapi/cloud-hypervisor.yaml | 7 ++ vmm/src/config.rs | 95 ++++++++++++++++++++++- 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/docs/snapshot_restore.md b/docs/snapshot_restore.md index df7248805e..2cf8eda5a8 100644 --- a/docs/snapshot_restore.md +++ b/docs/snapshot_restore.md @@ -93,6 +93,28 @@ start using it. At this point, the VM is fully restored and is identical to the VM which was snapshot earlier. +Restore also supports selecting how guest memory is populated: + +```bash +./cloud-hypervisor \ + --api-socket /tmp/cloud-hypervisor.sock \ + --restore source_url=file:///home/foo/snapshot,memory_restore_mode=ondemand +``` + +If `memory_restore_mode` is omitted, Cloud Hypervisor uses the eager-copy +restore path (`copy`). + +With `memory_restore_mode=ondemand`, restore uses `userfaultfd` to fault snapshot +pages in on first access instead of copying the full `memory-ranges` file into +guest RAM before restore completes. This mode is strict: if Cloud Hypervisor +cannot enable the `userfaultfd` restore path, restore fails instead of falling +back to `copy`. + +Current constraints for `memory_restore_mode=ondemand`: + +- `prefault=on` is not supported +- the snapshot memory ranges must be page-aligned + ## Restore a VM with new Net FDs For a VM created with FDs explicitly passed to NetConfig, a set of valid FDs need to be provided along with the VM restore command in the following syntax: diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index efdcf7a678..8bdf14e50f 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -1342,6 +1342,11 @@ components: destination_url: type: string + MemoryRestoreMode: + type: string + enum: [Copy, OnDemand] + default: Copy + RestoreConfig: required: - source_url @@ -1351,6 +1356,8 @@ components: type: string prefault: type: boolean + memory_restore_mode: + $ref: "#/components/schemas/MemoryRestoreMode" ReceiveMigrationData: required: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 42cbcfdbc0..8b284660a1 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -356,6 +356,9 @@ pub enum ValidationError { /// Number of FDs passed during Restore are incorrect to the NetConfig #[error("Number of Net FDs passed for '{0}' during Restore: {1}. Expected: {2}")] RestoreNetFdCountMismatch(String, usize, usize), + /// Prefault cannot be combined with on-demand restore + #[error("'prefault' cannot be combined with 'memory_restore_mode=ondemand'")] + InvalidRestorePrefaultWithOnDemand, /// Path provided in landlock-rules doesn't exist #[error("Path {0:?} provided in landlock-rules does not exist")] LandlockPathDoesNotExist(PathBuf), @@ -2564,27 +2567,61 @@ where } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] +pub enum MemoryRestoreMode { + /// Restore by eagerly copying the snapshot into guest RAM before resume. + #[default] + Copy, + /// Restore lazily by faulting snapshot pages into guest RAM on demand. + OnDemand, +} + +#[derive(Debug, Error)] +pub enum MemoryRestoreModeParseError { + #[error("Invalid value: {0}")] + InvalidValue(String), +} + +impl FromStr for MemoryRestoreMode { + type Err = MemoryRestoreModeParseError; + + fn from_str(s: &str) -> result::Result { + match s.to_lowercase().as_str() { + "copy" => Ok(Self::Copy), + "ondemand" => Ok(Self::OnDemand), + _ => Err(MemoryRestoreModeParseError::InvalidValue(s.to_owned())), + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] pub struct RestoreConfig { pub source_url: PathBuf, #[serde(default)] pub prefault: bool, #[serde(default)] + pub memory_restore_mode: MemoryRestoreMode, + #[serde(default)] pub net_fds: Option>, } impl RestoreConfig { pub const SYNTAX: &'static str = "Restore from a VM snapshot. \ - \nRestore parameters \"source_url=,prefault=on|off,\ + \nRestore parameters \"source_url=,prefault=on|off,memory_restore_mode=copy|ondemand,\ net_fds=\" \ \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \ - \n`prefault` brings memory pages in when enabled (disabled by default) \ + \n`prefault` controls eager prefaulting for the copy-based restore path (disabled by default) \ + \n`memory_restore_mode=copy` preserves the existing eager read-copy restore behavior, while `memory_restore_mode=ondemand` enables lazy demand paging and fails restore if userfaultfd support is unavailable \ \n`net_fds` is a list of net ids with new file descriptors. \ Only net devices backed by FDs directly are needed as input."; pub fn parse(restore: &str) -> Result { let mut parser = OptionParser::new(); - parser.add("source_url").add("prefault").add("net_fds"); + parser + .add("source_url") + .add("prefault") + .add("memory_restore_mode") + .add("net_fds"); parser.parse(restore).map_err(Error::ParseRestore)?; let source_url = parser @@ -2596,6 +2633,10 @@ impl RestoreConfig { .map_err(Error::ParseRestore)? .unwrap_or(Toggle(false)) .0; + let memory_restore_mode = parser + .convert::("memory_restore_mode") + .map_err(Error::ParseRestore)? + .unwrap_or_default(); let net_fds = parser .convert::>>("net_fds") .map_err(Error::ParseRestore)? @@ -2612,6 +2653,7 @@ impl RestoreConfig { Ok(RestoreConfig { source_url, prefault, + memory_restore_mode, net_fds, }) } @@ -2620,6 +2662,10 @@ impl RestoreConfig { // corresponding 'RestoreNetConfig' with a matched 'id' and expected // number of FDs. pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { + if self.memory_restore_mode == MemoryRestoreMode::OnDemand && self.prefault { + return Err(ValidationError::InvalidRestorePrefaultWithOnDemand); + } + let mut restored_net_with_fds = HashMap::new(); for n in self.net_fds.iter().flatten() { assert_eq!( @@ -4498,6 +4544,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" RestoreConfig { source_url: PathBuf::from("/path/to/snapshot"), prefault: false, + memory_restore_mode: MemoryRestoreMode::Copy, net_fds: None, } ); @@ -4508,6 +4555,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" RestoreConfig { source_url: PathBuf::from("/path/to/snapshot"), prefault: false, + memory_restore_mode: MemoryRestoreMode::Copy, net_fds: Some(vec![ RestoredNetConfig { id: "net0".to_string(), @@ -4522,11 +4570,39 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ]), } ); + assert_eq!( + RestoreConfig::parse("source_url=/path/to/snapshot,memory_restore_mode=ondemand")?, + RestoreConfig { + source_url: PathBuf::from("/path/to/snapshot"), + prefault: false, + memory_restore_mode: MemoryRestoreMode::OnDemand, + net_fds: None, + } + ); // Parsing should fail as source_url is a required field RestoreConfig::parse("prefault=off").unwrap_err(); + RestoreConfig::parse("source_url=/path/to/snapshot,memory_restore_mode=bogus").unwrap_err(); Ok(()) } + #[test] + fn test_restore_config_serde() { + assert_eq!( + serde_json::from_str::(r#"{"source_url":"/path/to/snapshot"}"#) + .unwrap() + .memory_restore_mode, + MemoryRestoreMode::Copy + ); + assert_eq!( + serde_json::from_str::( + r#"{"source_url":"/path/to/snapshot","memory_restore_mode":"OnDemand"}"# + ) + .unwrap() + .memory_restore_mode, + MemoryRestoreMode::OnDemand + ); + } + #[test] fn test_restore_config_validation() { // interested in only VmConfig.net, so set rest to default values @@ -4589,6 +4665,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" let valid_config = RestoreConfig { source_url: PathBuf::from("/path/to/snapshot"), prefault: false, + memory_restore_mode: MemoryRestoreMode::Copy, net_fds: Some(vec![ RestoredNetConfig { id: "net0".to_string(), @@ -4663,6 +4740,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" let another_valid_config = RestoreConfig { source_url: PathBuf::from("/path/to/snapshot"), prefault: false, + memory_restore_mode: MemoryRestoreMode::Copy, net_fds: None, }; snapshot_vm_config.net = Some(vec![NetConfig { @@ -4671,6 +4749,17 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..net_fixture() }]); another_valid_config.validate(&snapshot_vm_config).unwrap(); + + let invalid_restore_mode = RestoreConfig { + source_url: PathBuf::from("/path/to/snapshot"), + prefault: true, + memory_restore_mode: MemoryRestoreMode::OnDemand, + net_fds: None, + }; + assert_eq!( + invalid_restore_mode.validate(&snapshot_vm_config), + Err(ValidationError::InvalidRestorePrefaultWithOnDemand) + ); } fn platform_fixture() -> PlatformConfig { From c417924a29566945d3ec96eb99cc3dbddf4c6692 Mon Sep 17 00:00:00 2001 From: Shayon Mukherjee Date: Fri, 13 Mar 2026 08:16:33 -0700 Subject: [PATCH 0730/1893] 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 --- vm-migration/src/lib.rs | 35 +++ vmm/src/lib.rs | 31 ++- vmm/src/memory_manager.rs | 456 ++++++++++++++++++++++++++++++++++++- vmm/src/seccomp_filters.rs | 7 + vmm/src/vm.rs | 7 +- 5 files changed, 512 insertions(+), 24 deletions(-) diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 22da8df7f4..2283ff8bed 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -14,6 +14,38 @@ mod bitpos_iterator; mod context; pub mod protocol; +#[derive(Error, Debug)] +pub enum UffdError { + #[error("Snapshot ranges are not page-aligned")] + UnalignedRanges, + + #[error("Failed to create userfaultfd")] + Create(#[source] std::io::Error), + + #[error("Cannot translate GPA {gpa:#x} to host address")] + GpaTranslation { gpa: u64 }, + + #[error("Failed to register region at {addr:#x}+{len:#x}")] + Register { + addr: u64, + len: u64, + #[source] + source: std::io::Error, + }, + + #[error("Region at {addr:#x}+{len:#x} missing COPY/WAKE support")] + MissingIoctlSupport { addr: u64, len: u64 }, + + #[error("Failed to spawn handler thread")] + SpawnThread(#[source] std::io::Error), + + #[error("Handler terminated before startup completed")] + HandlerStartup, + + #[error("Handler failed after startup")] + HandlerFailed(#[source] std::io::Error), +} + #[derive(Error, Debug)] pub enum MigratableError { #[error("Failed to pause migratable component")] @@ -34,6 +66,9 @@ pub enum MigratableError { #[error("Failed to receive migratable component snapshot")] MigrateReceive(#[source] anyhow::Error), + #[error("On-demand restore failed")] + OnDemandRestore(#[source] UffdError), + #[error("Socket error")] MigrateSocket(#[source] std::io::Error), diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index f03927ce3e..0b82be49e6 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -51,7 +51,7 @@ use crate::api::{ ApiRequest, ApiResponse, RequestHandler, VmInfoResponse, VmReceiveMigrationData, VmSendMigrationData, VmmPingResponse, }; -use crate::config::{RestoreConfig, add_to_config}; +use crate::config::{MemoryRestoreMode, RestoreConfig, add_to_config}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::coredump::GuestDebuggable; use crate::landlock::Landlock; @@ -88,6 +88,8 @@ mod pci_segment; pub mod seccomp_filters; mod serial_manager; mod sigwinch_listener; +mod uffd; +mod userfaultfd; pub mod vm; pub mod vm_config; @@ -1506,6 +1508,7 @@ impl Vmm { source_url: &str, vm_config: Arc>, prefault: bool, + memory_restore_mode: MemoryRestoreMode, ) -> std::result::Result<(), VmError> { let snapshot = recv_vm_state(source_url).map_err(VmError::Restore)?; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] @@ -1548,6 +1551,7 @@ impl Vmm { Some(&snapshot), Some(source_url), Some(prefault), + Some(memory_restore_mode), )?; self.vm = Some(vm); @@ -1754,6 +1758,7 @@ impl RequestHandler for Vmm { None, None, None, + None, )?; self.vm = Some(vm); @@ -1838,17 +1843,22 @@ impl RequestHandler for Vmm { } } - self.vm_restore(source_url, vm_config, restore_cfg.prefault) - .map_err(|vm_restore_err| { - error!("VM Restore failed: {vm_restore_err:?}"); + self.vm_restore( + source_url, + vm_config, + restore_cfg.prefault, + restore_cfg.memory_restore_mode, + ) + .map_err(|vm_restore_err| { + error!("VM Restore failed: {vm_restore_err:?}"); - // Cleanup the VM being created while vm restore - if let Err(e) = self.vm_delete() { - return e; - } + // Cleanup the VM being created while vm restore + if let Err(e) = self.vm_delete() { + return e; + } - vm_restore_err - }) + vm_restore_err + }) } #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] @@ -1930,6 +1940,7 @@ impl RequestHandler for Vmm { None, None, None, + None, )?; // And we boot it diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 2cb735ecd1..ba0313b29a 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -7,13 +7,13 @@ use std::collections::BTreeMap; use std::collections::HashMap; use std::fs::{File, OpenOptions}; -use std::io::{self}; +use std::io::{self, Read as _, Seek, SeekFrom}; use std::ops::{BitAnd, Not, Sub}; -#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] -use std::os::fd::AsFd; +use std::os::fd::{AsFd, OwnedFd}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::mpsc::{self, Receiver, SyncSender}; use std::sync::{Arc, Barrier, Mutex}; use std::{ffi, result, thread}; @@ -44,15 +44,31 @@ use vm_memory::{ use vm_migration::protocol::{MemoryRange, MemoryRangeTable}; use vm_migration::{ Migratable, MigratableError, Pausable, Snapshot, SnapshotData, Snapshottable, Transportable, + UffdError, }; +use vmm_sys_util::eventfd::EventFd; +use crate::config::MemoryRestoreMode; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::coredump::{ CoredumpMemoryRegion, CoredumpMemoryRegions, DumpState, GuestDebuggableError, }; use crate::migration::url_to_path; use crate::vm_config::{HotplugMethod, MemoryConfig, MemoryZoneConfig}; -use crate::{GuestMemoryMmap, GuestRegionMmap, MEMORY_MANAGER_SNAPSHOT_ID}; +use crate::{GuestMemoryMmap, GuestRegionMmap, MEMORY_MANAGER_SNAPSHOT_ID, uffd}; + +struct UffdHandler { + stop_event: EventFd, + result_rx: Receiver>, + handle: thread::JoinHandle<()>, +} + +struct UffdRange { + host_addr: u64, + length: u64, + file_offset: u64, + page_size: u64, +} pub const MEMORY_MANAGER_ACPI_SIZE: usize = 0x18; @@ -116,13 +132,25 @@ impl VirtioMemZone { } } -#[derive(Default)] pub struct MemoryZone { regions: Vec>, virtio_mem_zone: Option, + shared: bool, + hugepages: bool, + backing_page_size: u64, } impl MemoryZone { + fn new(shared: bool, hugepages: bool, backing_page_size: u64) -> Self { + Self { + regions: Vec::new(), + virtio_mem_zone: None, + shared, + hugepages, + backing_page_size, + } + } + pub fn regions(&self) -> &Vec> { &self.regions } @@ -132,6 +160,21 @@ impl MemoryZone { pub fn virtio_mem_zone_mut(&mut self) -> Option<&mut VirtioMemZone> { self.virtio_mem_zone.as_mut() } + + fn backing_page_size_for_gpa(&self, gpa: u64) -> Option { + if self.regions.iter().any(|region| { + let start = region.start_addr().raw_value(); + gpa >= start && gpa < start + region.len() + }) { + return Some(self.backing_page_size); + } + + self.virtio_mem_zone.as_ref().and_then(|virtio_mem_zone| { + let start = virtio_mem_zone.region.start_addr().raw_value(); + (gpa >= start && gpa < start + virtio_mem_zone.region.len()) + .then_some(self.backing_page_size) + }) + } } pub type MemoryZones = HashMap; @@ -187,6 +230,7 @@ pub struct MemoryManager { // This is useful for getting the dirty pages as we need to know the // slots that the mapping is created in. guest_ram_mappings: Vec, + uffd_handler: Option, pub acpi_address: Option, #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] @@ -350,6 +394,12 @@ pub enum Error { MisalignedMemorySize, } +impl From for Error { + fn from(e: UffdError) -> Self { + Error::Restore(MigratableError::OnDemandRestore(e)) + } +} + const ENABLE_FLAG: usize = 0; const INSERTING_FLAG: usize = 1; const REMOVING_FLAG: usize = 2; @@ -551,7 +601,10 @@ impl MemoryManager { } // Add zone id to the list of memory zones. - memory_zones.insert(zone.id.clone(), MemoryZone::default()); + memory_zones.insert( + zone.id.clone(), + MemoryZone::new(zone.shared, zone.hugepages, zone_align_size), + ); for ram_region in ram_regions.iter() { let mut ram_region_offset = 0; @@ -642,7 +695,10 @@ impl MemoryManager { ); return Err(Error::DuplicateZoneId); } - memory_zones.insert(zone.id.clone(), MemoryZone::default()); + memory_zones.insert( + zone.id.clone(), + MemoryZone::new(zone.shared, zone.hugepages, zone_align_size), + ); } if ram_region_consumed { @@ -670,7 +726,11 @@ impl MemoryManager { let mut memory_zones = HashMap::new(); for zone_config in zones_config { - memory_zones.insert(zone_config.id.clone(), MemoryZone::default()); + let zone_page_size = memory_zone_get_align_size(zone_config)?; + memory_zones.insert( + zone_config.id.clone(), + MemoryZone::new(zone_config.shared, zone_config.hugepages, zone_page_size), + ); } for guest_ram_mapping in guest_ram_mappings { @@ -760,6 +820,360 @@ impl MemoryManager { Ok(()) } + /// Restore guest memory using userfaultfd for lazy demand paging. + /// + /// Instead of reading the entire snapshot into guest RAM upfront (which + /// blocks restore for hundreds of milliseconds at multi-GB sizes), this + /// registers the guest memory regions with a userfaultfd. A background + /// thread handles page faults by reading the corresponding page from the + /// snapshot file and copying it into guest memory via `UFFDIO_COPY`. + /// + /// This preserves the original memory mapping type (anonymous or shared), + /// making it compatible with VFIO device passthrough and shared-memory + /// guest RAM. + /// + /// Fails the restore if UFFD setup cannot be completed successfully. + /// + /// The handler thread keeps the snapshot file open while lazy restore + /// is active. The file must remain available until the VM is shut down or + /// all faulted pages have been served. + fn restore_by_uffd( + &mut self, + file_path: &Path, + saved_regions: &MemoryRangeTable, + exit_evt: &EventFd, + ) -> Result<(), Error> { + if saved_regions.is_empty() { + return Ok(()); + } + + let guest_memory = self.guest_memory.memory(); + let required_uffd_features = self.required_uffd_features(); + + // SAFETY: FFI call. Trivially safe. + let base_page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64; + + info!( + "UFFD restore: attempting demand-paged restore for {} region(s)", + saved_regions.regions().len() + ); + + if saved_regions + .regions() + .iter() + .any(|range| range.gpa % base_page_size != 0 || range.length % base_page_size != 0) + { + return Err(UffdError::UnalignedRanges.into()); + } + + let snapshot_file = File::open(file_path).map_err(Error::SnapshotOpen)?; + + let uffd_fd = uffd::create(required_uffd_features).map_err(UffdError::Create)?; + + let mut handler_ranges: Vec = Vec::new(); + let mut file_offset: u64 = 0; + + for range in saved_regions.regions() { + let host_addr = guest_memory + .get_host_address(GuestAddress(range.gpa)) + .map_err(|_| UffdError::GpaTranslation { gpa: range.gpa })? + as u64; + + let ioctls = uffd::register(uffd_fd.as_fd(), host_addr, range.length).map_err(|e| { + UffdError::Register { + addr: host_addr, + len: range.length, + source: e, + } + })?; + + if ioctls & crate::userfaultfd::UFFD_API_RANGE_IOCTLS_BASIC + != crate::userfaultfd::UFFD_API_RANGE_IOCTLS_BASIC + { + return Err(UffdError::MissingIoctlSupport { + addr: host_addr, + len: range.length, + } + .into()); + } + + let range_page_size = self + .memory_zones + .values() + .find_map(|zone| zone.backing_page_size_for_gpa(range.gpa)) + .unwrap_or(base_page_size); + + handler_ranges.push(UffdRange { + host_addr, + length: range.length, + file_offset, + page_size: range_page_size, + }); + + file_offset += range.length; + } + + info!( + "UFFD restore: registered {} region(s), {} total bytes, spawning handler", + handler_ranges.len(), + file_offset + ); + + let stop_event = EventFd::new(libc::EFD_NONBLOCK).map_err(Error::EventFdFail)?; + let thread_stop_event = stop_event.try_clone().map_err(Error::EventFdFail)?; + let thread_exit_evt = exit_evt.try_clone().map_err(Error::EventFdFail)?; + let (ready_tx, ready_rx) = mpsc::sync_channel(1); + let (result_tx, result_rx) = mpsc::sync_channel(1); + let handle = thread::Builder::new() + .name("uffd-handler".to_string()) + .spawn(move || { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || { + let max_page_size = handler_ranges + .iter() + .map(|r| r.page_size) + .max() + .unwrap_or(base_page_size); + let result = Self::uffd_handler_loop( + uffd_fd, + thread_stop_event, + snapshot_file, + &handler_ranges, + max_page_size, + &ready_tx, + ); + + if let Err(e) = &result { + error!("UFFD handler exited with error: {e}"); + } + + result_tx.send(result).ok(); + })) + .map_err(|_| { + error!("uffd-handler thread panicked"); + thread_exit_evt.write(1).ok(); + }) + .ok(); + }) + .map_err(UffdError::SpawnThread)?; + + if ready_rx.recv().is_err() { + handle.join().ok(); + return Err(UffdError::HandlerStartup.into()); + } + + if let Ok(Err(e)) = result_rx.try_recv() { + handle.join().ok(); + return Err(UffdError::HandlerFailed(e).into()); + } + + self.uffd_handler = Some(UffdHandler { + stop_event, + result_rx, + handle, + }); + + info!("UFFD restore: demand-paged restore enabled"); + + Ok(()) + } + + fn required_uffd_features(&self) -> u64 { + let mut features = 0u64; + if self.memory_zones.values().any(|z| z.shared && !z.hugepages) { + features |= crate::userfaultfd::UFFD_FEATURE_MISSING_SHMEM; + } + if self.memory_zones.values().any(|z| z.hugepages) { + features |= crate::userfaultfd::UFFD_FEATURE_MISSING_HUGETLBFS; + } + features + } + + fn stop_uffd_handler(&mut self) { + if let Some(uffd_handler) = self.uffd_handler.take() { + uffd_handler.stop_event.write(1).ok(); + uffd_handler.handle.join().ok(); + + match uffd_handler.result_rx.try_recv() { + Ok(Err(e)) => error!("UFFD handler terminated with error: {e}"), + Err(mpsc::TryRecvError::Disconnected) => { + warn!("UFFD handler terminated unexpectedly (possible panic)"); + } + _ => {} + } + } + } + + /// Poll the UFFD fd and serve page faults from the snapshot file. + /// + /// Runs until the fd is closed (EPOLLHUP) or an unrecoverable error occurs. + /// Each fault triggers a seek + read from the snapshot file followed by a + /// `UFFDIO_COPY` to resolve the fault and wake the faulting thread. + #[allow(clippy::needless_pass_by_value)] + fn uffd_handler_loop( + uffd_fd: OwnedFd, + stop_event: EventFd, + mut snapshot_file: File, + ranges: &[UffdRange], + page_size: u64, + ready_tx: &SyncSender<()>, + ) -> Result<(), io::Error> { + let uffd_raw_fd = uffd_fd.as_raw_fd(); + let mut page_buf = vec![0u8; page_size as usize]; + + let total_pages: u64 = ranges.iter().map(|r| r.length.div_ceil(r.page_size)).sum(); + let mut pages_served: u64 = 0; + + const EVENT_STOP: u64 = 0; + const EVENT_UFFD: u64 = 1; + + let epoll_fd = epoll::create(true).map_err(io::Error::other)?; + // SAFETY: epoll_fd is valid and owned by this scope. + let _epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; + + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + stop_event.as_raw_fd(), + epoll::Event::new(epoll::Events::EPOLLIN, EVENT_STOP), + ) + .map_err(io::Error::other)?; + + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + uffd_raw_fd, + epoll::Event::new(epoll::Events::EPOLLIN | epoll::Events::EPOLLHUP, EVENT_UFFD), + ) + .map_err(io::Error::other)?; + + ready_tx.send(()).ok(); + + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); 2]; + loop { + let num_events = match epoll::wait(epoll_fd, -1, &mut events) { + Ok(n) => n, + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + }; + + let mut got_uffd_data = false; + for event in events.iter().take(num_events) { + let token = event.data; + let evt_flags = event.events; + + if token == EVENT_STOP { + stop_event.read().ok(); + info!("UFFD handler: received stop event, exiting"); + return Ok(()); + } + + if token == EVENT_UFFD + && (evt_flags & epoll::Events::EPOLLHUP.bits()) != 0 + && (evt_flags & epoll::Events::EPOLLIN.bits()) == 0 + { + info!("UFFD handler: fd closed (EPOLLHUP), exiting"); + return Ok(()); + } + + if token == EVENT_UFFD && (evt_flags & epoll::Events::EPOLLIN.bits()) != 0 { + got_uffd_data = true; + } + } + + if !got_uffd_data { + continue; + } + + // SAFETY: UffdMsg is a plain repr(C) struct, safe to zero-init. + let mut msg: uffd::UffdMsg = unsafe { std::mem::zeroed() }; + // SAFETY: reading a uffd_msg-sized struct from the valid uffd fd. + let n = unsafe { + libc::read( + uffd_raw_fd, + &mut msg as *mut uffd::UffdMsg as *mut libc::c_void, + std::mem::size_of::(), + ) + }; + if n < 0 { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::WouldBlock { + continue; + } + return Err(err); + } + if n == 0 { + info!("UFFD handler: EOF on fd, exiting"); + return Ok(()); + } + if n as usize != std::mem::size_of::() { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "Short read from userfaultfd", + )); + } + + if msg.event != crate::userfaultfd::UFFD_EVENT_PAGEFAULT { + continue; + } + + let fault_addr = msg.pf_address; + + let mut served = false; + for range in ranges { + // Round down to the page boundary containing the faulted address. + let page_addr = fault_addr & !(range.page_size - 1); + if page_addr >= range.host_addr && page_addr < range.host_addr + range.length { + let offset_in_range = page_addr - range.host_addr; + let file_pos = range.file_offset + offset_in_range; + + snapshot_file.seek(SeekFrom::Start(file_pos))?; + snapshot_file.read_exact(&mut page_buf[..range.page_size as usize])?; + + loop { + match uffd::copy( + uffd_fd.as_fd(), + page_addr, + page_buf.as_ptr(), + range.page_size, + ) { + Ok(()) => { + pages_served += 1; + break; + } + Err(e) if e.raw_os_error() == Some(libc::EEXIST) => { + if let Err(e) = + uffd::wake(uffd_fd.as_fd(), page_addr, range.page_size) + { + warn!("UFFDIO_WAKE failed at {page_addr:#x}: {e}"); + } + break; + } + Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => { + // The kernel can report a transient EAGAIN while the fault + // is being resolved; yield and retry instead of aborting restore. + thread::yield_now(); + } + Err(e) => return Err(e), + } + } + served = true; + break; + } + } + + if !served { + return Err(io::Error::other(format!( + "UFFD handler: fault at {fault_addr:#x} does not belong to any registered range", + ))); + } + + if pages_served == total_pages { + info!("UFFD handler: all {pages_served} pages served, exiting"); + return Ok(()); + } + } + } + fn validate_memory_config( config: &MemoryConfig, user_provided_zones: bool, @@ -1227,6 +1641,7 @@ impl MemoryManager { snapshot_memory_ranges: MemoryRangeTable::default(), memory_zones, guest_ram_mappings: Vec::new(), + uffd_handler: None, acpi_address, log_dirty: dynamic, // Cannot log dirty pages on a TD arch_mem_regions, @@ -1240,13 +1655,16 @@ impl MemoryManager { Ok(Arc::new(Mutex::new(memory_manager))) } + #[allow(clippy::too_many_arguments)] pub fn new_from_snapshot( snapshot: &Snapshot, vm: Arc, config: &MemoryConfig, source_url: Option<&str>, prefault: bool, + memory_restore_mode: MemoryRestoreMode, phys_bits: u8, + exit_evt: &EventFd, ) -> Result>, Error> { if let Some(source_url) = source_url { let mut memory_file_path = url_to_path(source_url).map_err(Error::Restore)?; @@ -1266,9 +1684,17 @@ impl MemoryManager { Default::default(), )?; - mm.lock() - .unwrap() - .fill_saved_regions(memory_file_path, &mem_snapshot.memory_ranges)?; + if memory_restore_mode == MemoryRestoreMode::OnDemand { + mm.lock().unwrap().restore_by_uffd( + &memory_file_path, + &mem_snapshot.memory_ranges, + exit_evt, + )?; + } else { + mm.lock() + .unwrap() + .fill_saved_regions(memory_file_path, &mem_snapshot.memory_ranges)?; + } Ok(mm) } else { @@ -2530,6 +2956,12 @@ impl Aml for MemoryManager { impl Pausable for MemoryManager {} +impl Drop for MemoryManager { + fn drop(&mut self) { + self.stop_uffd_handler(); + } +} + #[derive(Clone, Serialize, Deserialize)] pub struct MemoryManagerSnapshotData { memory_ranges: MemoryRangeTable, diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index bb40b99f60..938e7832f3 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -25,6 +25,8 @@ use vhost::vhost_kern::vhost_binding::{ VHOST_VDPA_SET_STATUS, VHOST_VDPA_SET_VRING_ENABLE, VHOST_VDPA_SUSPEND, }; +use crate::userfaultfd::{UFFDIO_API, UFFDIO_COPY, UFFDIO_REGISTER, UFFDIO_WAKE}; + #[derive(Copy, Clone)] pub enum Thread { HttpApi, @@ -362,6 +364,10 @@ fn create_vmm_ioctl_seccomp_rule_common( VHOST_VDPA_GET_CONFIG_SIZE() )?], and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SUSPEND())?], + and![Cond::new(1, ArgLen::Dword, Eq, UFFDIO_API)?], + and![Cond::new(1, ArgLen::Dword, Eq, UFFDIO_COPY)?], + and![Cond::new(1, ArgLen::Dword, Eq, UFFDIO_REGISTER)?], + and![Cond::new(1, ArgLen::Dword, Eq, UFFDIO_WAKE)?], ]; let hypervisor_rules = create_vmm_ioctl_seccomp_rule_hypervisor(hypervisor_type)?; @@ -691,6 +697,7 @@ fn vmm_thread_rules( (libc::SYS_unlink, vec![]), #[cfg(target_arch = "aarch64")] (libc::SYS_unlinkat, vec![]), + (libc::SYS_userfaultfd, vec![]), (libc::SYS_wait4, vec![]), (libc::SYS_write, vec![]), (libc::SYS_writev, vec![]), diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index a094803ee4..710847fab3 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -76,7 +76,7 @@ use vm_migration::{ use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::sock_ctrl_msg::ScmSocket; -use crate::config::{ValidationError, add_to_config}; +use crate::config::{MemoryRestoreMode, ValidationError, add_to_config}; use crate::console_devices::{ConsoleDeviceError, ConsoleInfo}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::coredump::{ @@ -1265,6 +1265,7 @@ impl Vm { snapshot: Option<&Snapshot>, source_url: Option<&str>, prefault: Option, + memory_restore_mode: Option, ) -> Result { trace_scoped!("Vm::new"); @@ -1300,8 +1301,10 @@ impl Vm { vm.clone(), &vm_config.lock().unwrap().memory.clone(), source_url, - prefault.unwrap(), + prefault.unwrap_or(false), + memory_restore_mode.unwrap_or_default(), phys_bits, + &exit_evt, ) .map_err(Error::MemoryManager)? } else { From ec389c4faed4281687698dc5311308cd91385d46 Mon Sep 17 00:00:00 2001 From: Shayon Mukherjee Date: Fri, 13 Mar 2026 08:16:33 -0700 Subject: [PATCH 0731/1893] 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 --- cloud-hypervisor/tests/integration.rs | 168 ++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index bbc1999335..f0d5f6f252 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -11025,6 +11025,174 @@ mod common_sequential { handle_child_output(r, &output); } + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_uffd() { + _test_snapshot_restore_uffd("size=2G", &[], 1_920_000); + } + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_uffd_shared_memory() { + _test_snapshot_restore_uffd("size=512M,shared=on", &[], 480_000); + } + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_uffd_hugepage_zone() { + if !exec_host_command_status( + "grep -q '^Hugepagesize:[[:space:]]*2048 kB' /proc/meminfo && test $(awk '/HugePages_Free/ {print $2}' /proc/meminfo) -ge 256", + ) + .success() + { + println!("SKIPPED: not enough free 2MiB hugepages for UFFD restore test"); + return; + } + + _test_snapshot_restore_uffd( + "size=0", + &["id=mem0,size=512M,hugepages=on,hugepage_size=2M"], + 480_000, + ); + } + + fn _test_snapshot_restore_uffd( + memory_config: &str, + memory_zone_config: &[&str], + min_total_memory_kib: u32, + ) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); + + let console_text = String::from("On a branch floating down river a cricket, singing."); + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); + let socket = temp_vsock_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut source_cmd = GuestCommand::new(&guest); + source_cmd + .args(["--api-socket", &api_socket_source]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", "boot=4"]) + .args(["--memory", memory_config]); + + if !memory_zone_config.is_empty() { + source_cmd.args(["--memory-zone"]).args(memory_zone_config); + } + + let mut child = source_cmd + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); + assert!(guest.get_total_memory().unwrap_or_default() > min_total_memory_kib); + + guest.check_devices_common(Some(&socket), Some(&console_text), None); + + snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); + handle_child_output(r, &output); + + Command::new("rm") + .arg("-f") + .arg(socket.as_str()) + .output() + .unwrap(); + + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_restored]) + .args([ + "--event-monitor", + format!("path={event_path_restored}").as_str(), + ]) + .args([ + "--restore", + format!("source_url=file://{snapshot_dir},memory_restore_mode=ondemand").as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + thread::sleep(std::time::Duration::new(20, 0)); + + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(check_latest_events_exact( + &latest_events, + &event_path_restored + )); + + let r = std::panic::catch_unwind(|| { + assert!(remote_command(&api_socket_restored, "resume", None)); + thread::sleep(std::time::Duration::new(1, 0)); + let latest_events = [ + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(check_latest_events_exact( + &latest_events, + &event_path_restored + )); + + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); + assert!(guest.get_total_memory().unwrap_or_default() > min_total_memory_kib); + + guest.check_devices_common(Some(&socket), Some(&console_text), None); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + + let logs = format!( + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert!( + logs.contains("UFFD restore: demand-paged restore enabled"), + "Expected UFFD restore path to be enabled. output: {logs}" + ); + }); + handle_child_output(r, &output); + + let _ = remove_dir_all(snapshot_dir.as_str()); + } + #[test] #[cfg(not(feature = "mshv"))] // See issue #7437 #[ignore = "See #6970"] From ab8169c855553ee7609ac29f37d4349a590ec6ad Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 13 Mar 2026 13:11:44 -0700 Subject: [PATCH 0732/1893] build: Bump timeout on integration tests We now have more tests and are hitting up against the timeout Signed-off-by: Rob Bradford --- .github/workflows/integration-x86-64.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-x86-64.yaml b/.github/workflows/integration-x86-64.yaml index 9334b242ea..b620954d04 100644 --- a/.github/workflows/integration-x86-64.yaml +++ b/.github/workflows/integration-x86-64.yaml @@ -6,7 +6,7 @@ concurrency: jobs: build: - timeout-minutes: 60 + timeout-minutes: 80 strategy: fail-fast: false matrix: @@ -41,7 +41,7 @@ jobs: run: sudo modprobe openvswitch - name: Run integration tests if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }} - timeout-minutes: 40 + timeout-minutes: 60 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') }} From f77c6ef78bdca486eec0e6d81033af7082f9997d Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:50:37 -0700 Subject: [PATCH 0733/1893] virtio-devices: introduce ActivationContext for device activation Signed-off-by: Peter Oskolkov --- fuzz/fuzz_targets/balloon.rs | 10 +++--- fuzz/fuzz_targets/block.rs | 10 +++--- fuzz/fuzz_targets/console.rs | 10 +++--- fuzz/fuzz_targets/iommu.rs | 10 +++--- fuzz/fuzz_targets/mem.rs | 10 +++--- fuzz/fuzz_targets/net.rs | 10 +++--- fuzz/fuzz_targets/pmem.rs | 10 +++--- fuzz/fuzz_targets/rng.rs | 10 +++--- fuzz/fuzz_targets/vsock.rs | 10 +++--- fuzz/fuzz_targets/watchdog.rs | 10 +++--- virtio-devices/src/balloon.rs | 13 ++++---- virtio-devices/src/block.rs | 12 +++---- virtio-devices/src/console.rs | 13 ++++---- virtio-devices/src/device.rs | 13 ++++---- virtio-devices/src/iommu.rs | 13 ++++---- virtio-devices/src/lib.rs | 4 +-- virtio-devices/src/mem.rs | 13 ++++---- virtio-devices/src/net.rs | 12 +++---- virtio-devices/src/pmem.rs | 13 ++++---- virtio-devices/src/rng.rs | 13 ++++---- .../src/transport/pci_common_config.rs | 12 ++----- virtio-devices/src/transport/pci_device.rs | 13 ++++---- virtio-devices/src/vdpa.rs | 13 ++++---- virtio-devices/src/vhost_user/blk.rs | 14 ++++----- virtio-devices/src/vhost_user/fs.rs | 14 ++++----- .../src/vhost_user/generic_vhost_user.rs | 14 ++++----- virtio-devices/src/vhost_user/net.rs | 15 ++++----- virtio-devices/src/vsock/device.rs | 31 ++++++++++--------- virtio-devices/src/watchdog.rs | 13 ++++---- vmm/src/device_manager.rs | 2 +- 30 files changed, 183 insertions(+), 177 deletions(-) diff --git a/fuzz/fuzz_targets/balloon.rs b/fuzz/fuzz_targets/balloon.rs index b745cf6187..edb4f0cf1a 100644 --- a/fuzz/fuzz_targets/balloon.rs +++ b/fuzz/fuzz_targets/balloon.rs @@ -95,15 +95,15 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { reporting_queue_evt.write(1).unwrap(); balloon - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![ + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![ (0, inflate_q, inflate_evt), (1, deflate_q, deflate_evt), (2, reporting_q, reporting_evt), ], - ) + }) .ok(); // Wait for the events to finish and balloon device worker thread to return diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index 0ad9193fdb..19b875f044 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -91,11 +91,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { queue_evt.write(1).unwrap(); block - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and block device worker thread to return diff --git a/fuzz/fuzz_targets/console.rs b/fuzz/fuzz_targets/console.rs index 4b3a49df91..7f5fafaebc 100644 --- a/fuzz/fuzz_targets/console.rs +++ b/fuzz/fuzz_targets/console.rs @@ -128,11 +128,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { pipe_tx.write_all(console_input_bytes).unwrap(); // To use fuzzed data; console - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], + }) .unwrap(); // Wait for the events to finish and console device worker thread to return diff --git a/fuzz/fuzz_targets/iommu.rs b/fuzz/fuzz_targets/iommu.rs index 8c9f26b262..791ab6b000 100644 --- a/fuzz/fuzz_targets/iommu.rs +++ b/fuzz/fuzz_targets/iommu.rs @@ -107,14 +107,14 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { request_queue_evt.write(1).unwrap(); iommu - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![ + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![ (0, request_queue, request_evt), (0, _event_queue, _event_evt), ], - ) + }) .ok(); // Wait for the events to finish and vIOMMU device worker thread to return diff --git a/fuzz/fuzz_targets/mem.rs b/fuzz/fuzz_targets/mem.rs index 57fc9a91dd..46627e9315 100644 --- a/fuzz/fuzz_targets/mem.rs +++ b/fuzz/fuzz_targets/mem.rs @@ -105,11 +105,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { queue_evt.write(1).unwrap(); virtio_mem - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and virtio-mem device worker thread to return diff --git a/fuzz/fuzz_targets/net.rs b/fuzz/fuzz_targets/net.rs index 30968d2a47..0af835ac06 100644 --- a/fuzz/fuzz_targets/net.rs +++ b/fuzz/fuzz_targets/net.rs @@ -143,11 +143,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { input_queue_evt.write(1).unwrap(); output_queue_evt.write(1).unwrap(); - net.activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], - ) + net.activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], + }) .unwrap(); // Wait for the events to finish and net device worker thread to return diff --git a/fuzz/fuzz_targets/pmem.rs b/fuzz/fuzz_targets/pmem.rs index a8fcb7a774..b42c20daea 100644 --- a/fuzz/fuzz_targets/pmem.rs +++ b/fuzz/fuzz_targets/pmem.rs @@ -61,11 +61,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { // Kick the 'queue' event before activate the pmem device queue_evt.write(1).unwrap(); - pmem.activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + pmem.activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and pmem device worker thread to return diff --git a/fuzz/fuzz_targets/rng.rs b/fuzz/fuzz_targets/rng.rs index 8d5ffe35b3..d9cd11f099 100644 --- a/fuzz/fuzz_targets/rng.rs +++ b/fuzz/fuzz_targets/rng.rs @@ -99,11 +99,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { // Kick the 'queue' event before activate the rng device queue_evt.write(1).unwrap(); - rng.activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + rng.activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and rng device worker thread to return diff --git a/fuzz/fuzz_targets/vsock.rs b/fuzz/fuzz_targets/vsock.rs index 144b8b4057..72bdeb4d63 100644 --- a/fuzz/fuzz_targets/vsock.rs +++ b/fuzz/fuzz_targets/vsock.rs @@ -108,11 +108,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { .unwrap(); vsock - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and vsock device worker thread to return diff --git a/fuzz/fuzz_targets/watchdog.rs b/fuzz/fuzz_targets/watchdog.rs index f203a228f9..8736f8af3f 100644 --- a/fuzz/fuzz_targets/watchdog.rs +++ b/fuzz/fuzz_targets/watchdog.rs @@ -64,11 +64,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { queue_evt.write(1).unwrap(); watchdog - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and watchdog device worker thread to return diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index 3db6832617..bb9c46cbc8 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -590,12 +590,13 @@ impl VirtioDevice for Balloon { } } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 22b8ef31e6..82eca61827 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -1008,12 +1008,12 @@ impl VirtioDevice for Block { self.update_writeback(); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + } = context; // See if the guest didn't ack the device being read-only. // If so, warn and pretend it did. let original_acked_features = self.common.acked_features; diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index ab907db20f..74b42c46b5 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -710,12 +710,13 @@ impl VirtioDevice for Console { self.read_config_from_slice(self.config.lock().unwrap().as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; self.resizer .acked_features diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index f0ed28f517..d5e873bf42 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -53,6 +53,12 @@ pub struct VirtioSharedMemoryList { pub region_list: Vec, } +pub struct ActivationContext { + pub mem: GuestMemoryAtomic, + pub interrupt_cb: Arc, + pub queues: Vec<(usize, Queue, EventFd)>, +} + /// Trait for virtio devices to be driven by a virtio transport. /// /// The lifecycle of a virtio device is to be moved to a virtio transport, which will then query the @@ -94,12 +100,7 @@ pub trait VirtioDevice: Send { } /// Activates this device for real usage. - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_evt: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult; + fn activate(&mut self, context: ActivationContext) -> ActivateResult; /// Optionally deactivates this device and returns ownership of the guest memory map, interrupt /// event, and queue events. diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index 1097b6582e..513d510b56 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -1075,12 +1075,13 @@ impl VirtioDevice for Iommu { self.update_bypass(); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index 111c9007e1..14e565ccb8 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -42,8 +42,8 @@ pub use self::balloon::Balloon; pub use self::block::{Block, BlockState}; pub use self::console::{Console, ConsoleResizer, Endpoint}; pub use self::device::{ - DmaRemapping, VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioInterruptType, - VirtioSharedMemoryList, + ActivationContext, DmaRemapping, VirtioCommon, VirtioDevice, VirtioInterrupt, + VirtioInterruptType, VirtioSharedMemoryList, }; pub use self::epoll_helper::{ EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 936fdbe42a..aed8ed48d2 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -950,12 +950,13 @@ impl VirtioDevice for Mem { self.read_config_from_slice(self.config.lock().unwrap().as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 63b9de4116..2e3aa02eaf 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -693,12 +693,12 @@ impl VirtioDevice for Net { self.read_config_from_slice(self.config.as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let num_queues = queues.len(); diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index b7be248fa4..fd995747c2 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -377,12 +377,13 @@ impl VirtioDevice for Pmem { self.read_config_from_slice(self.config.as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); if let Some(disk) = self.disk.as_ref() { diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 2409e272c1..16a539f923 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -244,12 +244,13 @@ impl VirtioDevice for Rng { self.common.ack_features(value); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 5a7b5f57a4..68ed01c60a 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -404,11 +404,8 @@ impl Snapshottable for VirtioPciCommonConfig { #[cfg(test)] mod unit_tests { - use vm_memory::GuestMemoryAtomic; - use vmm_sys_util::eventfd::EventFd; - use super::*; - use crate::{ActivateResult, GuestMemoryMmap, VirtioInterrupt}; + use crate::{ActivateResult, ActivationContext}; struct DummyDevice(u32); const QUEUE_SIZE: u16 = 256; @@ -421,12 +418,7 @@ mod unit_tests { fn queue_max_sizes(&self) -> &[u16] { QUEUE_SIZES } - fn activate( - &mut self, - _mem: GuestMemoryAtomic, - _interrupt_evt: Arc, - _queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, _context: ActivationContext) -> ActivateResult { Ok(()) } diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index bf1d169c83..26440ddf2e 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -309,12 +309,13 @@ pub struct VirtioPciDeviceActivator { } impl VirtioPciDeviceActivator { - pub fn activate(&mut self) -> ActivateResult { - self.device.lock().unwrap().activate( - self.memory.take().unwrap(), - self.interrupt.take().unwrap(), - self.queues.take().unwrap(), - )?; + pub fn activate(mut self) -> ActivateResult { + let mut locked_device = self.device.lock().unwrap(); + locked_device.activate(crate::device::ActivationContext { + mem: self.memory.take().unwrap(), + interrupt_cb: self.interrupt.take().unwrap(), + queues: self.queues.take().unwrap(), + })?; self.device_activated.store(true, Ordering::SeqCst); if let Some(barrier) = self.barrier.take() { diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 9d20aac92e..4773ef55fa 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -428,12 +428,13 @@ impl VirtioDevice for Vdpa { } } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - virtio_interrupt: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb: virtio_interrupt, + queues, + .. + } = context; self.activate_vdpa(&mem.memory(), virtio_interrupt.as_ref(), &queues) .map_err(ActivateError::ActivateVdpa)?; diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 22896ba7c6..9125e79909 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -19,7 +19,6 @@ use virtio_bindings::virtio_blk::{ VIRTIO_BLK_F_GEOMETRY, VIRTIO_BLK_F_MQ, VIRTIO_BLK_F_RO, VIRTIO_BLK_F_SEG_MAX, VIRTIO_BLK_F_SIZE_MAX, VIRTIO_BLK_F_TOPOLOGY, VIRTIO_BLK_F_WRITE_ZEROES, }; -use virtio_queue::Queue; use vm_memory::{ByteValued, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; @@ -279,12 +278,13 @@ impl VirtioDevice for Blk { } } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 434454fcef..fb21105c8a 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -12,7 +12,6 @@ use serde::{Deserialize, Serialize}; use serde_with::{Bytes, serde_as}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; -use virtio_queue::Queue; use vm_device::UserspaceMapping; use vm_memory::{ByteValued, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; @@ -261,12 +260,13 @@ impl VirtioDevice for Fs { self.read_config_from_slice(self.config.as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index d38eee3a92..b8af44d75d 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -14,7 +14,6 @@ use vhost::vhost_user::message::{ VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures, }; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; -use virtio_queue::Queue; use vm_device::UserspaceMapping; use vm_memory::GuestMemoryAtomic; use vm_migration::protocol::MemoryRangeTable; @@ -277,12 +276,13 @@ impl VirtioDevice for GenericVhostUser { } } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 187d710e39..165115ae7e 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -19,7 +19,7 @@ use virtio_bindings::virtio_net::{ VIRTIO_NET_F_MAC, VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_MTU, }; use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; -use virtio_queue::{Queue, QueueT}; +use virtio_queue::QueueT; use vm_memory::{ByteValued, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; @@ -288,12 +288,13 @@ impl VirtioDevice for Net { self.read_config_from_slice(self.config.as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index aa86aa0c95..5b215c3597 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -435,12 +435,13 @@ where } } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); @@ -593,9 +594,11 @@ mod unit_tests { let memory = GuestMemoryAtomic::new(ctx.mem.clone()); // Test a bad activation. - let bad_activate = - ctx.device - .activate(memory.clone(), Arc::new(NoopVirtioInterrupt {}), Vec::new()); + let bad_activate = ctx.device.activate(crate::device::ActivationContext { + mem: memory.clone(), + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: Vec::new(), + }); match bad_activate { Err(ActivateError::BadActivate) => (), other => panic!("{other:?}"), @@ -603,10 +606,10 @@ mod unit_tests { // Test a correct activation. ctx.device - .activate( - memory, - Arc::new(NoopVirtioInterrupt {}), - vec![ + .activate(crate::device::ActivationContext { + mem: memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![ ( 0, Queue::new(256).unwrap(), @@ -623,7 +626,7 @@ mod unit_tests { EventFd::new(EFD_NONBLOCK).unwrap(), ), ], - ) + }) .unwrap(); } diff --git a/virtio-devices/src/watchdog.rs b/virtio-devices/src/watchdog.rs index 6b9f7cc0ac..742a2e0241 100644 --- a/virtio-devices/src/watchdog.rs +++ b/virtio-devices/src/watchdog.rs @@ -326,12 +326,13 @@ impl VirtioDevice for Watchdog { self.common.ack_features(value); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 04b9e3b44b..e560b02d7a 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4543,7 +4543,7 @@ impl DeviceManager { } pub fn activate_virtio_devices(&self) -> DeviceManagerResult<()> { - for mut activator in self.pending_activations.lock().unwrap().drain(..) { + for activator in self.pending_activations.lock().unwrap().drain(..) { activator .activate() .map_err(DeviceManagerError::VirtioActivate)?; From 21bd3ae91661104a7c58662316b4538eac09fbc7 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:51:50 -0700 Subject: [PATCH 0734/1893] virtio-devices: switch driver_status to Arc Signed-off-by: Peter Oskolkov --- virtio-devices/src/transport/pci_common_config.rs | 14 +++++++------- virtio-devices/src/transport/pci_device.rs | 10 ++++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 68ed01c60a..dcd65f7bc1 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -6,7 +6,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use std::sync::atomic::{AtomicU16, Ordering}; +use std::sync::atomic::{AtomicU8, AtomicU16, Ordering}; use std::sync::{Arc, Mutex}; use byteorder::{ByteOrder, LittleEndian}; @@ -125,7 +125,7 @@ pub fn get_vring_size(t: VringType, queue_size: u16) -> u64 { /// le64 queue_used; // 0x30 // read-write pub struct VirtioPciCommonConfig { pub access_platform: Option>, - pub driver_status: u8, + pub driver_status: Arc, pub config_generation: u8, pub device_feature_select: u32, pub driver_feature_select: u32, @@ -141,7 +141,7 @@ impl VirtioPciCommonConfig { ) -> Self { VirtioPciCommonConfig { access_platform, - driver_status: state.driver_status, + driver_status: Arc::new(AtomicU8::new(state.driver_status)), config_generation: state.config_generation, device_feature_select: state.device_feature_select, driver_feature_select: state.driver_feature_select, @@ -153,7 +153,7 @@ impl VirtioPciCommonConfig { fn state(&self) -> VirtioPciCommonConfigState { VirtioPciCommonConfigState { - driver_status: self.driver_status, + driver_status: self.driver_status.load(Ordering::Acquire), config_generation: self.config_generation, device_feature_select: self.device_feature_select, driver_feature_select: self.driver_feature_select, @@ -223,7 +223,7 @@ impl VirtioPciCommonConfig { debug!("read_common_config_byte: offset 0x{offset:x}"); // The driver is only allowed to do aligned, properly sized access. match offset { - 0x14 => self.driver_status, + 0x14 => self.driver_status.load(Ordering::Acquire), 0x15 => self.config_generation, _ => { warn!("invalid virtio config byte read: 0x{offset:x}"); @@ -235,7 +235,7 @@ impl VirtioPciCommonConfig { fn write_common_config_byte(&mut self, offset: u64, value: u8) { debug!("write_common_config_byte: offset 0x{offset:x}"); match offset { - 0x14 => self.driver_status = value, + 0x14 => self.driver_status.store(value, Ordering::Release), _ => { warn!("invalid virtio config byte write: 0x{offset:x}"); } @@ -437,7 +437,7 @@ mod unit_tests { fn write_base_regs() { let mut regs = VirtioPciCommonConfig { access_platform: None, - driver_status: 0xaa, + driver_status: Arc::new(AtomicU8::new(0xaa)), config_generation: 0x55, device_feature_select: 0x0, driver_feature_select: 0x0, diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 26440ddf2e..1a8d3da96e 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -660,13 +660,13 @@ impl VirtioPciDevice { fn is_driver_ready(&self) -> bool { let ready_bits = (DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK) as u8; - self.common_config.driver_status == ready_bits - && self.common_config.driver_status & DEVICE_FAILED as u8 == 0 + let driver_status = self.common_config.driver_status.load(Ordering::SeqCst); + driver_status == ready_bits && (driver_status & DEVICE_FAILED as u8) == 0 } /// Determines if the driver has requested the device (re)init / reset itself fn is_driver_init(&self) -> bool { - self.common_config.driver_status == DEVICE_INIT as u8 + self.common_config.driver_status.load(Ordering::SeqCst) == DEVICE_INIT as u8 } pub fn config_bar_addr(&self) -> u64 { @@ -1238,7 +1238,9 @@ impl PciDevice for VirtioPciDevice { self.common_config.queue_select = 0; } else { error!("Attempt to reset device when not implemented in underlying device"); - self.common_config.driver_status = crate::DEVICE_FAILED as u8; + self.common_config + .driver_status + .store(crate::DEVICE_FAILED as u8, Ordering::SeqCst); } } From b5053ae4dededfda406bc36c8c1956c4fc0ed704 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:55:03 -0700 Subject: [PATCH 0735/1893] virtio-devices: wire driver_status to EpollHandler Signed-off-by: Peter Oskolkov --- fuzz/fuzz_targets/balloon.rs | 1 + fuzz/fuzz_targets/block.rs | 1 + fuzz/fuzz_targets/console.rs | 1 + fuzz/fuzz_targets/iommu.rs | 1 + fuzz/fuzz_targets/mem.rs | 1 + fuzz/fuzz_targets/net.rs | 1 + fuzz/fuzz_targets/pmem.rs | 1 + fuzz/fuzz_targets/rng.rs | 1 + fuzz/fuzz_targets/vsock.rs | 1 + fuzz/fuzz_targets/watchdog.rs | 1 + virtio-devices/src/block.rs | 9 ++++++++- virtio-devices/src/device.rs | 3 ++- virtio-devices/src/net.rs | 9 ++++++++- virtio-devices/src/transport/pci_device.rs | 5 ++++- virtio-devices/src/vsock/device.rs | 2 ++ 15 files changed, 34 insertions(+), 4 deletions(-) diff --git a/fuzz/fuzz_targets/balloon.rs b/fuzz/fuzz_targets/balloon.rs index edb4f0cf1a..58b9b30582 100644 --- a/fuzz/fuzz_targets/balloon.rs +++ b/fuzz/fuzz_targets/balloon.rs @@ -103,6 +103,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { (1, deflate_q, deflate_evt), (2, reporting_q, reporting_evt), ], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index 19b875f044..51007fe384 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -95,6 +95,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/console.rs b/fuzz/fuzz_targets/console.rs index 7f5fafaebc..e27331ed01 100644 --- a/fuzz/fuzz_targets/console.rs +++ b/fuzz/fuzz_targets/console.rs @@ -132,6 +132,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .unwrap(); diff --git a/fuzz/fuzz_targets/iommu.rs b/fuzz/fuzz_targets/iommu.rs index 791ab6b000..a10640487f 100644 --- a/fuzz/fuzz_targets/iommu.rs +++ b/fuzz/fuzz_targets/iommu.rs @@ -114,6 +114,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { (0, request_queue, request_evt), (0, _event_queue, _event_evt), ], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/mem.rs b/fuzz/fuzz_targets/mem.rs index 46627e9315..73ec11b025 100644 --- a/fuzz/fuzz_targets/mem.rs +++ b/fuzz/fuzz_targets/mem.rs @@ -109,6 +109,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/net.rs b/fuzz/fuzz_targets/net.rs index 0af835ac06..df9a1dce5a 100644 --- a/fuzz/fuzz_targets/net.rs +++ b/fuzz/fuzz_targets/net.rs @@ -147,6 +147,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .unwrap(); diff --git a/fuzz/fuzz_targets/pmem.rs b/fuzz/fuzz_targets/pmem.rs index b42c20daea..0bd083a1c2 100644 --- a/fuzz/fuzz_targets/pmem.rs +++ b/fuzz/fuzz_targets/pmem.rs @@ -65,6 +65,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/rng.rs b/fuzz/fuzz_targets/rng.rs index d9cd11f099..13548664a8 100644 --- a/fuzz/fuzz_targets/rng.rs +++ b/fuzz/fuzz_targets/rng.rs @@ -103,6 +103,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/vsock.rs b/fuzz/fuzz_targets/vsock.rs index 72bdeb4d63..33ebe78886 100644 --- a/fuzz/fuzz_targets/vsock.rs +++ b/fuzz/fuzz_targets/vsock.rs @@ -112,6 +112,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/watchdog.rs b/fuzz/fuzz_targets/watchdog.rs index 8736f8af3f..31361755df 100644 --- a/fuzz/fuzz_targets/watchdog.rs +++ b/fuzz/fuzz_targets/watchdog.rs @@ -68,6 +68,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 82eca61827..ee05b6e8d6 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -13,7 +13,7 @@ use std::num::Wrapping; use std::ops::Deref; use std::os::unix::io::AsRawFd; use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering}; use std::sync::{Arc, Barrier}; use std::{io, result}; @@ -161,6 +161,8 @@ struct BlockEpollHandler { host_cpus: Option>, acked_features: u64, disable_sector0_writes: bool, + #[allow(unused)] + device_status: Arc, } fn has_feature(features: u64, feature_flag: u64) -> bool { @@ -666,6 +668,7 @@ pub struct Block { queue_affinity: BTreeMap>, disable_sector0_writes: bool, lock_granularity_choice: LockGranularityChoice, + device_status: Arc, } #[derive(Serialize, Deserialize)] @@ -813,6 +816,7 @@ impl Block { queue_affinity, disable_sector0_writes, lock_granularity_choice: lock_granularity, + device_status: Arc::new(AtomicU8::new(0)), }) } @@ -1013,7 +1017,9 @@ impl VirtioDevice for Block { mem, interrupt_cb, mut queues, + device_status, } = context; + self.device_status = device_status; // See if the guest didn't ack the device being read-only. // If so, warn and pretend it did. let original_acked_features = self.common.acked_features; @@ -1069,6 +1075,7 @@ impl VirtioDevice for Block { host_cpus: self.queue_affinity.get(&queue_idx).cloned(), acked_features: self.common.acked_features, disable_sector0_writes: self.disable_sector0_writes, + device_status: self.device_status.clone(), }; let paused = self.common.paused.clone(); diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index d5e873bf42..4b5cdaf03f 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use std::io::Write; use std::num::Wrapping; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Barrier}; use std::thread; @@ -57,6 +57,7 @@ pub struct ActivationContext { pub mem: GuestMemoryAtomic, pub interrupt_cb: Arc, pub queues: Vec<(usize, Queue, EventFd)>, + pub device_status: Arc, } /// Trait for virtio devices to be driven by a virtio transport. diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 2e3aa02eaf..9f72b93103 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -10,7 +10,7 @@ use std::net::IpAddr; use std::num::Wrapping; use std::ops::Deref; use std::os::unix::io::{AsRawFd, RawFd}; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Barrier}; use std::{result, thread}; @@ -179,6 +179,8 @@ struct NetEpollHandler { // a restore as the vCPU thread isn't ready to handle the interrupt. This causes // issues when combined with VIRTIO_RING_F_EVENT_IDX interrupt suppression. driver_awake: bool, + #[allow(unused)] + device_status: Arc, } impl NetEpollHandler { @@ -414,6 +416,7 @@ pub struct Net { seccomp_action: SeccompAction, rate_limiter_config: Option, exit_evt: EventFd, + device_status: Arc, } #[derive(Serialize, Deserialize)] @@ -535,6 +538,7 @@ impl Net { seccomp_action, rate_limiter_config, exit_evt, + device_status: Arc::new(AtomicU8::new(0)), }) } @@ -698,7 +702,9 @@ impl VirtioDevice for Net { mem, interrupt_cb, mut queues, + device_status, } = context; + self.device_status = device_status; self.common.activate(&queues, interrupt_cb.clone())?; let num_queues = queues.len(); @@ -803,6 +809,7 @@ impl VirtioDevice for Net { kill_evt, pause_evt, driver_awake: false, + device_status: self.device_status.clone(), }; let paused = self.common.paused.clone(); diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 1a8d3da96e..7f049070d6 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -10,7 +10,7 @@ use std::any::Any; use std::cmp; use std::io::Write; use std::ops::Deref; -use std::sync::atomic::{AtomicBool, AtomicU16, AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU16, AtomicUsize, Ordering}; use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; @@ -306,6 +306,7 @@ pub struct VirtioPciDeviceActivator { queues: Option>, barrier: Option>, id: String, + status: Arc, } impl VirtioPciDeviceActivator { @@ -315,6 +316,7 @@ impl VirtioPciDeviceActivator { mem: self.memory.take().unwrap(), interrupt_cb: self.interrupt.take().unwrap(), queues: self.queues.take().unwrap(), + device_status: self.status, })?; self.device_activated.store(true, Ordering::SeqCst); @@ -820,6 +822,7 @@ impl VirtioPciDevice { device_activated: self.device_activated.clone(), barrier, id: self.id.clone(), + status: self.common_config.driver_status.clone(), } } diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 5b215c3597..6d38ecf398 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -598,6 +598,7 @@ mod unit_tests { mem: memory.clone(), interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: Vec::new(), + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }); match bad_activate { Err(ActivateError::BadActivate) => (), @@ -626,6 +627,7 @@ mod unit_tests { EventFd::new(EFD_NONBLOCK).unwrap(), ), ], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .unwrap(); } From 563303b50a6d4a06b0660840887684d6167da3c2 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:56:49 -0700 Subject: [PATCH 0736/1893] 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: https://github.com/rust-vmm/vm-virtio/blob/863837ef863f6880bb8357e60bbac49e72c0844c/virtio-queue/src/queue.rs#L708 but this error is (logged and) ignored if pop_descriptor_chain() is used: https://github.com/rust-vmm/vm-virtio/blob/863837ef863f6880bb8357e60bbac49e72c0844c/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 #include + +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 --- net_util/src/queue_pair.rs | 16 +++++++-- virtio-devices/src/lib.rs | 1 + virtio-devices/src/net.rs | 67 +++++++++++++++++++++++++++++++------- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index 86a1c758dc..c0b8825e71 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -51,7 +51,13 @@ impl TxVirtio { let mut retry_write = false; let mut rate_limit_reached = false; - while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) { + loop { + let mut iter = queue + .iter(mem) + .map_err(NetQueuePairError::QueueIteratorFailed)?; + let Some(mut desc_chain) = iter.next() else { + break; + }; if rate_limit_reached { queue.go_to_previous_position(); break; @@ -180,7 +186,13 @@ impl RxVirtio { let mut exhausted_descs = true; let mut rate_limit_reached = false; - while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) { + loop { + let mut iter = queue + .iter(mem) + .map_err(NetQueuePairError::QueueIteratorFailed)?; + let Some(mut desc_chain) = iter.next() else { + break; + }; if rate_limit_reached { exhausted_descs = false; queue.go_to_previous_position(); diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index 14e565ccb8..f69ff5579e 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -66,6 +66,7 @@ const DEVICE_ACKNOWLEDGE: u32 = 0x01; const DEVICE_DRIVER: u32 = 0x02; const DEVICE_DRIVER_OK: u32 = 0x04; const DEVICE_FEATURES_OK: u32 = 0x08; +const DEVICE_NEEDS_RESET: u32 = 0x40; const DEVICE_FAILED: u32 = 0x80; const VIRTIO_F_RING_INDIRECT_DESC: u32 = 28; diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 9f72b93103..ec8afc2cc7 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -16,7 +16,7 @@ use std::{result, thread}; use anyhow::anyhow; use event_monitor::event; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; #[cfg(not(fuzzing))] use net_util::virtio_features_to_tap_offload; use net_util::{ @@ -179,7 +179,6 @@ struct NetEpollHandler { // a restore as the vCPU thread isn't ready to handle the interrupt. This causes // issues when combined with VIRTIO_RING_F_EVENT_IDX interrupt suppression. driver_awake: bool, - #[allow(unused)] device_status: Arc, } @@ -194,6 +193,9 @@ impl NetEpollHandler { } fn handle_rx_event(&mut self) -> result::Result<(), DeviceError> { + if self.needs_reset() { + return Ok(()); + } let queue_evt = &self.queue_evt_pair.0; if let Err(e) = queue_evt.read() { error!("Failed to get rx queue event: {e:?}"); @@ -222,13 +224,43 @@ impl NetEpollHandler { Ok(()) } + fn handle_queue_iterator_error(&mut self, err: &virtio_queue::Error) { + // The guest submitted a corrupted VirtQ request, and the error + // was logged during queue processing. We cannot just ignore the + // error, as the guest could continue spamming the VMM with bad + // requests, triggering excessive error logging. So we mark + // the device "NEEDS_RESET", effectively stopping all request + // processing (see self.needs_reset() usage) until the guest + // resets and reactivates the device. + + warn!( + "Corrupted request detected (virtqueue error: {err:?}). \ +Setting device status to 'NEEDS_RESET' and stopping processing queues until reset." + ); + + self.device_status + .fetch_or(crate::DEVICE_NEEDS_RESET as u8, Ordering::SeqCst); + + // Let the guest know that the device status has changed. + if let Err(e) = self.interrupt_cb.trigger(VirtioInterruptType::Config) { + error!("Failed to signal config interrupt: {e:?}"); + } + } + fn process_tx(&mut self) -> result::Result<(), DeviceError> { - if self + if self.needs_reset() { + return Ok(()); + } + let res = self .net - .process_tx(&self.mem.memory(), &mut self.queue_pair.1) - .map_err(DeviceError::NetQueuePair)? - || !self.driver_awake - { + .process_tx(&self.mem.memory(), &mut self.queue_pair.1); + + if let Err(net_util::NetQueuePairError::QueueIteratorFailed(err)) = res { + self.handle_queue_iterator_error(&err); + return Ok(()); + } + + if res.map_err(DeviceError::NetQueuePair)? || !self.driver_awake { self.signal_used_queue(self.queue_index_base + 1)?; debug!("Signalling TX queue"); } else { @@ -252,12 +284,19 @@ impl NetEpollHandler { } fn handle_rx_tap_event(&mut self) -> result::Result<(), DeviceError> { - if self + if self.needs_reset() { + return Ok(()); + } + let res = self .net - .process_rx(&self.mem.memory(), &mut self.queue_pair.0) - .map_err(DeviceError::NetQueuePair)? - || !self.driver_awake - { + .process_rx(&self.mem.memory(), &mut self.queue_pair.0); + + if let Err(net_util::NetQueuePairError::QueueIteratorFailed(err)) = res { + self.handle_queue_iterator_error(&err); + return Ok(()); + } + + if res.map_err(DeviceError::NetQueuePair)? || !self.driver_awake { self.signal_used_queue(self.queue_index_base)?; debug!("Signalling RX queue"); } else { @@ -307,6 +346,10 @@ impl NetEpollHandler { Ok(()) } + + fn needs_reset(&self) -> bool { + (self.device_status.load(Ordering::Acquire) & crate::DEVICE_NEEDS_RESET as u8) != 0 + } } impl EpollHelperHandler for NetEpollHandler { From 8b60b38281f95a519802e21358080582c28c46ef Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:58:15 -0700 Subject: [PATCH 0737/1893] virtio-devices: block: handle corrupted requests with NEEDS_RESET Signed-off-by: Peter Oskolkov --- virtio-devices/src/block.rs | 46 +++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index ee05b6e8d6..1668565340 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -161,7 +161,6 @@ struct BlockEpollHandler { host_cpus: Option>, acked_features: u64, disable_sector0_writes: bool, - #[allow(unused)] device_status: Arc, } @@ -170,6 +169,10 @@ fn has_feature(features: u64, feature_flag: u64) -> bool { } impl BlockEpollHandler { + fn needs_reset(&self) -> bool { + (self.device_status.load(Ordering::Acquire) & crate::DEVICE_NEEDS_RESET as u8) != 0 + } + fn check_request( features: u64, request: &Request, @@ -200,12 +203,48 @@ impl BlockEpollHandler { Ok(()) } + fn handle_queue_iterator_error(&mut self, err: &virtio_queue::Error) { + // The guest submitted a corrupted VirtQ request, and the error + // was logged during queue processing. We cannot just ignore the + // error, as the guest could continue spamming the VMM with bad + // requests, triggering excessive error logging. So we mark + // the device "NEEDS_RESET", effectively stopping all request + // processing (see self.needs_reset() usage) until the guest + // resets and reactivates the device. + + warn!( + "Corrupted request detected (virtqueue error: {err:?}). \ +Setting device status to 'NEEDS_RESET' and stopping processing queues until reset." + ); + + self.device_status + .fetch_or(crate::DEVICE_NEEDS_RESET as u8, Ordering::SeqCst); + + // Let the guest know that the device status has changed. + if let Err(e) = self.interrupt_cb.trigger(VirtioInterruptType::Config) { + error!("Failed to signal config interrupt: {e:?}"); + } + } + fn process_queue_submit(&mut self) -> Result<()> { + if self.needs_reset() { + return Ok(()); + } let queue = &mut self.queue; let mut batch_requests = Vec::new(); let mut batch_inflight_requests = Vec::new(); - while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) { + loop { + let mut desc_chain = match queue.iter(self.mem.memory()) { + Ok(mut iter) => match iter.next() { + Some(c) => c, + None => break, + }, + Err(err) => { + self.handle_queue_iterator_error(&err); + return Ok(()); + } + }; let mut request = Request::parse(&mut desc_chain, self.access_platform.as_deref()) .map_err(Error::RequestParsing)?; @@ -388,6 +427,9 @@ impl BlockEpollHandler { } fn process_queue_complete(&mut self) -> Result<()> { + if self.needs_reset() { + return Ok(()); + } let mem = self.mem.memory(); let mut read_bytes = Wrapping(0); let mut write_bytes = Wrapping(0); From c53bc3d1701b6e1c2fc67aea3ba90d2ac473aa78 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 13 Mar 2026 18:15:12 -0700 Subject: [PATCH 0738/1893] 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 --- scripts/prepare_vdpa.sh | 92 +++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/scripts/prepare_vdpa.sh b/scripts/prepare_vdpa.sh index 4a99daaf7b..601e61dbcf 100755 --- a/scripts/prepare_vdpa.sh +++ b/scripts/prepare_vdpa.sh @@ -1,35 +1,73 @@ #!/usr/bin/env bash set -x -sudo apt install -y libncurses-dev gawk flex bison openssl libssl-dev dkms libelf-dev libudev-dev libpci-dev libiberty-dev autoconf git make dpkg-dev libmnl-dev pkg-config iproute2 -sudo sed -i -- 's/# deb-src/deb-src/g' /etc/apt/sources.list -sudo apt update -apt-get source linux-image-unsigned-"$(uname -r)" -pushd linux-azure*/drivers/vdpa/vdpa_sim/ || exit -# REUSE-IgnoreStart -cat <<'EOF' >Makefile +build_install_vdpa_sim_modules_ubuntu() { + sudo apt install -y libncurses-dev gawk flex bison openssl libssl-dev dkms libelf-dev libudev-dev libpci-dev libiberty-dev autoconf git make dpkg-dev libmnl-dev pkg-config iproute2 + sudo sed -i -- 's/# deb-src/deb-src/g' /etc/apt/sources.list + sudo apt update + apt-get source linux-image-unsigned-"$(uname -r)" + pushd linux-azure*/drivers/vdpa/vdpa_sim/ || exit + # REUSE-IgnoreStart + cat <<'EOF' >Makefile # SPDX-License-Identifier: GPL-2.0 obj-m += vdpa_sim.o obj-m += vdpa_sim_net.o obj-m += vdpa_sim_blk.o EOF -# REUSE-IgnoreEnd -make -C /lib/modules/"$(uname -r)"/build M="$PWD" -sudo make -C /lib/modules/"$(uname -r)"/build M="$PWD" modules_install -popd || exit -sudo depmod -a -sudo modprobe vdpa -sudo modprobe vhost_vdpa -sudo modprobe vdpa_sim -sudo modprobe vdpa_sim_blk -sudo modprobe vdpa_sim_net -# Create /dev/vhost-vdpa-0 -sudo vdpa dev add name vdpa-blk1 mgmtdev vdpasim_blk -# Create /dev/vhost-vdpa-1 -sudo vdpa dev add name vdpa-blk2 mgmtdev vdpasim_blk -# Create /dev/vhost-vdpa-2 -sudo vdpa dev add name vdpa-net1 mgmtdev vdpasim_net -sudo chmod 660 /dev/vhost-vdpa-0 -sudo chmod 660 /dev/vhost-vdpa-1 -sudo chmod 660 /dev/vhost-vdpa-2 -vdpa dev show -jp + # REUSE-IgnoreEnd + make -C /lib/modules/"$(uname -r)"/build M="$PWD" + sudo make -C /lib/modules/"$(uname -r)"/build M="$PWD" modules_install + popd || exit + sudo depmod -a +} + +check_vdpa_sim_modules() { + for module in $MODULES; do + modinfo "$module" || { + echo "Module $module is not installed. Please build and install it first." + exit 1 + } + done +} + +modproobe_modules() { + for module in $MODULES; do + sudo modprobe "$module" || { + echo "Failed to load module $module. Please check if it is installed correctly." + exit 1 + } + done +} + +prepare_vdpa() { + # Create /dev/vhost-vdpa-0 + sudo vdpa dev add name vdpa-blk1 mgmtdev vdpasim_blk + # Create /dev/vhost-vdpa-1 + sudo vdpa dev add name vdpa-blk2 mgmtdev vdpasim_blk + # Create /dev/vhost-vdpa-2 + sudo vdpa dev add name vdpa-net1 mgmtdev vdpasim_net + sudo chmod 660 /dev/vhost-vdpa-0 + sudo chmod 660 /dev/vhost-vdpa-1 + sudo chmod 660 /dev/vhost-vdpa-2 + vdpa dev show -jp +} + +MODULES="vdpa vhost_vdpa vdpa_sim vdpa_sim_blk vdpa_sim_net" +DISTRO_NAME="ubuntu" +if [[ -f /etc/lsb-release ]]; then + DISTRO_NAME=$(grep DISTRIB_ID /etc/lsb-release | cut -d '=' -f 2) + # Converts the value of the DISTRO_NAME variable to lowercase letters. + DISTRO_NAME=$(echo "$DISTRO_NAME" | tr '[:upper:]' '[:lower:]') + echo "Distribution Name: $DISTRO_NAME" +fi + +if [[ "$DISTRO_NAME" == "ubuntu" ]]; then + build_install_vdpa_sim_modules_ubuntu +fi +# For other distros, we assume the modules are already built and installed +# For Azure Linux, the modules are included in the kernel and should be available by default +check_vdpa_sim_modules + +modproobe_modules + +prepare_vdpa From 5aa68ddf9ebc0c36fc85eb64e03007b965675284 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 12:05:17 +0100 Subject: [PATCH 0739/1893] 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 --- block/src/lib.rs | 2 + block/src/raw_async_io_tests.rs | 162 ++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 block/src/raw_async_io_tests.rs diff --git a/block/src/lib.rs b/block/src/lib.rs index f477cd36c6..6e3e50178f 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -24,6 +24,8 @@ pub mod qcow_sync; /// Enabled with the `"io_uring"` feature pub mod raw_async; pub mod raw_async_aio; +#[cfg(test)] +mod raw_async_io_tests; pub mod raw_sync; pub mod vhd; pub mod vhdx; diff --git a/block/src/raw_async_io_tests.rs b/block/src/raw_async_io_tests.rs new file mode 100644 index 0000000000..560e41e334 --- /dev/null +++ b/block/src/raw_async_io_tests.rs @@ -0,0 +1,162 @@ +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// 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}; + +/// 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) = async_io.next_completed_request().unwrap(); + 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) = async_io.next_completed_request().unwrap(); + 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) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 10); + assert_eq!(result, 0); + + let (user_data, result) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 11); + assert_eq!(result, 0); + + let (user_data, result) = async_io.next_completed_request().unwrap(); + 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)); +} From d883b54fb7cf6fff2ea53645b16fb6f51f727f82 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 12:07:57 +0100 Subject: [PATCH 0740/1893] 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 --- block/src/raw_async_aio.rs | 150 ++----------------------------------- block/src/raw_sync.rs | 150 ++----------------------------------- 2 files changed, 10 insertions(+), 290 deletions(-) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 20fb26c2c4..7f66ce4114 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -242,174 +242,34 @@ impl AsyncIo for RawFileAsyncAio { #[cfg(test)] mod unit_tests { - use std::io::{Read, Seek, SeekFrom, Write}; + use std::os::unix::io::AsRawFd; use vmm_sys_util::tempfile::TempFile; use super::*; + use crate::raw_async_io_tests; #[test] fn test_punch_hole() { let temp_file = TempFile::new().unwrap(); let mut file = temp_file.into_file(); - - // Write 4MB of data - let data = vec![0xAA; 4 * 1024 * 1024]; - file.write_all(&data).unwrap(); - file.sync_all().unwrap(); - - // Create async IO instance let mut async_io = RawFileAsyncAio::new(file.as_raw_fd(), 128).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) = async_io.next_completed_request().unwrap(); - 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" - ); + raw_async_io_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(); - - // Write 4MB of data - let data = vec![0xBB; 4 * 1024 * 1024]; - file.write_all(&data).unwrap(); - file.sync_all().unwrap(); - - // Create async IO instance let mut async_io = RawFileAsyncAio::new(file.as_raw_fd(), 128).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) = async_io.next_completed_request().unwrap(); - 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" - ); + raw_async_io_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(); - - // Write 8MB of data - let data = vec![0xCC; 8 * 1024 * 1024]; - file.write_all(&data).unwrap(); - file.sync_all().unwrap(); - - // Create async IO instance let mut async_io = RawFileAsyncAio::new(file.as_raw_fd(), 128).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) = async_io.next_completed_request().unwrap(); - assert_eq!(user_data, 10); - assert_eq!(result, 0); - - let (user_data, result) = async_io.next_completed_request().unwrap(); - assert_eq!(user_data, 11); - assert_eq!(result, 0); - - let (user_data, result) = async_io.next_completed_request().unwrap(); - 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)); + raw_async_io_tests::test_punch_hole_multiple_operations(&mut async_io, &mut file); } } diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 9c2d6b7893..0e60a48a75 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -211,174 +211,34 @@ impl AsyncIo for RawFileSync { #[cfg(test)] mod unit_tests { - use std::io::{Read, Seek, SeekFrom, Write}; + use std::os::unix::io::AsRawFd; use vmm_sys_util::tempfile::TempFile; use super::*; + use crate::raw_async_io_tests; #[test] fn test_punch_hole() { let temp_file = TempFile::new().unwrap(); let mut file = temp_file.into_file(); - - // Write 4MB of data - let data = vec![0xAA; 4 * 1024 * 1024]; - file.write_all(&data).unwrap(); - file.sync_all().unwrap(); - - // Create async IO instance let mut async_io = RawFileSync::new(file.as_raw_fd()); - - // 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) = async_io.next_completed_request().unwrap(); - 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" - ); + raw_async_io_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(); - - // Write 4MB of data - let data = vec![0xBB; 4 * 1024 * 1024]; - file.write_all(&data).unwrap(); - file.sync_all().unwrap(); - - // Create async IO instance let mut async_io = RawFileSync::new(file.as_raw_fd()); - - // 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) = async_io.next_completed_request().unwrap(); - 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" - ); + raw_async_io_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(); - - // Write 8MB of data - let data = vec![0xCC; 8 * 1024 * 1024]; - file.write_all(&data).unwrap(); - file.sync_all().unwrap(); - - // Create async IO instance let mut async_io = RawFileSync::new(file.as_raw_fd()); - - // 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) = async_io.next_completed_request().unwrap(); - assert_eq!(user_data, 10); - assert_eq!(result, 0); - - let (user_data, result) = async_io.next_completed_request().unwrap(); - assert_eq!(user_data, 11); - assert_eq!(result, 0); - - let (user_data, result) = async_io.next_completed_request().unwrap(); - 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)); + raw_async_io_tests::test_punch_hole_multiple_operations(&mut async_io, &mut file); } } From 05aa9ebcd412476d79964d749a9a17f6c8473984 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 13:26:52 +0100 Subject: [PATCH 0741/1893] 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 --- block/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 6e3e50178f..c02b315cc2 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -45,7 +45,9 @@ use std::{cmp, mem, result}; #[cfg(feature = "io_uring")] use io_uring::{IoUring, Probe, opcode}; -use libc::{S_IFBLK, S_IFMT, ioctl}; +use libc::{ + FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE, S_IFBLK, S_IFMT, ioctl, +}; use log::{debug, error, info, warn}; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -807,10 +809,6 @@ pub fn probe_sparse_support(file: &File) -> bool { /// Probe sparse support for a regular file using fallocate(). fn probe_file_sparse_support(fd: libc::c_int) -> bool { - const FALLOC_FL_KEEP_SIZE: libc::c_int = 0x01; - const FALLOC_FL_PUNCH_HOLE: libc::c_int = 0x02; - const FALLOC_FL_ZERO_RANGE: libc::c_int = 0x10; - // SAFETY: FFI call with valid fd let file_size = unsafe { libc::lseek(fd, 0, libc::SEEK_END) }; if file_size < 0 { From 1733e08a0f075c804db299bd0ae883ed94dffe9b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 13:27:18 +0100 Subject: [PATCH 0742/1893] 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 --- block/src/raw_async.rs | 5 +---- block/src/raw_async_aio.rs | 5 +---- block/src/raw_sync.rs | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 152e5fa3ba..8544040f50 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -7,6 +7,7 @@ use std::io::{Error, Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; use io_uring::{IoUring, opcode, types}; +use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; use log::warn; use vmm_sys_util::eventfd::EventFd; @@ -268,8 +269,6 @@ impl AsyncIo for RawFileAsync { fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { let (submitter, mut sq, _) = self.io_uring.split(); - const FALLOC_FL_PUNCH_HOLE: i32 = 0x02; - const FALLOC_FL_KEEP_SIZE: i32 = 0x01; let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; // SAFETY: The file descriptor is known to be valid. @@ -295,8 +294,6 @@ impl AsyncIo for RawFileAsync { fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { let (submitter, mut sq, _) = self.io_uring.split(); - const FALLOC_FL_ZERO_RANGE: i32 = 0x10; - const FALLOC_FL_KEEP_SIZE: i32 = 0x01; let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE; // SAFETY: The file descriptor is known to be valid. diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 7f66ce4114..f59e463b4c 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -10,6 +10,7 @@ use std::fs::File; use std::io::{Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; +use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; use log::warn; use vmm_sys_util::aio; use vmm_sys_util::eventfd::EventFd; @@ -189,8 +190,6 @@ impl AsyncIo for RawFileAsyncAio { // 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 (RawFileSync). - const FALLOC_FL_PUNCH_HOLE: i32 = 0x02; - const FALLOC_FL_KEEP_SIZE: i32 = 0x01; let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; // SAFETY: FFI call with valid arguments @@ -216,8 +215,6 @@ impl AsyncIo for RawFileAsyncAio { // 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 (RawFileSync). - const FALLOC_FL_ZERO_RANGE: i32 = 0x10; - const FALLOC_FL_KEEP_SIZE: i32 = 0x01; let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE; // SAFETY: FFI call with valid arguments diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 0e60a48a75..b9f89dde05 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -7,6 +7,7 @@ use std::fs::File; use std::io::{Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; +use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; use log::warn; use vmm_sys_util::eventfd::EventFd; @@ -161,8 +162,6 @@ impl AsyncIo for RawFileSync { } fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { - const FALLOC_FL_PUNCH_HOLE: i32 = 0x02; - const FALLOC_FL_KEEP_SIZE: i32 = 0x01; let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; // SAFETY: FFI call with valid arguments @@ -185,8 +184,6 @@ impl AsyncIo for RawFileSync { } fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { - const FALLOC_FL_ZERO_RANGE: i32 = 0x10; - const FALLOC_FL_KEEP_SIZE: i32 = 0x01; let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE; // SAFETY: FFI call with valid arguments From faebd57db88539feef8dbb037956e2135cea7cff Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 16 Mar 2026 10:51:05 +0100 Subject: [PATCH 0743/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- CONTRIBUTING.md | 53 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac65550c05..1518d0f1fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,22 +61,31 @@ commit you make. 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**._ -``` -: 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 ` footer +- Follow the pattern: \ + ``` + : Change summary + + 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. + + Signed-off-by: + ``` + -Signed-off-by: -``` +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`. -For example: +Example patch: ``` vm-virtio: Reset underlying device on driver request @@ -94,6 +103,20 @@ configure it anyway. Signed-off-by: Rob Bradford ``` +### 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 +127,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 `: addressing review for A`-style commits. + ## Issue tracking If you have a problem, please let us know. We recommend using From a97348d24e05a8dcea018bae00f85a8a42e6be0d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Mar 2026 02:38:50 -0700 Subject: [PATCH 0744/1893] 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 --- net_util/src/tap.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 36f0e2ba33..1622add3a6 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -663,7 +663,7 @@ mod unit_tests { } // Sends a test packet on the interface named "ifname". - fn pnet_send_packet(ifname: String) { + fn pnet_send_packet(ifname: &str) { let payload = DATA_STRING.as_bytes(); // eth hdr + ip hdr + udp hdr + payload len @@ -682,7 +682,7 @@ mod unit_tests { // interface, an object that can be used to send Ethernet frames, and a receiver of // Ethernet frames arriving at the specified interface. fn pnet_get_mac_tx_rx( - ifname: String, + ifname: &str, ) -> (MacAddr, Box, Box) { let interface_name_matches = |iface: &NetworkInterface| iface.name == ifname; @@ -778,7 +778,7 @@ mod unit_tests { tap.enable().unwrap(); // Send a packet to the interface. We expect to be able to receive it on the associated fd. - pnet_send_packet(tap.if_name_as_str().to_owned()); + pnet_send_packet(tap.if_name_as_str()); let mut buf = [0u8; 4096]; @@ -836,7 +836,7 @@ mod unit_tests { tap.set_ip_addr(ip_addr, Some(netmask)).unwrap(); tap.enable().unwrap(); - let (mac, _, mut rx) = pnet_get_mac_tx_rx(tap.if_name_as_str().to_owned()); + let (mac, _, mut rx) = pnet_get_mac_tx_rx(tap.if_name_as_str()); let payload = DATA_STRING.as_bytes(); From 5b23f0a154f9adc5d71f9a9eb2802ee24a230ecf Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Mar 2026 02:43:57 -0700 Subject: [PATCH 0745/1893] 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 --- cloud-hypervisor/tests/integration.rs | 142 +++++++++----------------- 1 file changed, 51 insertions(+), 91 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index f0d5f6f252..b8ab87e652 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -4599,15 +4599,14 @@ mod common_parallel { let output = child.wait_with_output().unwrap(); let stderr = String::from_utf8_lossy(&output.stderr); panic!( - "VM should not have exited when opening corrupt image as readonly. Exit status: {}, stderr: {}", - status, stderr + "VM should not have exited when opening corrupt image as readonly. Exit status: {status}, stderr: {stderr}" ); } Ok(None) => { // VM is still running as expected } Err(e) => { - panic!("Error checking process status: {}", e); + panic!("Error checking process status: {e}"); } } @@ -4617,8 +4616,7 @@ mod common_parallel { let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("QCOW2 image is marked corrupt, opening read-only"), - "Expected warning about corrupt image being opened read-only. stderr: {}", - stderr + "Expected warning about corrupt image being opened read-only. stderr: {stderr}" ); assert_eq!( @@ -6764,9 +6762,9 @@ mod common_parallel { // Create a disk image that we can write to assert!( - exec_host_command_output(&format!( - "sudo dd if=/dev/zero of=/tmp/resize.img bs=1M count=16" - )) + exec_host_command_output( + &"sudo dd if=/dev/zero of=/tmp/resize.img bs=1M count=16".to_string() + ) .status .success() ); @@ -7416,7 +7414,7 @@ mod common_parallel { // Helper function to verify sparse file fn verify_sparse_file(test_disk_path: &str, expected_ratio: f64) { - let res = exec_host_command_output(&format!("ls -s --block-size=1 {}", test_disk_path)); + let res = exec_host_command_output(&format!("ls -s --block-size=1 {test_disk_path}")); assert!(res.status.success(), "ls -s command failed"); let out = String::from_utf8_lossy(&res.stdout); let actual_bytes: u64 = out @@ -7425,7 +7423,7 @@ mod common_parallel { .and_then(|s| s.parse().ok()) .expect("Failed to parse ls -s output"); - let res = exec_host_command_output(&format!("ls -l {}", test_disk_path)); + let res = exec_host_command_output(&format!("ls -l {test_disk_path}")); assert!(res.status.success(), "ls -l command failed"); let out = String::from_utf8_lossy(&res.stdout); let apparent_size: u64 = out @@ -7437,17 +7435,14 @@ mod common_parallel { let threshold = (apparent_size as f64 * expected_ratio) as u64; assert!( actual_bytes < threshold, - "Expected file to be sparse: apparent_size={} bytes, actual_disk_usage={} bytes (threshold={})", - apparent_size, - actual_bytes, - threshold + "Expected file to be sparse: apparent_size={apparent_size} bytes, actual_disk_usage={actual_bytes} bytes (threshold={threshold})" ); } // Helper function to count zero flagged regions in QCOW2 image fn count_qcow2_zero_regions(test_disk_path: &str) -> Option { let res = - exec_host_command_output(&format!("qemu-img map --output=json -U {}", test_disk_path)); + exec_host_command_output(&format!("qemu-img map --output=json -U {test_disk_path}")); if !res.status.success() { return None; } @@ -7476,8 +7471,7 @@ mod common_parallel { // - RAW: Verify actual holes (unallocated extents) exist in sparse regions // - Could parse extent output to count holes vs allocated regions fn verify_fiemap_extents(test_disk_path: &str, format_type: &str) { - let blocksize_output = - exec_host_command_output(&format!("stat -f -c %S {}", test_disk_path)); + let blocksize_output = exec_host_command_output(&format!("stat -f -c %S {test_disk_path}")); let blocksize = if blocksize_output.status.success() { String::from_utf8_lossy(&blocksize_output.stdout) .trim() @@ -7488,7 +7482,7 @@ mod common_parallel { }; let fiemap_output = - exec_host_command_output(&format!("filefrag -b {} -v {}", blocksize, test_disk_path)); + exec_host_command_output(&format!("filefrag -b {blocksize} -v {test_disk_path}")); if fiemap_output.status.success() { let fiemap_str = String::from_utf8_lossy(&fiemap_output.stdout); @@ -7498,8 +7492,7 @@ mod common_parallel { assert!( has_extents || has_holes, - "FIEMAP should show extent information or holes for {} file", - format_type + "FIEMAP should show extent information or holes for {format_type} file" ); } } @@ -7508,8 +7501,7 @@ mod common_parallel { fn assert_guest_disk_region_is_zero(guest: &Guest, device: &str, offset: u64, length: u64) { let result = guest .ssh_command(&format!( - "sudo hexdump -v -s {} -n {} -e '1/1 \"%02x\"' {} | grep -qv '^00*$' && echo 'NONZERO' || echo 'ZEROS'", - offset, length, device + "sudo hexdump -v -s {offset} -n {length} -e '1/1 \"%02x\"' {device} | grep -qv '^00*$' && echo 'NONZERO' || echo 'ZEROS'" )) .unwrap(); @@ -7560,7 +7552,7 @@ mod common_parallel { .as_path() .join(format!("discard_test.{}", format_name.to_lowercase())); - let mut cmd = format!("qemu-img create -f {} ", qemu_img_format); + let mut cmd = format!("qemu-img create -f {qemu_img_format} "); if !extra_create_args.is_empty() { cmd.push_str(&extra_create_args.join(" ")); cmd.push(' '); @@ -7570,8 +7562,7 @@ mod common_parallel { let res = exec_host_command_output(&cmd); assert!( res.status.success(), - "Failed to create {} test image", - format_name + "Failed to create {format_name} test image" ); let mut child = GuestCommand::new(&guest) @@ -7641,8 +7632,7 @@ mod common_parallel { // Write one 4MB block at offset 1MB guest .ssh_command(&format!( - "sudo dd if=/dev/zero of=/dev/vdc bs=1M count={} seek={} oflag=direct", - WRITE_SIZE_MB, WRITE_OFFSET_MB + "sudo dd if=/dev/zero of=/dev/vdc bs=1M count={WRITE_SIZE_MB} seek={WRITE_OFFSET_MB} oflag=direct" )) .unwrap(); guest.ssh_command("sync").unwrap(); @@ -7668,19 +7658,14 @@ mod common_parallel { for (i, (offset, length)) in discard_operations.iter().enumerate() { let result = guest .ssh_command(&format!( - "sudo blkdiscard -v -o {} -l {} /dev/vdc 2>&1 || true", - offset, length + "sudo blkdiscard -v -o {offset} -l {length} /dev/vdc 2>&1 || true" )) .unwrap(); assert!( !result.contains("Operation not supported") && !result.contains("BLKDISCARD"), - "blkdiscard #{} at offset {} length {} failed: {}", - i, - offset, - length, - result + "blkdiscard #{i} at offset {offset} length {length} failed: {result}" ); } @@ -7746,8 +7731,7 @@ mod common_parallel { let all_zeros = buffer.iter().all(|&b| b == 0); assert!( all_zeros, - "Expected discarded region at offset {} length {} to contain all zeros", - offset, length + "Expected discarded region at offset {offset} length {length} to contain all zeros" ); } @@ -8112,7 +8096,7 @@ mod common_parallel { .as_path() .join(format!("fstrim_test.{}", format_name.to_lowercase())); - let mut cmd = format!("qemu-img create -f {} ", qemu_img_format); + let mut cmd = format!("qemu-img create -f {qemu_img_format} "); if !extra_create_args.is_empty() { cmd.push_str(&extra_create_args.join(" ")); cmd.push(' '); @@ -8122,8 +8106,7 @@ mod common_parallel { let res = exec_host_command_output(&cmd); assert!( res.status.success(), - "Failed to create {} test image", - format_name + "Failed to create {format_name} test image" ); const WRITE_SIZE_MB: u64 = 4; @@ -8187,8 +8170,7 @@ mod common_parallel { for (iteration, &write_size_kb) in BLOCK_DISCARD_TEST_SIZES_KB.iter().enumerate() { guest .ssh_command(&format!( - "sudo dd if=/dev/zero of=/mnt/test/testfile{} bs=1K count={}", - iteration, write_size_kb + "sudo dd if=/dev/zero of=/mnt/test/testfile{iteration} bs=1K count={write_size_kb}" )) .unwrap(); @@ -8200,20 +8182,19 @@ mod common_parallel { "ls -s --block-size=1 {}", test_disk_path.to_str().unwrap() )); - if res.status.success() { - if let Some(size) = String::from_utf8_lossy(&res.stdout) + if res.status.success() + && let Some(size) = String::from_utf8_lossy(&res.stdout) .split_whitespace() .next() .and_then(|s| s.parse::().ok()) - { - max_size_during_writes.set(max_size_during_writes.get().max(size)); - } + { + max_size_during_writes.set(max_size_during_writes.get().max(size)); } } // Make blocks available for discard guest - .ssh_command(&format!("sudo rm /mnt/test/testfile{}", iteration)) + .ssh_command(&format!("sudo rm /mnt/test/testfile{iteration}")) .unwrap(); guest.ssh_command("sync").unwrap(); @@ -8224,10 +8205,7 @@ mod common_parallel { // Would output like "/mnt/test: X bytes (Y MB) trimmed" assert!( fstrim_result.contains("trimmed") || fstrim_result.contains("bytes"), - "fstrim iteration {} ({}KB) should report trimmed bytes: {}", - iteration, - write_size_kb, - fstrim_result + "fstrim iteration {iteration} ({write_size_kb}KB) should report trimmed bytes: {fstrim_result}" ); } else { // For unsupported formats, expect fstrim to fail @@ -8322,13 +8300,11 @@ mod common_parallel { let test_disk_path = guest.tmp_dir.as_path().join("sparse_off_test.raw"); let test_disk_path = test_disk_path.to_str().unwrap(); - let res = exec_host_command_output(&format!( - "truncate -s {} {}", - TEST_DISK_SIZE, test_disk_path - )); + let res = + exec_host_command_output(&format!("truncate -s {TEST_DISK_SIZE} {test_disk_path}")); assert!(res.status.success(), "Failed to create sparse test file"); - let res = exec_host_command_output(&format!("ls -s --block-size=1 {}", test_disk_path)); + let res = exec_host_command_output(&format!("ls -s --block-size=1 {test_disk_path}")); assert!(res.status.success()); let initial_bytes: u64 = String::from_utf8_lossy(&res.stdout) .split_whitespace() @@ -8337,8 +8313,7 @@ mod common_parallel { .expect("Failed to parse initial disk usage"); assert!( initial_bytes < INITIAL_ALLOCATION_THRESHOLD, - "File should be initially sparse: {} bytes allocated", - initial_bytes + "File should be initially sparse: {initial_bytes} bytes allocated" ); let mut child = GuestCommand::new(&guest) @@ -8358,7 +8333,7 @@ mod common_parallel { guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - format!("path={},sparse=off", test_disk_path).as_str(), + format!("path={test_disk_path},sparse=off").as_str(), ]) .default_net() .capture_output() @@ -8388,7 +8363,7 @@ mod common_parallel { // - physical >= logical is fully allocated, modulo block alignment // - physical < logical is still sparse - let res = exec_host_command_output(&format!("ls -l {}", test_disk_path)); + let res = exec_host_command_output(&format!("ls -l {test_disk_path}")); assert!(res.status.success()); let logical_size: u64 = String::from_utf8_lossy(&res.stdout) .split_whitespace() @@ -8396,7 +8371,7 @@ mod common_parallel { .and_then(|s| s.parse().ok()) .expect("Failed to parse logical size"); - let res = exec_host_command_output(&format!("ls -s --block-size=1 {}", test_disk_path)); + let res = exec_host_command_output(&format!("ls -s --block-size=1 {test_disk_path}")); assert!(res.status.success()); let physical_size: u64 = String::from_utf8_lossy(&res.stdout) .split_whitespace() @@ -8406,33 +8381,26 @@ mod common_parallel { assert_eq!( logical_size, TEST_DISK_SIZE_BYTES, - "Logical size should be exactly {} bytes, got {}", - TEST_DISK_SIZE_BYTES, logical_size + "Logical size should be exactly {TEST_DISK_SIZE_BYTES} bytes, got {logical_size}" ); - let res = exec_host_command_output(&format!("stat -c '%o' {}", test_disk_path)); + let res = exec_host_command_output(&format!("stat -c '%o' {test_disk_path}")); assert!(res.status.success()); let block_size: u64 = String::from_utf8_lossy(&res.stdout) .trim() .parse() .expect("Failed to parse block size from stat"); - let expected_max = ((logical_size + block_size - 1) / block_size) * block_size; + let expected_max = logical_size.div_ceil(block_size) * block_size; assert!( physical_size >= logical_size, - "File should be fully allocated with sparse=off: logical={} bytes, physical={} bytes (physical < logical means still sparse)", - logical_size, - physical_size + "File should be fully allocated with sparse=off: logical={logical_size} bytes, physical={physical_size} bytes (physical < logical means still sparse)" ); assert!( physical_size <= expected_max, - "Physical size seems too large: logical={} bytes, physical={} bytes, expected_max={} bytes (block_size={})", - logical_size, - physical_size, - expected_max, - block_size + "Physical size seems too large: logical={logical_size} bytes, physical={physical_size} bytes, expected_max={expected_max} bytes (block_size={block_size})" ); } @@ -8449,8 +8417,7 @@ mod common_parallel { let test_disk_path = test_disk_path.to_str().unwrap(); let res = exec_host_command_output(&format!( - "qemu-img create -f qcow2 {} {}", - test_disk_path, TEST_DISK_SIZE + "qemu-img create -f qcow2 {test_disk_path} {TEST_DISK_SIZE}" )); assert!(res.status.success(), "Failed to create QCOW2 test image"); @@ -8474,7 +8441,7 @@ mod common_parallel { guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - format!("path={},sparse=off,num_queues=4", test_disk_path).as_str(), + format!("path={test_disk_path},sparse=off,num_queues=4").as_str(), ]) .default_net() .capture_output() @@ -8496,11 +8463,10 @@ mod common_parallel { let mut current_offset_kb = 1024; - for (_iteration, &size_kb) in BLOCK_DISCARD_TEST_SIZES_KB.iter().enumerate() { + for &size_kb in BLOCK_DISCARD_TEST_SIZES_KB.iter() { guest .ssh_command(&format!( - "sudo dd if=/dev/urandom of=/dev/vdc bs=1K count={} seek={} oflag=direct", - size_kb, current_offset_kb + "sudo dd if=/dev/urandom of=/dev/vdc bs=1K count={size_kb} seek={current_offset_kb} oflag=direct" )) .unwrap(); @@ -8538,9 +8504,7 @@ mod common_parallel { assert!( zero_regions_after > zero_regions_before, - "Expected zero-flagged regions to increase with sparse=off: before={}, after={}", - zero_regions_before, - zero_regions_after + "Expected zero-flagged regions to increase with sparse=off: before={zero_regions_before}, after={zero_regions_after}" ); disk_check_consistency(&test_disk_path, None); @@ -12762,26 +12726,22 @@ mod vfio { fn test_nvidia_guest_numa_generic_initiator() { // Skip test if VFIO device is not available or not ready if !std::path::Path::new(NVIDIA_VFIO_DEVICE).exists() { - println!("SKIPPED: VFIO device {} not found", NVIDIA_VFIO_DEVICE); + println!("SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} not found"); return; } // Check if device is bound to vfio-pci driver - let driver_path = format!("{}/driver", NVIDIA_VFIO_DEVICE); + let driver_path = format!("{NVIDIA_VFIO_DEVICE}/driver"); if let Ok(driver) = std::fs::read_link(&driver_path) { let driver_name = driver.file_name().unwrap_or_default().to_string_lossy(); if driver_name != "vfio-pci" { println!( - "SKIPPED: VFIO device {} bound to {}, not vfio-pci", - NVIDIA_VFIO_DEVICE, driver_name + "SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} bound to {driver_name}, not vfio-pci" ); return; } } else { - println!( - "SKIPPED: VFIO device {} not bound to any driver", - NVIDIA_VFIO_DEVICE - ); + println!("SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} not bound to any driver"); return; } @@ -12802,7 +12762,7 @@ mod vfio { ]) .args([ "--device", - &format!("id=vfio0,path={},iommu=on", NVIDIA_VFIO_DEVICE), + &format!("id=vfio0,path={NVIDIA_VFIO_DEVICE},iommu=on"), ]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) From c14fd5c5754b44bd95a217ecbe4ae8e5ef8d36c5 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Mar 2026 02:47:18 -0700 Subject: [PATCH 0746/1893] 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 --- cloud-hypervisor/tests/integration.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index b8ab87e652..53fcbc17c3 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2513,7 +2513,7 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let mut guest = Guest::new(Box::new(disk_config)); guest.kernel_path = Some(fw_path(FwType::RustHypervisorFirmware)); - _test_simple_launch(&guest) + _test_simple_launch(&guest); } #[test] @@ -2522,7 +2522,7 @@ mod common_parallel { let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let mut guest = Guest::new(Box::new(disk_config)); guest.kernel_path = Some(fw_path(FwType::Ovmf)); - _test_simple_launch(&guest) + _test_simple_launch(&guest); } #[test] @@ -14787,7 +14787,7 @@ mod common_cvm { let guest = GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); - _test_simple_launch(&guest) + _test_simple_launch(&guest); } #[test] From b92ab6e4b10e92408f97fd557931e02f97747cfc Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Mar 2026 02:48:05 -0700 Subject: [PATCH 0747/1893] 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 --- cloud-hypervisor/tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 53fcbc17c3..25dde15c03 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -3485,7 +3485,7 @@ mod common_parallel { fn get_image_info(path: &std::path::Path) -> Option { let output = run_qemu_img(path, &["info", "-U", "--output=json"], None); - output.status.success().then(|| ())?; + output.status.success().then_some(())?; serde_json::from_slice(&output.stdout).ok() } From 41a2a8ea6278a4c24b30f766476bb478e618c638 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Mar 2026 02:49:43 -0700 Subject: [PATCH 0748/1893] 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 --- cloud-hypervisor/tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 25dde15c03..f7345bf8d9 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7673,7 +7673,7 @@ mod common_parallel { guest.ssh_command("sync").unwrap(); // Verify VM sees zeros in discarded regions - for (_i, (offset, length)) in discard_operations.iter().enumerate() { + for (offset, length) in discard_operations.iter() { assert_guest_disk_region_is_zero(&guest, "/dev/vdc", *offset, *length); } From 93d8896042a794fd63f11819ecb665c725755c52 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Mar 2026 02:50:13 -0700 Subject: [PATCH 0749/1893] 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 --- cloud-hypervisor/tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index f7345bf8d9..730b5b4688 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -8507,7 +8507,7 @@ mod common_parallel { "Expected zero-flagged regions to increase with sparse=off: before={zero_regions_before}, after={zero_regions_after}" ); - disk_check_consistency(&test_disk_path, None); + disk_check_consistency(test_disk_path, None); } #[test] From 0a7e32c31217af05102e9b30b1da4453b5061389 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Mar 2026 02:49:13 -0700 Subject: [PATCH 0750/1893] 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 --- cloud-hypervisor/tests/integration.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 730b5b4688..d48b18adbf 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -6762,11 +6762,9 @@ mod common_parallel { // Create a disk image that we can write to assert!( - exec_host_command_output( - &"sudo dd if=/dev/zero of=/tmp/resize.img bs=1M count=16".to_string() - ) - .status - .success() + exec_host_command_output("sudo dd if=/dev/zero of=/tmp/resize.img bs=1M count=16") + .status + .success() ); let mut cmd = GuestCommand::new(&guest); From 14d8cf5f1e0a817e7194bf3f7bd89900ed6dfeb0 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Mar 2026 03:01:27 -0700 Subject: [PATCH 0751/1893] 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(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 --- cloud-hypervisor/tests/integration.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index d48b18adbf..8841b00aea 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -3782,7 +3782,7 @@ mod common_parallel { /// /// Creates a VM with multiple virtio queues on the test disk, then runs the /// provided test closure. Handles VM lifecycle and consistency checks. - fn run_multiqueue_qcow2_test(image_config: QcowTestImageConfig, test_fn: F) + fn run_multiqueue_qcow2_test(image_config: &QcowTestImageConfig, test_fn: F) where F: FnOnce(&Guest) + std::panic::UnwindSafe, { @@ -3793,7 +3793,7 @@ mod common_parallel { let test_image_path = guest.tmp_dir.as_path().join("test.qcow2"); // Create test image based on configuration and capture backing checksum if applicable - let initial_backing_checksum = match image_config { + let initial_backing_checksum = match *image_config { QcowTestImageConfig::Simple(size) => { Command::new("qemu-img") .arg("create") @@ -3876,7 +3876,7 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2_multiqueue_writes() { - run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + run_multiqueue_qcow2_test(&QcowTestImageConfig::Simple("256M"), |guest| { assert_eq!( guest .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l") @@ -3946,7 +3946,7 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2_multiqueue_mixed_rw() { - run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("512M"), |guest| { + run_multiqueue_qcow2_test(&QcowTestImageConfig::Simple("512M"), |guest| { guest .ssh_command("sudo mkfs.ext4 -F /dev/vdc") .expect("Failed to format disk"); @@ -3997,7 +3997,7 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2_multiqueue_backing() { - run_multiqueue_qcow2_test(QcowTestImageConfig::WithBacking, |guest| { + run_multiqueue_qcow2_test(&QcowTestImageConfig::WithBacking, |guest| { guest .ssh_command("sudo mkfs.ext4 -F /dev/vdc") .expect("Failed to format disk"); @@ -4041,7 +4041,7 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2_multiqueue_random_4k() { - run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + run_multiqueue_qcow2_test(&QcowTestImageConfig::Simple("256M"), |guest| { guest .ssh_command( "for i in $(seq 1 8); do \ @@ -4071,7 +4071,7 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2_multiqueue_fsync() { - run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + run_multiqueue_qcow2_test(&QcowTestImageConfig::Simple("256M"), |guest| { guest .ssh_command("sudo mkfs.ext4 -F /dev/vdc") .expect("Failed to format disk"); @@ -4118,7 +4118,7 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2_multiqueue_metadata() { - run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + run_multiqueue_qcow2_test(&QcowTestImageConfig::Simple("256M"), |guest| { guest .ssh_command("sudo mkfs.ext4 -F /dev/vdc") .expect("Failed to format disk"); @@ -4197,7 +4197,7 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2_multiqueue_discard_mount() { - run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("256M"), |guest| { + run_multiqueue_qcow2_test(&QcowTestImageConfig::Simple("256M"), |guest| { guest .ssh_command("sudo mkfs.ext4 -F /dev/vdc") .expect("Failed to format disk"); @@ -4260,7 +4260,7 @@ mod common_parallel { } #[test] fn test_virtio_block_qcow2_multiqueue_wide_writes() { - run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("1G"), |guest| { + run_multiqueue_qcow2_test(&QcowTestImageConfig::Simple("1G"), |guest| { // Scattered write pattern - write to widely separated offsets in parallel. // This should initiate many L2 table allocations simultaneously across different queues. guest @@ -4300,7 +4300,7 @@ mod common_parallel { #[test] fn test_virtio_block_qcow2_multiqueue_discard_stress() { - run_multiqueue_qcow2_test(QcowTestImageConfig::Simple("512M"), |guest| { + run_multiqueue_qcow2_test(&QcowTestImageConfig::Simple("512M"), |guest| { guest .ssh_command("sudo mkfs.ext4 -F /dev/vdc") .expect("Failed to format disk"); From 0da63a4507995ddf73f261e54837e970b5df4da1 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 16 Mar 2026 02:53:39 -0700 Subject: [PATCH 0752/1893] 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 --- cloud-hypervisor/tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 8841b00aea..fb1049100a 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 // -#![cfg(devcli_testenv)] +#![cfg(any(devcli_testenv, clippy))] #![allow(clippy::undocumented_unsafe_blocks)] // When enabling the `mshv` feature, we skip quite some tests and // hence have known dead-code. This annotation silences dead-code From f1875668e0f1cc2cda99ca54f8bb6c488ea65bdc Mon Sep 17 00:00:00 2001 From: Souradeep Date: Thu, 12 Mar 2026 11:59:03 +0000 Subject: [PATCH 0753/1893] 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 --- scripts/run_integration_tests_aarch64.sh | 4 ++-- scripts/{sha1sums-aarch64 => sha1sums-aarch64-common} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename scripts/{sha1sums-aarch64 => sha1sums-aarch64-common} (100%) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 68500028bc..2489d7892a 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -26,7 +26,7 @@ build_virtiofsd() { } update_workloads() { - cp scripts/sha1sums-aarch64 "$WORKLOADS_DIR" + cp scripts/sha1sums-aarch64-common "$WORKLOADS_DIR" FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-arm64-custom-20210929-0.raw" FOCAL_OS_RAW_IMAGE_DOWNLOAD_URL="https://ch-images.azureedge.net/$FOCAL_OS_RAW_IMAGE_NAME" @@ -138,7 +138,7 @@ update_workloads() { pushd "$WORKLOADS_DIR" || exit - if ! sha1sum sha1sums-aarch64 --check; then + if ! sha1sum sha1sums-aarch64-common --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi diff --git a/scripts/sha1sums-aarch64 b/scripts/sha1sums-aarch64-common similarity index 100% rename from scripts/sha1sums-aarch64 rename to scripts/sha1sums-aarch64-common From 1539b195d12c23d93ddd9f8ed4c347da14745a8b Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Mon, 16 Mar 2026 17:44:25 +0100 Subject: [PATCH 0754/1893] 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 --- virtio-devices/src/vhost_user/generic_vhost_user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index b8af44d75d..b90c6c079d 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -121,7 +121,7 @@ since the backend only supports {backend_num_queues}\n", ); return Err(Error::BadQueueNum); } - // Create virtio-vhost-user device configuration. + ( acked_features, // If part of the available features that have been acked, the From a7fefb63dd58687afcefe066e00baade1ef5db55 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 16 Mar 2026 17:02:39 +0100 Subject: [PATCH 0755/1893] 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 --- virtio-devices/src/block.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 1668565340..af6d638dd0 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -777,10 +777,12 @@ impl Block { // - Always advertise WRITE_ZEROES // - Advertise DISCARD only if sparse=true OR format supports marking // clusters as zero without deallocating + let mut discard_supported = false; if disk_image.supports_sparse_operations() { avail_features |= 1u64 << VIRTIO_BLK_F_WRITE_ZEROES; if sparse || disk_image.supports_zero_flag() { avail_features |= 1u64 << VIRTIO_BLK_F_DISCARD; + discard_supported = true; } } else if sparse { warn!("sparse=on requested but backend does not support sparse operations"); @@ -823,6 +825,17 @@ impl Block { ..Default::default() }; + if avail_features & (1u64 << VIRTIO_BLK_F_WRITE_ZEROES) != 0 { + config.max_write_zeroes_sectors = u32::MAX; + config.max_write_zeroes_seg = 1; + config.write_zeroes_may_unmap = if discard_supported { 1 } else { 0 }; + } + if avail_features & (1u64 << VIRTIO_BLK_F_DISCARD) != 0 { + config.max_discard_sectors = u32::MAX; + config.max_discard_seg = 1; + config.discard_sector_alignment = 1; + } + if num_queues > 1 { avail_features |= 1u64 << VIRTIO_BLK_F_MQ; config.num_queues = num_queues as u16; From 083892b2e22d2a79cb92656e22aa259854718940 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 16 Mar 2026 17:13:52 +0100 Subject: [PATCH 0756/1893] 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 --- cloud-hypervisor/tests/integration.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index fb1049100a..7218cbb393 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -11844,7 +11844,7 @@ mod windows { .args(["--kernel", edk2_path().to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks_sparse_off() + .default_disks() .default_net() .capture_output() .spawn() @@ -11893,7 +11893,7 @@ mod windows { .args([ "--disk", format!( - "path={},num_queues=4,sparse=off", + "path={},num_queues=4", windows_guest .guest() .disk_config @@ -11961,7 +11961,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks_sparse_off() + .default_disks() .default_net() .capture_output() .spawn() @@ -12050,7 +12050,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks_sparse_off() + .default_disks() .default_net() .capture_output() .spawn() @@ -12125,7 +12125,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks_sparse_off() + .default_disks() .default_net() .capture_output() .spawn() @@ -12199,7 +12199,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks_sparse_off() + .default_disks() .default_net() .capture_output() .spawn() @@ -12273,7 +12273,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks_sparse_off() + .default_disks() .default_net() .capture_output() .spawn() @@ -12369,7 +12369,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks_sparse_off() + .default_disks() .default_net() .capture_output() .spawn() @@ -12501,7 +12501,7 @@ mod windows { .args(["--kernel", ovmf_path.to_str().unwrap()]) .args(["--serial", "tty"]) .args(["--console", "off"]) - .default_disks_sparse_off() + .default_disks() // The multi net dev config is borrowed from test_multiple_network_interfaces .args([ "--net", From ad3179fe11502a0c2fbb554b6b305c72f2f5bfaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 00:07:55 +0000 Subject: [PATCH 0757/1893] 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] --- Cargo.lock | 143 +++++++++++++++++++++++++-------------------- Cargo.toml | 4 +- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 107 +++++++++++++++++---------------- vmm/Cargo.toml | 4 +- 5 files changed, 138 insertions(+), 122 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1833fda938..083f421012 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,7 +42,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", - "anstyle-parse", + "anstyle-parse 0.2.7", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse 1.0.0", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -52,9 +67,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -65,13 +80,22 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + [[package]] name = "anstyle-query" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -82,7 +106,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -370,9 +394,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -399,20 +423,20 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "clap_lex", "strsim", @@ -421,9 +445,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cloud-hypervisor" @@ -459,9 +483,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "concat-idents" @@ -517,9 +541,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -527,11 +551,10 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", @@ -541,9 +564,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", @@ -618,7 +641,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -670,7 +693,7 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ - "anstream", + "anstream 0.6.21", "anstyle", "env_filter", "jiff", @@ -700,7 +723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -778,12 +801,6 @@ dependencies = [ "spin", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.1.5" @@ -893,23 +910,23 @@ dependencies = [ [[package]] name = "gdbstub" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf845b08f7c2ef3b5ad19f80779d43ae20d278652b91bb80adda65baf2d8ed6" +checksum = "5bafc7e33650ab9f05dcc16325f05d56b8d10393114e31a19a353b86fa60cfe7" dependencies = [ "bitflags 2.11.0", "cfg-if", "log", "managed", "num-traits", - "paste", + "pastey", ] [[package]] name = "gdbstub_arch" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22dde0e1b68787036ccedd0b1ff6f953527a0e807e571fbe898975203027278f" +checksum = "6c02bfe7bd65f42bcda751456869dfa1eb2bd1c36e309b9ec27f4888d41cf258" dependencies = [ "gdbstub", "num-traits", @@ -1255,9 +1272,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.24" +version = "1.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" +checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" dependencies = [ "cc", "libc", @@ -1413,9 +1430,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -1423,9 +1440,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1444,9 +1461,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1485,9 +1502,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -1549,10 +1566,10 @@ dependencies = [ ] [[package]] -name = "paste" -version = "1.0.15" +name = "pastey" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" [[package]] name = "pci" @@ -1723,9 +1740,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] @@ -1895,7 +1912,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -1981,9 +1998,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "serde_core", "serde_with_macros", @@ -1991,9 +2008,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ "darling", "proc-macro2", @@ -2095,15 +2112,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2262,13 +2279,13 @@ dependencies = [ [[package]] name = "uds_windows" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4c4ad78e8a..b8fc54d9e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,14 +78,14 @@ igvm_defs = "0.4.0" # serde crates serde = "1.0.228" serde_json = "1.0.149" -serde_with = { version = "3.17.0", default-features = false } +serde_with = { version = "3.18.0", default-features = false } # other crates anyhow = "1.0.102" bitflags = "2.11.0" byteorder = "1.5.0" cfg-if = "1.0.4" -clap = "4.5.60" +clap = "4.6.0" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.8" diff --git a/devices/Cargo.toml b/devices/Cargo.toml index af4d7b73ce..516f41e834 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -21,7 +21,7 @@ linux-loader = { workspace = true, features = [ "pe", ], optional = true } log = { workspace = true } -num_enum = "0.7.5" +num_enum = "0.7.6" pci = { path = "../pci" } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 5d5f5e7a45..be0380751f 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -19,9 +19,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -34,15 +34,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -173,9 +173,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -202,18 +202,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -223,9 +223,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cloud-hypervisor-fuzz" @@ -255,9 +255,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "concat-idents" @@ -298,9 +298,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -308,11 +308,10 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", @@ -322,9 +321,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", @@ -468,12 +467,6 @@ dependencies = [ "spin", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.1.5" @@ -494,23 +487,23 @@ checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "gdbstub" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf845b08f7c2ef3b5ad19f80779d43ae20d278652b91bb80adda65baf2d8ed6" +checksum = "5bafc7e33650ab9f05dcc16325f05d56b8d10393114e31a19a353b86fa60cfe7" dependencies = [ "bitflags 2.11.0", "cfg-if", "log", "managed", "num-traits", - "paste", + "pastey", ] [[package]] name = "gdbstub_arch" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22dde0e1b68787036ccedd0b1ff6f953527a0e807e571fbe898975203027278f" +checksum = "6c02bfe7bd65f42bcda751456869dfa1eb2bd1c36e309b9ec27f4888d41cf258" dependencies = [ "gdbstub", "num-traits", @@ -537,19 +530,19 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core", "wasip2", "wasip3", @@ -820,7 +813,7 @@ name = "net_util" version = "0.1.0" dependencies = [ "epoll", - "getrandom 0.4.1", + "getrandom 0.4.2", "libc", "log", "net_gen", @@ -845,9 +838,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -855,9 +848,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -867,9 +860,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -905,10 +898,10 @@ dependencies = [ ] [[package]] -name = "paste" -version = "1.0.15" +name = "pastey" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" [[package]] name = "pci" @@ -980,6 +973,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.10.0" @@ -987,7 +986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" dependencies = [ "chacha20", - "getrandom 0.4.1", + "getrandom 0.4.2", "rand_core", ] @@ -1091,9 +1090,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "serde_core", "serde_with_macros", @@ -1101,9 +1100,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ "darling", "proc-macro2", @@ -1295,7 +1294,7 @@ version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "rand", "wasm-bindgen", diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 35fe314299..ab0278e6d1 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -49,8 +49,8 @@ epoll = { workspace = true } event_monitor = { path = "../event_monitor" } flume = { workspace = true } futures = { version = "0.3.32", optional = true } -gdbstub = { version = "0.7.9", optional = true } -gdbstub_arch = { version = "0.3.2", optional = true } +gdbstub = { version = "0.7.10", optional = true } +gdbstub_arch = { version = "0.3.3", optional = true } hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } igvm = { workspace = true, optional = true } From ef91fc64e577cb7621ebeef9e8695ad3279c5e38 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 15 Mar 2026 06:35:43 -0700 Subject: [PATCH 0758/1893] 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 --- virtio-devices/src/vhost_user/mod.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 158da3d800..0dad19acea 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -302,6 +302,7 @@ pub struct VhostUserCommon { pub vu_num_queues: usize, pub migration_started: bool, pub server: bool, + pub interrupt_cb: Option>, } impl VhostUserCommon { @@ -345,6 +346,8 @@ impl VhostUserCommon { ) .map_err(ActivateError::VhostUserSetup)?; + self.interrupt_cb = Some(interrupt_cb.clone()); + Ok(VhostUserEpollHandler { vu: vu.clone(), mem, @@ -425,10 +428,16 @@ impl VhostUserCommon { if let Some(vu) = &self.vu { vu.lock().unwrap().resume_vhost_user().map_err(|e| { MigratableError::Resume(anyhow!("Error resuming vhost-user backend: {e:?}")) - }) - } else { - Ok(()) + })?; + } + if let Some(interrupt_cb) = &self.interrupt_cb { + for i in 0..self.vu_num_queues { + interrupt_cb + .trigger(crate::VirtioInterruptType::Queue(i as u16)) + .ok(); + } } + Ok(()) } pub fn snapshot<'a, T>(&mut self, state: &T) -> std::result::Result From c42cc478febdeee2135238a159fb4dcd26563d5d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 14:34:07 +0100 Subject: [PATCH 0759/1893] 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 --- performance-metrics/src/main.rs | 37 +++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index bef0b74ab1..d1228153da 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -1338,24 +1338,35 @@ fn main() { .unwrap_or_default(), }); - init_tests(&overrides); + // Determine which tests will actually run. + let tests_to_run: Vec<&&PerformanceTest> = test_list + .iter() + .filter(|t| test_filter.is_empty() || test_filter.iter().any(|&s| t.name.contains(s))) + .collect(); - for test in test_list.iter() { - if test_filter.is_empty() || test_filter.iter().any(|&s| test.name.contains(s)) { - settle_host(); - match run_test_with_timeout(test, &overrides) { - Ok(r) => { - metrics_report.results.push(r); - } - Err(e) => { - eprintln!("Aborting test due to error: '{e:?}'"); - std::process::exit(1); - } + // Skip heavy VM level init/cleanup when only micro benchmarks are selected. + let needs_vm_tests = tests_to_run.iter().any(|t| !t.name.starts_with("micro_")); + + if needs_vm_tests { + init_tests(&overrides); + } + + for test in tests_to_run { + settle_host(); + match run_test_with_timeout(test, &overrides) { + Ok(r) => { + metrics_report.results.push(r); + } + Err(e) => { + eprintln!("Aborting test due to error: '{e:?}'"); + std::process::exit(1); } } } - cleanup_tests(); + if needs_vm_tests { + cleanup_tests(); + } let mut report_file: Box = if let Some(file) = cmd_arguments.get_one::("report-file") { From b12620cf2545a97b930b44f505f05890eef77baf Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 15 Mar 2026 11:47:37 +0100 Subject: [PATCH 0760/1893] 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 --- performance-metrics/src/main.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index d1228153da..cdc06fe108 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -179,6 +179,7 @@ pub struct PerformanceTestControl { net_control: Option<(bool, bool)>, // First bool is for RX(true)/TX(false), second bool is for bandwidth or PPS block_control: Option, num_boot_vcpus: Option, + num_ops: Option, // Workload size for micro benchmarks } impl fmt::Display for PerformanceTestControl { @@ -203,6 +204,9 @@ impl fmt::Display for PerformanceTestControl { o.fio_ops, o.bandwidth, o.test_file ); } + if let Some(o) = self.num_ops { + output = format!("{output}, num_ops = {o}"); + } write!(f, "{output}") } @@ -219,6 +223,7 @@ impl PerformanceTestControl { net_control: None, block_control: None, num_boot_vcpus: Some(1), + num_ops: None, } } } @@ -235,6 +240,13 @@ struct PerformanceTest { impl PerformanceTest { pub fn run(&self, overrides: &PerformanceTestOverrides) -> PerformanceTestResult { + if self.control.num_ops.is_some() && !self.name.starts_with("micro_") { + eprintln!( + "Warning: num_ops is set on '{}' but has no effect on non micro benchmarks", + self.name + ); + } + // Run warmup iterations if configured (results discarded) for _ in 0..self.control.warmup_iterations { if let Some(test_timeout) = overrides.test_timeout { From be6f63a740c857c1f6c9b5b569eedfa9e4fdc7c5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 15:28:14 +0100 Subject: [PATCH 0761/1893] 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 --- Cargo.lock | 1 + performance-metrics/Cargo.toml | 1 + performance-metrics/src/main.rs | 1 + performance-metrics/src/util.rs | 36 +++++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+) create mode 100644 performance-metrics/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 083f421012..af88d4db99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1602,6 +1602,7 @@ dependencies = [ "serde_json", "test_infra", "thiserror 2.0.18", + "vmm-sys-util", ] [[package]] diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index 472f1159b3..60be8f45a6 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -11,6 +11,7 @@ serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } test_infra = { path = "../test_infra" } thiserror = { workspace = true } +vmm-sys-util = { workspace = true } [lints] workspace = true diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index cdc06fe108..bcfee6d974 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -5,6 +5,7 @@ // Custom harness to run performance tests mod performance_tests; +mod util; use std::process::Command; use std::sync::Arc; diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs new file mode 100644 index 0000000000..dcc2257501 --- /dev/null +++ b/performance-metrics/src/util.rs @@ -0,0 +1,36 @@ +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +//! Shared benchmark helpers. + +use std::io::ErrorKind; +use std::thread; +use std::time::Duration; + +use vmm_sys_util::eventfd::EventFd; +use vmm_sys_util::tempfile::TempFile; + +pub const BLOCK_SIZE: u64 = 4096; + +/// Create a temporary file pre sized to hold `num_blocks` blocks. +pub fn sized_tempfile(num_blocks: usize) -> TempFile { + let tmp = TempFile::new().expect("failed to create tempfile"); + tmp.as_file() + .set_len(BLOCK_SIZE * num_blocks as u64) + .expect("failed to set file length"); + tmp +} + +/// Spin and wait until the given eventfd becomes readable. +pub fn wait_for_eventfd(notifier: &EventFd) { + loop { + match notifier.read() { + Ok(_) => return, + Err(e) if e.kind() == ErrorKind::WouldBlock => { + thread::sleep(Duration::from_micros(50)); + } + Err(e) => panic!("eventfd read failed: {e}"), + } + } +} From 00957fa9dbe7004eafefecb805e1e775e5aad36f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 15:28:29 +0100 Subject: [PATCH 0762/1893] 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 --- Cargo.lock | 2 + performance-metrics/Cargo.toml | 2 + performance-metrics/src/main.rs | 31 +++++++++++- performance-metrics/src/micro_bench_block.rs | 53 ++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 performance-metrics/src/micro_bench_block.rs diff --git a/Cargo.lock b/Cargo.lock index af88d4db99..7fa87d6512 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1596,8 +1596,10 @@ dependencies = [ name = "performance-metrics" version = "0.1.0" dependencies = [ + "block", "clap", "dirs", + "libc", "serde", "serde_json", "test_infra", diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index 60be8f45a6..516ea9e0a2 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -5,8 +5,10 @@ name = "performance-metrics" version = "0.1.0" [dependencies] +block = { path = "../block" } clap = { workspace = true, features = ["wrap_help"] } dirs = { workspace = true } +libc = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } test_infra = { path = "../test_infra" } diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index bcfee6d974..c648d189c7 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -4,6 +4,7 @@ // // Custom harness to run performance tests +mod micro_bench_block; mod performance_tests; mod util; @@ -336,6 +337,10 @@ mod adjuster { v * 1000.0 } + pub fn s_to_us(v: f64) -> f64 { + v * 1_000_000.0 + } + pub fn bps_to_gbps(v: f64) -> f64 { v / (1_000_000_000_f64) } @@ -346,7 +351,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 60] = [ +const TEST_LIST: [PerformanceTest; 62] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1197,6 +1202,30 @@ const TEST_LIST: [PerformanceTest; 60] = [ }, unit_adjuster: adjuster::Bps_to_MiBps, }, + PerformanceTest { + name: "micro_block_raw_aio_drain_128_us", + func_ptr: micro_bench_block::micro_bench_aio_drain, + control: PerformanceTestControl { + test_timeout: 5, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_raw_aio_drain_256_us", + func_ptr: micro_bench_block::micro_bench_aio_drain, + control: PerformanceTestControl { + test_timeout: 5, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs new file mode 100644 index 0000000000..6dc51af658 --- /dev/null +++ b/performance-metrics/src/micro_bench_block.rs @@ -0,0 +1,53 @@ +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +//! In process micro benchmarks for block layer internals. +//! +//! These run without booting a VM and measure hot path operations +//! (e.g. AIO completion draining) at the syscall level. + +use std::os::unix::io::AsRawFd; +use std::time::Instant; + +use block::async_io::AsyncIo; +use block::raw_async_aio::RawFileAsyncAio; + +use crate::PerformanceTestControl; +use crate::util::{self, BLOCK_SIZE}; + +/// Submit num_ops AIO writes, wait for them all to land, then time +/// how long it takes to drain every completion via next_completed_request(). +/// +/// Returns the drain wall clock time in seconds. +pub fn micro_bench_aio_drain(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let tmp = util::sized_tempfile(num_ops); + let fd = tmp.as_file().as_raw_fd(); + let mut aio = RawFileAsyncAio::new(fd, num_ops as u32).expect("failed to create AIO context"); + + let buf = vec![0xA5u8; BLOCK_SIZE as usize]; + + // Submit all writes. + for i in 0..num_ops { + let iovec = libc::iovec { + iov_base: buf.as_ptr() as *mut _, + iov_len: buf.len(), + }; + aio.write_vectored((i as u64 * BLOCK_SIZE) as libc::off_t, &[iovec], i as u64) + .expect("write_vectored failed"); + } + + // Wait until the eventfd signals that completions are available. + util::wait_for_eventfd(aio.notifier()); + + // Drain all completions and measure. + let start = Instant::now(); + let mut drained = 0usize; + while drained < num_ops { + if aio.next_completed_request().is_some() { + drained += 1; + } + } + start.elapsed().as_secs_f64() +} From 0f7dc514ba4d13e29b51db491d7447152e74a4f9 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 12 Mar 2026 23:08:23 -0400 Subject: [PATCH 0763/1893] 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 --- vmm/src/interrupt.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/vmm/src/interrupt.rs b/vmm/src/interrupt.rs index e42ba2f76b..70a58dfb12 100644 --- a/vmm/src/interrupt.rs +++ b/vmm/src/interrupt.rs @@ -5,7 +5,6 @@ use std::collections::HashMap; use std::io; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use devices::interrupt_controller::InterruptController; @@ -23,7 +22,7 @@ pub type Result = std::io::Result; struct InterruptRoute { gsi: u32, irq_fd: EventFd, - registered: AtomicBool, + registered: bool, } impl InterruptRoute { @@ -36,39 +35,39 @@ impl InterruptRoute { Ok(InterruptRoute { gsi, irq_fd, - registered: AtomicBool::new(false), + registered: false, }) } - pub fn enable(&self, vm: &dyn hypervisor::Vm) -> Result<()> { - if !self.registered.load(Ordering::Acquire) { + pub fn enable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { + if !self.registered { vm.register_irqfd(&self.irq_fd, self.gsi) .map_err(|e| io::Error::other(format!("Failed registering irq_fd: {e}")))?; // Update internals to track the irq_fd as "registered". - self.registered.store(true, Ordering::Release); + self.registered = true; } Ok(()) } - pub fn disable(&self, vm: &dyn hypervisor::Vm) -> Result<()> { - if self.registered.load(Ordering::Acquire) { + pub fn disable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { + if self.registered { vm.unregister_irqfd(&self.irq_fd, self.gsi) .map_err(|e| io::Error::other(format!("Failed unregistering irq_fd: {e}")))?; // Update internals to track the irq_fd as "unregistered". - self.registered.store(false, Ordering::Release); + self.registered = false; } Ok(()) } - pub fn trigger(&self) -> Result<()> { + pub fn trigger(&mut self) -> Result<()> { self.irq_fd.write(1) } - pub fn notifier(&self) -> Option { + pub fn notifier(&mut self) -> Option { Some( self.irq_fd .try_clone() @@ -85,7 +84,7 @@ pub struct RoutingEntry { pub struct MsiInterruptGroup { vm: Arc, gsi_msi_routes: Arc>>, - irq_routes: HashMap, + irq_routes: HashMap>, } impl MsiInterruptGroup { @@ -109,7 +108,7 @@ impl MsiInterruptGroup { fn new( vm: Arc, gsi_msi_routes: Arc>>, - irq_routes: HashMap, + irq_routes: HashMap>, ) -> Self { MsiInterruptGroup { vm, @@ -122,7 +121,7 @@ impl MsiInterruptGroup { impl InterruptSourceGroup for MsiInterruptGroup { fn enable(&self) -> Result<()> { for (_, route) in self.irq_routes.iter() { - route.enable(self.vm.as_ref())?; + route.lock().unwrap().enable(self.vm.as_ref())?; } Ok(()) @@ -130,7 +129,7 @@ impl InterruptSourceGroup for MsiInterruptGroup { fn disable(&self) -> Result<()> { for (_, route) in self.irq_routes.iter() { - route.disable(self.vm.as_ref())?; + route.lock().unwrap().disable(self.vm.as_ref())?; } Ok(()) @@ -138,7 +137,7 @@ impl InterruptSourceGroup for MsiInterruptGroup { fn trigger(&self, index: InterruptIndex) -> Result<()> { if let Some(route) = self.irq_routes.get(&index) { - return route.trigger(); + return route.lock().unwrap().trigger(); } Err(io::Error::other(format!( @@ -148,7 +147,7 @@ impl InterruptSourceGroup for MsiInterruptGroup { fn notifier(&self, index: InterruptIndex) -> Option { if let Some(route) = self.irq_routes.get(&index) { - return route.notifier(); + return route.lock().unwrap().notifier(); } None @@ -162,6 +161,7 @@ impl InterruptSourceGroup for MsiInterruptGroup { set_gsi: bool, ) -> Result<()> { if let Some(route) = self.irq_routes.get(&index) { + let mut route = route.lock().unwrap(); let entry = RoutingEntry { route: self.vm.make_routing_entry(route.gsi, &config), masked, @@ -293,10 +293,10 @@ impl InterruptManager for MsiInterruptManager { fn create_group(&self, config: Self::GroupConfig) -> Result> { let mut allocator = self.allocator.lock().unwrap(); - let mut irq_routes: HashMap = + let mut irq_routes: HashMap> = HashMap::with_capacity(config.count as usize); for i in config.base..config.base + config.count { - irq_routes.insert(i, InterruptRoute::new(&mut allocator)?); + irq_routes.insert(i, Mutex::new(InterruptRoute::new(&mut allocator)?)); } Ok(Arc::new(MsiInterruptGroup::new( From e1c40211ae977df51949e3d8967101059ef64d4c Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 12 Mar 2026 21:48:12 -0400 Subject: [PATCH 0764/1893] 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 --- Cargo.lock | 1 + fuzz/Cargo.lock | 1 + fuzz/fuzz_targets/balloon.rs | 9 +++++++++ fuzz/fuzz_targets/block.rs | 9 +++++++++ fuzz/fuzz_targets/console.rs | 9 +++++++++ fuzz/fuzz_targets/iommu.rs | 9 +++++++++ fuzz/fuzz_targets/mem.rs | 9 +++++++++ fuzz/fuzz_targets/net.rs | 9 +++++++++ fuzz/fuzz_targets/pmem.rs | 9 +++++++++ fuzz/fuzz_targets/rng.rs | 9 +++++++++ fuzz/fuzz_targets/vsock.rs | 9 +++++++++ fuzz/fuzz_targets/watchdog.rs | 9 +++++++++ virtio-devices/Cargo.toml | 1 + virtio-devices/src/device.rs | 6 ++++++ virtio-devices/src/transport/pci_device.rs | 9 +++++++++ virtio-devices/src/vsock/mod.rs | 9 +++++++++ 16 files changed, 117 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 7fa87d6512..1c02d2604f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2455,6 +2455,7 @@ dependencies = [ "byteorder", "epoll", "event_monitor", + "hypervisor", "libc", "log", "mshv-ioctls", diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index be0380751f..e03789eaf4 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1372,6 +1372,7 @@ dependencies = [ "byteorder", "epoll", "event_monitor", + "hypervisor", "libc", "log", "net_util", diff --git a/fuzz/fuzz_targets/balloon.rs b/fuzz/fuzz_targets/balloon.rs index 58b9b30582..69f0c07e84 100644 --- a/fuzz/fuzz_targets/balloon.rs +++ b/fuzz/fuzz_targets/balloon.rs @@ -119,6 +119,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } macro_rules! align { diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index 51007fe384..952011b55b 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -121,6 +121,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } fn setup_virt_queue(bytes: &[u8; QUEUE_DATA_SIZE]) -> Queue { diff --git a/fuzz/fuzz_targets/console.rs b/fuzz/fuzz_targets/console.rs index e27331ed01..a335a96027 100644 --- a/fuzz/fuzz_targets/console.rs +++ b/fuzz/fuzz_targets/console.rs @@ -148,6 +148,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } fn setup_virt_queues(bytes: &[&[u8; QUEUE_DATA_SIZE]], base_addr: u64) -> Vec { diff --git a/fuzz/fuzz_targets/iommu.rs b/fuzz/fuzz_targets/iommu.rs index a10640487f..11600a36a7 100644 --- a/fuzz/fuzz_targets/iommu.rs +++ b/fuzz/fuzz_targets/iommu.rs @@ -130,6 +130,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } fn setup_virt_queue(bytes: &[u8; QUEUE_DATA_SIZE]) -> Queue { diff --git a/fuzz/fuzz_targets/mem.rs b/fuzz/fuzz_targets/mem.rs index 73ec11b025..e430e195aa 100644 --- a/fuzz/fuzz_targets/mem.rs +++ b/fuzz/fuzz_targets/mem.rs @@ -125,6 +125,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } // Create a dummy virtio-mem device for fuzzing purpose only diff --git a/fuzz/fuzz_targets/net.rs b/fuzz/fuzz_targets/net.rs index df9a1dce5a..efc9605806 100644 --- a/fuzz/fuzz_targets/net.rs +++ b/fuzz/fuzz_targets/net.rs @@ -166,6 +166,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } fn setup_virt_queues(bytes: &[&[u8; QUEUE_DATA_SIZE]], base_addr: u64) -> Vec { diff --git a/fuzz/fuzz_targets/pmem.rs b/fuzz/fuzz_targets/pmem.rs index 0bd083a1c2..37eabf86cd 100644 --- a/fuzz/fuzz_targets/pmem.rs +++ b/fuzz/fuzz_targets/pmem.rs @@ -95,6 +95,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } // Create a dummy virtio-pmem device for fuzzing purpose only diff --git a/fuzz/fuzz_targets/rng.rs b/fuzz/fuzz_targets/rng.rs index 13548664a8..c3029f33b4 100644 --- a/fuzz/fuzz_targets/rng.rs +++ b/fuzz/fuzz_targets/rng.rs @@ -119,6 +119,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } fn setup_virt_queue(bytes: &[u8; QUEUE_DATA_SIZE]) -> Queue { diff --git a/fuzz/fuzz_targets/vsock.rs b/fuzz/fuzz_targets/vsock.rs index 33ebe78886..559f2ec138 100644 --- a/fuzz/fuzz_targets/vsock.rs +++ b/fuzz/fuzz_targets/vsock.rs @@ -128,6 +128,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } fn setup_virt_queue(bytes: &[u8; QUEUE_DATA_SIZE]) -> Queue { diff --git a/fuzz/fuzz_targets/watchdog.rs b/fuzz/fuzz_targets/watchdog.rs index 31361755df..60f4afab55 100644 --- a/fuzz/fuzz_targets/watchdog.rs +++ b/fuzz/fuzz_targets/watchdog.rs @@ -84,6 +84,15 @@ impl VirtioInterrupt for NoopVirtioInterrupt { fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } fn setup_virt_queue(bytes: &[u8; QUEUE_DATA_SIZE]) -> Queue { diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index 41b9da8e0a..d2658eeeca 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -17,6 +17,7 @@ block = { path = "../block" } byteorder = { workspace = true } epoll = { workspace = true } event_monitor = { path = "../event_monitor" } +hypervisor = { path = "../hypervisor" } libc = { workspace = true } log = { workspace = true } mshv-ioctls = { workspace = true, optional = true } diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 4b5cdaf03f..91b742a0bc 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -37,6 +37,12 @@ pub trait VirtioInterrupt: Send + Sync { fn notifier(&self, _int_type: VirtioInterruptType) -> Option { None } + fn set_notifier( + &self, + int_type: u32, + notifier: Option, + vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()>; } #[derive(Clone)] diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 7f049070d6..70e03d0287 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -914,6 +914,15 @@ impl VirtioInterrupt for VirtioInterruptMsix { self.interrupt_source_group .notifier(vector as InterruptIndex) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } impl PciDevice for VirtioPciDevice { diff --git a/virtio-devices/src/vsock/mod.rs b/virtio-devices/src/vsock/mod.rs index 7895587855..34561f5d46 100644 --- a/virtio-devices/src/vsock/mod.rs +++ b/virtio-devices/src/vsock/mod.rs @@ -188,6 +188,15 @@ pub mod unit_tests { ) -> std::result::Result<(), std::io::Error> { Ok(()) } + + fn set_notifier( + &self, + _interrupt: u32, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + unimplemented!() + } } pub struct TestBackend { From 9f62c33d0064b5687343c409753138b74aa5deab Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 12 Mar 2026 22:07:43 -0400 Subject: [PATCH 0765/1893] 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 --- vmm/src/interrupt.rs | 49 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/vmm/src/interrupt.rs b/vmm/src/interrupt.rs index 70a58dfb12..07727d1ad6 100644 --- a/vmm/src/interrupt.rs +++ b/vmm/src/interrupt.rs @@ -21,13 +21,16 @@ pub type Result = std::io::Result; struct InterruptRoute { gsi: u32, - irq_fd: EventFd, + irq_fd: Option, registered: bool, } impl InterruptRoute { pub fn new(allocator: &mut SystemAllocator) -> Result { - let irq_fd = EventFd::new(libc::EFD_NONBLOCK)?; + Self::new_with_fd(allocator, Some(EventFd::new(libc::EFD_NONBLOCK)?)) + } + + pub fn new_with_fd(allocator: &mut SystemAllocator, irq_fd: Option) -> Result { let gsi = allocator .allocate_gsi() .ok_or_else(|| io::Error::other("Failed allocating new GSI"))?; @@ -41,8 +44,10 @@ impl InterruptRoute { pub fn enable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { if !self.registered { - vm.register_irqfd(&self.irq_fd, self.gsi) - .map_err(|e| io::Error::other(format!("Failed registering irq_fd: {e}")))?; + if let Some(ref irq_fd) = self.irq_fd { + vm.register_irqfd(irq_fd, self.gsi) + .map_err(|e| io::Error::other(format!("Failed registering irq_fd: {e}")))?; + } // Update internals to track the irq_fd as "registered". self.registered = true; @@ -53,8 +58,10 @@ impl InterruptRoute { pub fn disable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { if self.registered { - vm.unregister_irqfd(&self.irq_fd, self.gsi) - .map_err(|e| io::Error::other(format!("Failed unregistering irq_fd: {e}")))?; + if let Some(ref irq_fd) = self.irq_fd { + vm.unregister_irqfd(irq_fd, self.gsi) + .map_err(|e| io::Error::other(format!("Failed unregistering irq_fd: {e}")))?; + } // Update internals to track the irq_fd as "unregistered". self.registered = false; @@ -64,16 +71,44 @@ impl InterruptRoute { } pub fn trigger(&mut self) -> Result<()> { - self.irq_fd.write(1) + match self.irq_fd { + Some(ref fd) => fd.write(1), + None => Ok(()), + } } pub fn notifier(&mut self) -> Option { Some( self.irq_fd + .as_ref()? .try_clone() .expect("Failed cloning interrupt's EventFd"), ) } + + #[allow(dead_code)] + pub fn set_notifier( + &mut self, + eventfd: Option, + vm: &dyn hypervisor::Vm, + ) -> Result<()> { + let old_irqfd = core::mem::replace(&mut self.irq_fd, eventfd); + if self.registered { + if let Some(ref irq_fd) = self.irq_fd { + vm.register_irqfd(irq_fd, self.gsi) + .map_err(|e| io::Error::other(format!("Failed registering irq_fd: {e}")))? + } + // If the irqfd cannot be unregistered, what to do? Spin? + // Returning an error isn't helpful as the new irqfd is already registered. + if let Some(old_irq_fd) = old_irqfd { + match vm.unregister_irqfd(&old_irq_fd, self.gsi) { + Ok(()) => {} + Err(e) => log::warn!("Failed unregistering old irqfd: {e}"), + } + } + } + Ok(()) + } } pub struct RoutingEntry { From d609410b8ba5c1e44ec10697bb48aebddc36b47b Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 11 Feb 2026 20:08:45 -0500 Subject: [PATCH 0766/1893] 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 --- pci/src/lib.rs | 5 +- pci/src/msix.rs | 62 ++++++++++++++++++++-- pci/src/vfio.rs | 4 +- virtio-devices/src/device.rs | 12 +++++ virtio-devices/src/transport/pci_device.rs | 52 ++++++++++-------- vm-device/src/interrupt/mod.rs | 42 ++++++++++++++- vmm/src/device_manager.rs | 4 +- vmm/src/interrupt.rs | 46 +++++++++++++++- 8 files changed, 194 insertions(+), 33 deletions(-) diff --git a/pci/src/lib.rs b/pci/src/lib.rs index 5ab87cf19d..17c3ab7235 100644 --- a/pci/src/lib.rs +++ b/pci/src/lib.rs @@ -32,7 +32,10 @@ pub use self::device::{ BarReprogrammingParams, DeviceRelocation, Error as PciDeviceError, PciDevice, }; pub use self::msi::{MsiCap, MsiConfig, msi_num_enabled_vectors}; -pub use self::msix::{MSIX_CONFIG_ID, MSIX_TABLE_ENTRY_SIZE, MsixCap, MsixConfig, MsixTableEntry}; +pub use self::msix::{ + MSIX_CONFIG_ID, MSIX_TABLE_ENTRY_SIZE, MaybeMutInterruptSourceGroup, MsixCap, MsixConfig, + MsixTableEntry, +}; pub use self::vfio::{MmioRegion, VfioDmaMapping, VfioPciDevice, VfioPciError}; pub use self::vfio_user::{VfioUserDmaMapping, VfioUserPciDevice, VfioUserPciDeviceError}; diff --git a/pci/src/msix.rs b/pci/src/msix.rs index 9bc5e63f3a..49b379b02a 100644 --- a/pci/src/msix.rs +++ b/pci/src/msix.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause // -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::{io, result}; use byteorder::{ByteOrder, LittleEndian}; @@ -15,6 +15,7 @@ use vm_device::interrupt::{ }; use vm_memory::ByteValued; use vm_migration::{MigratableError, Pausable, Snapshot, Snapshottable}; +use vmm_sys_util::eventfd::EventFd; use crate::{PciCapability, PciCapabilityId}; @@ -72,11 +73,66 @@ pub struct MsixConfigState { enabled: bool, } +#[derive(Clone)] +pub enum MaybeMutInterruptSourceGroup { + Immutable(Arc), + Mutable(Arc>), +} + +macro_rules! impl_method { + ($( + fn $i: ident(&self $(,$index:ident : $InterruptIndex:ty)*$(,)?) -> $r: ty; + )*) => { + $( + fn $i(&self $(,$index: $InterruptIndex)*) -> $r { + match self { + Self::Immutable(source) => source.$i($($index),*), + Self::Mutable(source) => source.lock().unwrap().$i($($index),*), + } + } + )* + }; +} + +impl InterruptSourceGroup for MaybeMutInterruptSourceGroup { + impl_method! { + fn trigger(&self, index: InterruptIndex) -> vm_device::interrupt::Result<()>; + + fn notifier(&self, index: InterruptIndex) -> Option; + + fn update( + &self, + index: InterruptIndex, + config: InterruptSourceConfig, + masked: bool, + set_gsi: bool, + ) -> vm_device::interrupt::Result<()>; + + fn set_gsi(&self) -> vm_device::interrupt::Result<()>; + } +} + +impl MaybeMutInterruptSourceGroup { + pub fn set_notifier( + &self, + index: InterruptIndex, + eventfd: Option, + vm: &dyn hypervisor::Vm, + ) -> std::io::Result<()> { + match self { + Self::Immutable(_) => panic!( + "Attempted to set a notifier of an immutable source. You must mark your device as needing a mutable source by having sets_irqfd() return true." + ), + Self::Mutable(source) => source.lock().unwrap().set_notifier(index, eventfd, vm), + } + } +} + pub struct MsixConfig { pub table_entries: Vec, pub pba_entries: Vec, pub devid: u32, - interrupt_source_group: Arc, + interrupt_source_group: MaybeMutInterruptSourceGroup, masked: bool, enabled: bool, } @@ -84,7 +140,7 @@ pub struct MsixConfig { impl MsixConfig { pub fn new( msix_vectors: u16, - interrupt_source_group: Arc, + interrupt_source_group: MaybeMutInterruptSourceGroup, devid: u32, state: Option, ) -> result::Result { diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 9e8e7e3163..e46e276aa6 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -37,7 +37,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::mmap::MmapRegion; use crate::msi::{MSI_CONFIG_ID, MsiConfigState}; -use crate::msix::MsixConfigState; +use crate::msix::{MaybeMutInterruptSourceGroup, MsixConfigState}; use crate::{ BarReprogrammingParams, MSIX_CONFIG_ID, MSIX_TABLE_ENTRY_SIZE, MsiCap, MsiConfig, MsixCap, MsixConfig, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, @@ -863,7 +863,7 @@ impl VfioCommon { let msix_config = MsixConfig::new( msix_cap.table_size(), - interrupt_source_group.clone(), + MaybeMutInterruptSourceGroup::Immutable(interrupt_source_group.clone()), bdf.into(), state, ) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 91b742a0bc..f0673f5614 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -80,6 +80,18 @@ pub trait VirtioDevice: Send { /// The maximum size of each queue that this device supports. fn queue_max_sizes(&self) -> &[u16]; + /// Whether the device needs to register extra irqfds at runtime + /// from external sources. + /// The default is false. If this is true, locking is required for + /// most operations involving interrupts (but not for sending) + /// interrupts from external irqfds). + /// + /// If the device claims to not need to register irqfds, but + /// attempts to do so, a panic will ensue. + fn interrupt_source_mutable(&self) -> bool { + false + } + /// The set of feature bits that this device supports. fn features(&self) -> u64 { 0 diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 70e03d0287..3e2a96ccd9 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -17,9 +17,10 @@ use anyhow::anyhow; use libc::EFD_NONBLOCK; use log::{error, info}; use pci::{ - BarReprogrammingParams, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, - PciCapability, PciCapabilityId, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, - PciHeaderType, PciMassStorageSubclass, PciNetworkControllerSubclass, PciSubclass, + BarReprogrammingParams, MaybeMutInterruptSourceGroup, MsixCap, MsixConfig, PciBarConfiguration, + PciBarRegionType, PciCapability, PciCapabilityId, PciClassCode, PciConfiguration, PciDevice, + PciDeviceError, PciHeaderType, PciMassStorageSubclass, PciNetworkControllerSubclass, + PciSubclass, }; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -359,7 +360,7 @@ pub struct VirtioPciDevice { // PCI interrupts. interrupt_status: Arc, virtio_interrupt: Option>, - interrupt_source_group: Arc, + interrupt_source_group: MaybeMutInterruptSourceGroup, // virtio queues queues: Vec, @@ -433,17 +434,26 @@ impl VirtioPciDevice { let pci_device_id = VIRTIO_PCI_DEVICE_ID_BASE + locked_device.device_type() as u16; - let interrupt_source_group = interrupt_manager - .create_group(MsiIrqGroupConfig { + let interrupt_source_group: MaybeMutInterruptSourceGroup = { + let config = MsiIrqGroupConfig { base: 0, count: msix_num as InterruptIndex, + }; + (if locked_device.interrupt_source_mutable() { + interrupt_manager + .create_group_mut(config) + .map(MaybeMutInterruptSourceGroup::Mutable) + } else { + interrupt_manager + .create_group(config) + .map(MaybeMutInterruptSourceGroup::Immutable) }) .map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( "Failed creating MSI interrupt group: {e}" )) - })?; - + })? + }; let msix_state = vm_migration::state_from_id(snapshot, pci::MSIX_CONFIG_ID).map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( @@ -452,14 +462,11 @@ impl VirtioPciDevice { })?; let (msix_config, msix_config_clone) = if msix_num > 0 { + let interrupt_source_group: MaybeMutInterruptSourceGroup = + interrupt_source_group.clone(); let msix_config = Arc::new(Mutex::new( - MsixConfig::new( - msix_num, - interrupt_source_group.clone(), - pci_device_bdf, - msix_state, - ) - .unwrap(), + MsixConfig::new(msix_num, interrupt_source_group, pci_device_bdf, msix_state) + .unwrap(), )); let msix_config_clone = msix_config.clone(); (Some(msix_config), Some(msix_config_clone)) @@ -598,7 +605,7 @@ impl VirtioPciDevice { memory, settings_bar: 0, use_64bit_bar, - interrupt_source_group, + interrupt_source_group: interrupt_source_group.clone(), cap_pci_cfg_info, bar_regions: vec![], activate_evt, @@ -855,7 +862,7 @@ pub struct VirtioInterruptMsix { msix_config: Arc>, config_vector: Arc, queues_vectors: Arc>>, - interrupt_source_group: Arc, + interrupt_source_group: MaybeMutInterruptSourceGroup, } impl VirtioInterruptMsix { @@ -863,7 +870,7 @@ impl VirtioInterruptMsix { msix_config: Arc>, config_vector: Arc, queues_vectors: Arc>>, - interrupt_source_group: Arc, + interrupt_source_group: MaybeMutInterruptSourceGroup, ) -> Self { VirtioInterruptMsix { msix_config, @@ -917,11 +924,12 @@ impl VirtioInterrupt for VirtioInterruptMsix { fn set_notifier( &self, - _interrupt: u32, - _eventfd: Option, - _vm: &dyn hypervisor::Vm, + interrupt: u32, + eventfd: Option, + vm: &dyn hypervisor::Vm, ) -> std::io::Result<()> { - unimplemented!() + self.interrupt_source_group + .set_notifier(interrupt, eventfd, vm) } } diff --git a/vm-device/src/interrupt/mod.rs b/vm-device/src/interrupt/mod.rs index 342cbe0631..e9b0180d29 100644 --- a/vm-device/src/interrupt/mod.rs +++ b/vm-device/src/interrupt/mod.rs @@ -57,7 +57,8 @@ //! * The virtual device backend requests the interrupt manager to create an interrupt group //! according to guest configuration information -use std::sync::Arc; +use std::io::{Error, ErrorKind}; +use std::sync::{Arc, Mutex}; pub use hypervisor::{InterruptSourceConfig, LegacyIrqSourceConfig, MsiIrqSourceConfig}; use vmm_sys_util::eventfd::EventFd; @@ -107,6 +108,30 @@ pub trait InterruptManager: Send + Sync { /// * count: number of Interrupt Sources to be managed by the group object. fn create_group(&self, config: Self::GroupConfig) -> Result>; + /// Create an [InterruptSourceGroup](trait.InterruptSourceGroup.html) object to manage + /// interrupt sources for a virtual device + /// + /// An [InterruptSourceGroup](trait.InterruptSourceGroup.html) object manages all interrupt + /// sources of the same type for a virtual device. + /// + /// This is the same as [`Self::create_group`], except that the returned + /// [`InterruptSourceGroup`] allows setting the irqfd used as notifier via + /// [`InterruptSourceGroup::set_notifier`]. + /// + /// # Arguments + /// * interrupt_type: type of interrupt source. + /// * base: base Interrupt Source ID to be managed by the group object. + /// * count: number of Interrupt Sources to be managed by the group object. + fn create_group_mut( + &self, + _config: Self::GroupConfig, + ) -> Result>> { + Err(Error::new( + ErrorKind::Unsupported, + "setting notifiers not supported", + )) + } + /// Destroy an [InterruptSourceGroup](trait.InterruptSourceGroup.html) object created by /// [create_group()](trait.InterruptManager.html#tymethod.create_group). /// @@ -137,7 +162,7 @@ pub trait InterruptSourceGroup: Send + Sync { /// Returns an interrupt notifier from this interrupt. /// /// An interrupt notifier allows for external components and processes - /// to inject interrupts into a guest, by writing to the file returned + /// to inject interrupts into a guest, by writing to the [`EventFd`] returned /// by this method. #[allow(unused_variables)] fn notifier(&self, index: InterruptIndex) -> Option; @@ -159,4 +184,17 @@ pub trait InterruptSourceGroup: Send + Sync { /// Set the interrupt group GSI routing table. fn set_gsi(&self) -> Result<()>; + + /// Sets the [`EventFd`] used to trigger interrupts. + fn set_notifier( + &mut self, + _index: InterruptIndex, + _eventfd: Option, + _vm: &dyn hypervisor::Vm, + ) -> Result<()> { + Err(Error::new( + ErrorKind::Unsupported, + "setting notifiers not supported", + )) + } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index e560b02d7a..958d3086b1 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4201,8 +4201,8 @@ impl DeviceManager { return Err(DeviceManagerError::MissingNode); } - // Allows support for one MSI-X vector per queue. It also adds 1 - // as we need to take into account the dedicated vector to notify + // Allows support for one MSI-X vector per interrupt needed by the device. + // It also adds 1 as we need to take into account the dedicated vector to notify // about a virtio config change. let msix_num = (virtio_device.lock().unwrap().queue_max_sizes().len() + 1) as u16; diff --git a/vmm/src/interrupt.rs b/vmm/src/interrupt.rs index 07727d1ad6..0995d83567 100644 --- a/vmm/src/interrupt.rs +++ b/vmm/src/interrupt.rs @@ -86,6 +86,9 @@ impl InterruptRoute { ) } + // This is currently not used, but the upcoming vhost-guest feature + // will use it. Use #[allow(dead_code)] to suppress a compiler + // warning. #[allow(dead_code)] pub fn set_notifier( &mut self, @@ -96,7 +99,7 @@ impl InterruptRoute { if self.registered { if let Some(ref irq_fd) = self.irq_fd { vm.register_irqfd(irq_fd, self.gsi) - .map_err(|e| io::Error::other(format!("Failed registering irq_fd: {e}")))? + .map_err(|e| io::Error::other(format!("Failed registering irq_fd: {e}")))?; } // If the irqfd cannot be unregistered, what to do? Spin? // Returning an error isn't helpful as the new irqfd is already registered. @@ -235,6 +238,19 @@ impl InterruptSourceGroup for MsiInterruptGroup { let routes = self.gsi_msi_routes.lock().unwrap(); self.set_gsi_routes(&routes) } + + fn set_notifier( + &mut self, + index: InterruptIndex, + eventfd: Option, + vm: &dyn hypervisor::Vm, + ) -> Result<()> { + if let Some(route) = self.irq_routes.get(&index) { + return route.lock().unwrap().set_notifier(eventfd, vm); + } + + Ok(()) + } } pub struct LegacyUserspaceInterruptGroup { @@ -323,6 +339,26 @@ impl InterruptManager for LegacyUserspaceInterruptManager { } } +impl MsiInterruptManager { + fn create_group_raw( + &self, + config: ::GroupConfig, + ) -> Result { + let mut allocator = self.allocator.lock().unwrap(); + let mut irq_routes: HashMap> = + HashMap::with_capacity(config.count as usize); + for i in config.base..config.base + config.count { + irq_routes.insert(i, Mutex::new(InterruptRoute::new(&mut allocator)?)); + } + + Ok(MsiInterruptGroup::new( + self.vm.clone(), + self.gsi_msi_routes.clone(), + irq_routes, + )) + } +} + impl InterruptManager for MsiInterruptManager { type GroupConfig = MsiIrqGroupConfig; @@ -341,6 +377,14 @@ impl InterruptManager for MsiInterruptManager { ))) } + fn create_group_mut( + &self, + config: Self::GroupConfig, + ) -> vm_device::interrupt::Result>> { + let r = self.create_group_raw(config)?; + Ok(Arc::new(Mutex::new(r))) + } + fn destroy_group(&self, _group: Arc) -> Result<()> { Ok(()) } From 3e2e453d89813d5bef2e51e2b3673451ba6fd946 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 17 Mar 2026 17:46:36 +0100 Subject: [PATCH 0767/1893] 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 --- block/src/lib.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index c02b315cc2..2c8556c395 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -621,15 +621,18 @@ impl Request { } let mut wz_sector = [0u8; 8]; - let mut wz_num_sectors = [0u8; 4]; + let mut wz_flags = [0u8; 4]; mem.read_slice(&mut wz_sector, data_addr) .map_err(ExecuteError::Read)?; mem.read_slice(&mut wz_num_sectors, data_addr.checked_add(8).unwrap()) .map_err(ExecuteError::Read)?; + mem.read_slice(&mut wz_flags, data_addr.checked_add(12).unwrap()) + .map_err(ExecuteError::Read)?; let wz_sector = u64::from_le_bytes(wz_sector); let wz_num_sectors = u32::from_le_bytes(wz_num_sectors); + let wz_flags = u32::from_le_bytes(wz_flags); let wz_offset = wz_sector * SECTOR_SIZE; if wz_offset == 0 && disable_sector0_writes { @@ -637,9 +640,15 @@ impl Request { } let wz_length = (wz_num_sectors as u64) * SECTOR_SIZE; - disk_image - .write_zeroes(wz_offset, wz_length, user_data) - .map_err(ExecuteError::AsyncWriteZeroes)?; + if wz_flags & VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP != 0 { + disk_image + .punch_hole(wz_offset, wz_length, user_data) + .map_err(ExecuteError::AsyncPunchHole)?; + } else { + disk_image + .write_zeroes(wz_offset, wz_length, user_data) + .map_err(ExecuteError::AsyncWriteZeroes)?; + } } RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), } From 26035df8e5ec737b178ba22eb24b2e0f85741887 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 17 Mar 2026 21:37:29 +0100 Subject: [PATCH 0768/1893] 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 --- cloud-hypervisor/tests/integration.rs | 88 +++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 7218cbb393..6bcbecddbc 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7764,6 +7764,94 @@ mod common_parallel { _test_virtio_block_discard_with_backend("raw", "raw", &[], true, false, true); } + #[test] + fn test_virtio_block_write_zeroes_unmap_raw() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + + let test_disk_path = guest.tmp_dir.as_path().join("write_zeroes_unmap_test.raw"); + + let res = exec_host_command_output(&format!( + "dd if=/dev/zero of={} bs=1M count=128", + test_disk_path.to_str().unwrap() + )); + assert!(res.status.success(), "Failed to create raw test image"); + + let mut child = GuestCommand::new(&guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={},image_type=raw", test_disk_path.to_str().unwrap()).as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + let wz_max = guest + .ssh_command("cat /sys/block/vdc/queue/write_zeroes_max_bytes") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(); + assert!( + wz_max > 0, + "write_zeroes_max_bytes={wz_max}, VIRTIO_BLK_F_WRITE_ZEROES not negotiated" + ); + + guest + .ssh_command("sudo dd if=/dev/urandom of=/dev/vdc bs=1M count=64 oflag=direct") + .unwrap(); + guest.ssh_command("sync").unwrap(); + + // fallocate --punch-hole on a block device sends + // WRITE_ZEROES with VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP set. + let result = guest + .ssh_command("sudo fallocate -p -o 0 -l 67108864 /dev/vdc 2>&1 || true") + .unwrap(); + assert!( + !result.contains("Operation not supported") && !result.contains("not supported"), + "fallocate --punch-hole failed: {result}" + ); + guest.ssh_command("sync").unwrap(); + + assert_guest_disk_region_is_zero(&guest, "/dev/vdc", 0, 4096 * 256); + + let test_disk_str = test_disk_path.to_str().unwrap(); + verify_sparse_file(test_disk_str, 1.0); + verify_fiemap_extents(test_disk_str, "raw"); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + } + #[test] fn test_virtio_block_discard_unsupported_vhd() { _test_virtio_block_discard("vhd", "vpc", &["-o", "subformat=fixed"], false, false); From 0e7b2d68fc2e278a15bea7e12f39ad766059eef8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 18 Mar 2026 00:08:45 +0100 Subject: [PATCH 0769/1893] 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 --- performance-metrics/src/main.rs | 37 ++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index c648d189c7..2f6b8bdb50 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -1301,6 +1301,13 @@ fn main() { .num_args(1) .required(false), ) + .arg( + Arg::new("test-exclude") + .long("test-exclude") + .help("Exclude metrics tests matching the provided keywords") + .num_args(1) + .required(false), + ) .arg( Arg::new("list-tests") .long("list-tests") @@ -1346,19 +1353,31 @@ fn main() { .filter(|t| !(cfg!(target_arch = "aarch64") && t.name == "virtio_net_latency_us")) .collect(); + let test_filter = match cmd_arguments.get_many::("test-filter") { + Some(s) => s.collect(), + None => Vec::new(), + }; + + let test_exclude = match cmd_arguments.get_many::("test-exclude") { + Some(s) => s.collect(), + None => Vec::new(), + }; + + // Determine which tests will actually run. + let tests_to_run: Vec<&&PerformanceTest> = test_list + .iter() + .filter(|t| test_filter.is_empty() || test_filter.iter().any(|&s| t.name.contains(s))) + .filter(|t| !test_exclude.iter().any(|&s| t.name.contains(s))) + .collect(); + if cmd_arguments.get_flag("list-tests") { - for test in test_list.iter() { + for test in tests_to_run.iter() { println!("\"{}\" ({})", test.name, test.control); } return; } - let test_filter = match cmd_arguments.get_many::("test-filter") { - Some(s) => s.collect(), - None => Vec::new(), - }; - // Run performance tests sequentially and report results (in both readable/json format) let mut metrics_report: MetricsReport = Default::default(); @@ -1380,12 +1399,6 @@ fn main() { .unwrap_or_default(), }); - // Determine which tests will actually run. - let tests_to_run: Vec<&&PerformanceTest> = test_list - .iter() - .filter(|t| test_filter.is_empty() || test_filter.iter().any(|&s| t.name.contains(s))) - .collect(); - // Skip heavy VM level init/cleanup when only micro benchmarks are selected. let needs_vm_tests = tests_to_run.iter().any(|t| !t.name.starts_with("micro_")); From 07479536abcd087b18dc71a62e996966af5552af Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 18 Mar 2026 00:09:22 +0100 Subject: [PATCH 0770/1893] 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 --- scripts/run_metrics.sh | 4 ++++ scripts/test-util.sh | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/scripts/run_metrics.sh b/scripts/run_metrics.sh index f70ccf1cde..88c207b322 100755 --- a/scripts/run_metrics.sh +++ b/scripts/run_metrics.sh @@ -108,6 +108,10 @@ if [ -n "$test_filter" ]; then test_binary_args+=("--test-filter $test_filter") fi +if [ -n "$test_exclude" ]; then + test_binary_args+=("--test-exclude $test_exclude") +fi + # Ensure that git commands can be run in this directory (for metrics report) git config --global --add safe.directory "$PWD" diff --git a/scripts/test-util.sh b/scripts/test-util.sh index 8958439330..5b414f8583 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -122,6 +122,10 @@ process_common_args() { shift test_filter="$1" ;; + "--test-exclude") + shift + test_exclude="$1" + ;; "--build-guest-kernel") build_kernel=true ;; From f4772e7f4c58d72eb8e03e729c81a2e0a3432dfc Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 18 Mar 2026 00:10:37 +0100 Subject: [PATCH 0771/1893] 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 --- .github/workflows/integration-metrics.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-metrics.yaml b/.github/workflows/integration-metrics.yaml index 952e938fdf..4e66f4b614 100644 --- a/.github/workflows/integration-metrics.yaml +++ b/.github/workflows/integration-metrics.yaml @@ -17,6 +17,6 @@ jobs: fetch-depth: 0 - 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_ -- --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' From 068b5ecb6318ae7ec56a1025cff174b953708609 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 17 Mar 2026 09:30:01 -0700 Subject: [PATCH 0772/1893] 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 --- cloud-hypervisor/tests/integration.rs | 83 +++++++++++++++++++-------- docs/snapshot_restore.md | 9 +++ vmm/src/config.rs | 32 ++++++++++- vmm/src/lib.rs | 18 ++++-- 4 files changed, 108 insertions(+), 34 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 6bcbecddbc..6e4f8c1fec 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -10814,16 +10814,22 @@ mod common_sequential { #[test] #[cfg(not(feature = "mshv"))] fn test_snapshot_restore_hotplug_virtiomem() { - _test_snapshot_restore(true); + _test_snapshot_restore(true, false); } #[test] #[cfg(not(feature = "mshv"))] // See issue #7437 fn test_snapshot_restore_basic() { - _test_snapshot_restore(false); + _test_snapshot_restore(false, false); } - fn _test_snapshot_restore(use_hotplug: bool) { + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_with_resume() { + _test_snapshot_restore(false, true); + } + + fn _test_snapshot_restore(use_hotplug: bool, use_resume_option: bool) { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -10975,7 +10981,7 @@ mod common_sequential { ]) .args([ "--restore", - format!("source_url=file://{snapshot_dir}").as_str(), + format!("source_url=file://{snapshot_dir},resume={use_resume_option}").as_str(), ]) .capture_output() .spawn() @@ -11005,28 +11011,12 @@ mod common_sequential { &expected_events, &event_path_restored )); - let latest_events = [&MetaEvent { - event: "restored".to_string(), - device_id: None, - }]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); - - // Remove the snapshot dir - let _ = remove_dir_all(snapshot_dir.as_str()); - - let r = std::panic::catch_unwind(|| { - // Resume the VM - assert!(remote_command(&api_socket_restored, "resume", None)); - // There is no way that we can ensure the 'write()' to the - // event file is completed when the 'resume' request is - // returned successfully, because the 'write()' was done - // asynchronously from a different thread of Cloud - // Hypervisor (e.g. the event-monitor thread). - thread::sleep(std::time::Duration::new(1, 0)); + if use_resume_option { let latest_events = [ + &MetaEvent { + event: "restored".to_string(), + device_id: None, + }, &MetaEvent { event: "resuming".to_string(), device_id: None, @@ -11040,6 +11030,49 @@ mod common_sequential { &latest_events, &event_path_restored )); + } else { + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(check_latest_events_exact( + &latest_events, + &event_path_restored + )); + } + + // Remove the snapshot dir + let _ = remove_dir_all(snapshot_dir.as_str()); + + let r = std::panic::catch_unwind(|| { + if use_resume_option { + // VM was automatically resumed via restore option, just wait for events + thread::sleep(std::time::Duration::new(1, 0)); + } else { + // Resume the VM manually + assert!(remote_command(&api_socket_restored, "resume", None)); + // There is no way that we can ensure the 'write()' to the + // event file is completed when the 'resume' request is + // returned successfully, because the 'write()' was done + // asynchronously from a different thread of Cloud + // Hypervisor (e.g. the event-monitor thread). + thread::sleep(std::time::Duration::new(1, 0)); + + let latest_events = [ + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(check_latest_events_exact( + &latest_events, + &event_path_restored + )); + } // Perform same checks to validate VM has been properly restored assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); diff --git a/docs/snapshot_restore.md b/docs/snapshot_restore.md index 2cf8eda5a8..567f77a9a6 100644 --- a/docs/snapshot_restore.md +++ b/docs/snapshot_restore.md @@ -90,6 +90,15 @@ start using it. ./ch-remote --api-socket=/tmp/cloud-hypervisor.sock resume ``` +Alternatively, the `resume` option can be used to automatically resume the VM +after restore completes: + +```bash +./cloud-hypervisor \ + --api-socket /tmp/cloud-hypervisor.sock \ + --restore source_url=file:///home/foo/snapshot,resume=true +``` + At this point, the VM is fully restored and is identical to the VM which was snapshot earlier. diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 8b284660a1..93ec6c8915 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2603,17 +2603,20 @@ pub struct RestoreConfig { pub memory_restore_mode: MemoryRestoreMode, #[serde(default)] pub net_fds: Option>, + #[serde(default)] + pub resume: bool, } impl RestoreConfig { pub const SYNTAX: &'static str = "Restore from a VM snapshot. \ \nRestore parameters \"source_url=,prefault=on|off,memory_restore_mode=copy|ondemand,\ - net_fds=\" \ + net_fds=,resume=true|false\" \ \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \ \n`prefault` controls eager prefaulting for the copy-based restore path (disabled by default) \ \n`memory_restore_mode=copy` preserves the existing eager read-copy restore behavior, while `memory_restore_mode=ondemand` enables lazy demand paging and fails restore if userfaultfd support is unavailable \ \n`net_fds` is a list of net ids with new file descriptors. \ - Only net devices backed by FDs directly are needed as input."; + Only net devices backed by FDs directly are needed as input.\ + \n `resume` controls whether the VM will be directly resumed after restore "; pub fn parse(restore: &str) -> Result { let mut parser = OptionParser::new(); @@ -2621,7 +2624,8 @@ impl RestoreConfig { .add("source_url") .add("prefault") .add("memory_restore_mode") - .add("net_fds"); + .add("net_fds") + .add("resume"); parser.parse(restore).map_err(Error::ParseRestore)?; let source_url = parser @@ -2649,12 +2653,18 @@ impl RestoreConfig { }) .collect() }); + let resume = parser + .convert::("resume") + .map_err(Error::ParseRestore)? + .unwrap_or(Toggle(false)) + .0; Ok(RestoreConfig { source_url, prefault, memory_restore_mode, net_fds, + resume, }) } @@ -4546,6 +4556,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" prefault: false, memory_restore_mode: MemoryRestoreMode::Copy, net_fds: None, + resume: false, } ); assert_eq!( @@ -4568,6 +4579,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" fds: Some(vec![5, 6, 7, 8]), } ]), + resume: false, } ); assert_eq!( @@ -4577,6 +4589,17 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" prefault: false, memory_restore_mode: MemoryRestoreMode::OnDemand, net_fds: None, + resume: false, + } + ); + assert_eq!( + RestoreConfig::parse("source_url=/path/to/snapshot,resume=on")?, + RestoreConfig { + source_url: PathBuf::from("/path/to/snapshot"), + prefault: false, + memory_restore_mode: MemoryRestoreMode::Copy, + net_fds: None, + resume: true, } ); // Parsing should fail as source_url is a required field @@ -4678,6 +4701,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" fds: Some(vec![7, 8]), }, ]), + resume: false, }; valid_config.validate(&snapshot_vm_config).unwrap(); @@ -4742,6 +4766,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" prefault: false, memory_restore_mode: MemoryRestoreMode::Copy, net_fds: None, + resume: false, }; snapshot_vm_config.net = Some(vec![NetConfig { id: Some("net2".to_owned()), @@ -4755,6 +4780,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" prefault: true, memory_restore_mode: MemoryRestoreMode::OnDemand, net_fds: None, + resume: false, }; assert_eq!( invalid_restore_mode.validate(&snapshot_vm_config), diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 0b82be49e6..cb72d69d96 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1849,16 +1849,22 @@ impl RequestHandler for Vmm { restore_cfg.prefault, restore_cfg.memory_restore_mode, ) - .map_err(|vm_restore_err| { - error!("VM Restore failed: {vm_restore_err:?}"); - - // Cleanup the VM being created while vm restore + .and_then(|()| { + if restore_cfg.resume { + self.vm_resume() + } else { + Ok(()) + } + }) + .map_err(|e| { + error!("VM Restore failed: {e:?}"); if let Err(e) = self.vm_delete() { return e; } + e + })?; - vm_restore_err - }) + Ok(()) } #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] From 54b27d8812a49b70af80c4d598796c5b88e667f4 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 18 Mar 2026 08:10:34 -0700 Subject: [PATCH 0773/1893] vmm: openapi: Add resume field to RestoreConfig Signed-off-by: Rob Bradford --- vmm/src/api/openapi/cloud-hypervisor.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 8bdf14e50f..e8d3350dba 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -1358,6 +1358,8 @@ components: type: boolean memory_restore_mode: $ref: "#/components/schemas/MemoryRestoreMode" + resume: + type: boolean ReceiveMigrationData: required: From 9655eaddd5b6414e8c629045a40c14eb0996ba3d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 18 Mar 2026 13:49:12 +0100 Subject: [PATCH 0774/1893] 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 --- block/src/lib.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 2c8556c395..4a6ea4979d 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -69,6 +69,13 @@ use crate::vhdx::VhdxError; const SECTOR_SHIFT: u8 = 9; pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT; +/// Field offsets within `struct virtio_blk_discard_write_zeroes`. +const DISCARD_WZ_SECTOR_OFFSET: u64 = + mem::offset_of!(virtio_blk_discard_write_zeroes, sector) as u64; +const DISCARD_WZ_NUM_SECTORS_OFFSET: u64 = + mem::offset_of!(virtio_blk_discard_write_zeroes, num_sectors) as u64; +const DISCARD_WZ_FLAGS_OFFSET: u64 = mem::offset_of!(virtio_blk_discard_write_zeroes, flags) as u64; + #[derive(Error, Debug)] pub enum Error { #[error("Guest gave us bad memory addresses")] @@ -589,9 +596,15 @@ impl Request { let mut discard_sector = [0u8; 8]; let mut discard_num_sectors = [0u8; 4]; - mem.read_slice(&mut discard_sector, data_addr) + + let sector_addr = data_addr.checked_add(DISCARD_WZ_SECTOR_OFFSET).unwrap(); + mem.read_slice(&mut discard_sector, sector_addr) .map_err(ExecuteError::Read)?; - mem.read_slice(&mut discard_num_sectors, data_addr.checked_add(8).unwrap()) + + let num_sectors_addr = data_addr + .checked_add(DISCARD_WZ_NUM_SECTORS_OFFSET) + .unwrap(); + mem.read_slice(&mut discard_num_sectors, num_sectors_addr) .map_err(ExecuteError::Read)?; let discard_sector = u64::from_le_bytes(discard_sector); @@ -623,11 +636,19 @@ impl Request { let mut wz_sector = [0u8; 8]; let mut wz_num_sectors = [0u8; 4]; let mut wz_flags = [0u8; 4]; - mem.read_slice(&mut wz_sector, data_addr) + + let sector_addr = data_addr.checked_add(DISCARD_WZ_SECTOR_OFFSET).unwrap(); + mem.read_slice(&mut wz_sector, sector_addr) .map_err(ExecuteError::Read)?; - mem.read_slice(&mut wz_num_sectors, data_addr.checked_add(8).unwrap()) + + let num_sectors_addr = data_addr + .checked_add(DISCARD_WZ_NUM_SECTORS_OFFSET) + .unwrap(); + mem.read_slice(&mut wz_num_sectors, num_sectors_addr) .map_err(ExecuteError::Read)?; - mem.read_slice(&mut wz_flags, data_addr.checked_add(12).unwrap()) + + let flags_addr = data_addr.checked_add(DISCARD_WZ_FLAGS_OFFSET).unwrap(); + mem.read_slice(&mut wz_flags, flags_addr) .map_err(ExecuteError::Read)?; let wz_sector = u64::from_le_bytes(wz_sector); From 0ee0441f2b79cbcca3049f5b697b6f6aa2b7e8d6 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 17 Mar 2026 17:49:09 +0100 Subject: [PATCH 0775/1893] 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 --- virtio-devices/src/block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index af6d638dd0..2adbff74f8 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -833,7 +833,7 @@ impl Block { if avail_features & (1u64 << VIRTIO_BLK_F_DISCARD) != 0 { config.max_discard_sectors = u32::MAX; config.max_discard_seg = 1; - config.discard_sector_alignment = 1; + config.discard_sector_alignment = (logical_block_size / SECTOR_SIZE) as u32; } if num_queues > 1 { From 19fa512f0245f96c95d0a06c6f2e7c7e83bf38e4 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 18 Mar 2026 15:27:47 +0100 Subject: [PATCH 0776/1893] 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 --- block/src/raw_async_aio.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index f59e463b4c..c2e6a174e0 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -172,18 +172,16 @@ impl AsyncIo for RawFileAsyncAio { } fn next_completed_request(&mut self) -> Option<(u64, i32)> { - // Drain synchronous completions first (from punch_hole/write_zeroes). - if let Some(completed) = self.completion_list.pop_front() { - return Some(completed); - } - - let mut events: [aio::IoEvent; 1] = [aio::IoEvent::default()]; - let rc = self.ctx.get_events(0, &mut events, None).unwrap(); - if rc == 0 { - None - } else { - Some((events[0].data, events[0].res as i32)) + if self.completion_list.is_empty() { + // Drain pending AIO completions batched into the same queue. + let mut events = [aio::IoEvent::default(); 32]; + let rc = self.ctx.get_events(0, &mut events, None).unwrap(); + for event in &events[..rc] { + self.completion_list + .push_back((event.data, event.res as i32)); + } } + self.completion_list.pop_front() } fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { From e278e5e931d62e0a11c0a584c290a412ab41c8e3 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 17:20:38 -0800 Subject: [PATCH 0777/1893] 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 --- cloud-hypervisor/tests/integration.rs | 28 +++++++++++---------------- test_infra/src/lib.rs | 5 +++++ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 6e4f8c1fec..b65a2c4427 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1052,25 +1052,13 @@ fn _test_guest_numa_nodes(acpi: bool) { } #[allow(unused_variables)] -fn _test_power_button(acpi: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let mut cmd = GuestCommand::new(&guest); +fn _test_power_button(guest: &Guest) { + let mut cmd = GuestCommand::new(guest); let api_socket = temp_api_path(&guest.tmp_dir); - #[cfg(target_arch = "x86_64")] - let kernel_path = direct_kernel_boot_path(); - #[cfg(target_arch = "aarch64")] - let kernel_path = if acpi { - edk2_path() - } else { - direct_kernel_boot_path() - }; - cmd.default_cpus() .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_kernel_cmdline() .capture_output() .default_disks() .default_net() @@ -2828,7 +2816,9 @@ mod common_parallel { #[test] fn test_power_button() { - _test_power_button(false); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + _test_power_button(&guest); } #[test] @@ -14533,7 +14523,11 @@ mod aarch64_acpi { #[test] fn test_power_button_acpi() { - _test_power_button(true); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_kernel_path(edk2_path().to_str().unwrap()); + _test_power_button(&guest); } #[test] diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 22a1ea99dd..c96e568997 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1013,6 +1013,11 @@ impl Guest { self } + pub fn with_kernel_path(mut self, kernel_path: &str) -> Self { + self.kernel_path = Some(kernel_path.to_string()); + self + } + pub fn default_net_string(&self) -> String { format!( "tap=,mac={},ip={},mask=255.255.255.128", From b6d8df772d9bf025c26d4b5cb3001b6cf3b7ad8c Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 17:32:42 -0800 Subject: [PATCH 0778/1893] 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 --- cloud-hypervisor/tests/integration.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index b65a2c4427..1cca190168 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14934,4 +14934,12 @@ mod common_cvm { let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_delete(&target_api, &guest); } + + #[test] + fn test_power_button() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_power_button(&guest); + } } From dacc92a08fd84379cdcc2afe7dec94b4c0f6b3b5 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 17:50:10 -0800 Subject: [PATCH 0779/1893] 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 --- cloud-hypervisor/tests/integration.rs | 32 ++++++++++++--------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 1cca190168..144a8706d5 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1781,28 +1781,15 @@ fn get_fd_count(pid: u32) -> usize { fs::read_dir(format!("/proc/{pid}/fd")).unwrap().count() } -fn _test_virtio_vsock(hotplug: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - #[cfg(target_arch = "x86_64")] - let kernel_path = direct_kernel_boot_path(); - #[cfg(target_arch = "aarch64")] - let kernel_path = if hotplug { - edk2_path() - } else { - direct_kernel_boot_path() - }; - +fn _test_virtio_vsock(guest: &Guest, hotplug: bool) { let socket = temp_vsock_path(&guest.tmp_dir); let api_socket = temp_api_path(&guest.tmp_dir); - let mut cmd = GuestCommand::new(&guest); + let mut cmd = GuestCommand::new(guest); cmd.args(["--api-socket", &api_socket]); cmd.default_cpus(); cmd.default_memory(); - cmd.args(["--kernel", kernel_path.to_str().unwrap()]); - cmd.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]); + cmd.default_kernel_cmdline(); cmd.default_disks(); cmd.default_net(); @@ -5965,12 +5952,21 @@ mod common_parallel { #[test] fn test_virtio_vsock() { - _test_virtio_vsock(false); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + _test_virtio_vsock(&guest, false); } #[test] fn test_virtio_vsock_hotplug() { - _test_virtio_vsock(true); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + #[cfg(target_arch = "x86_64")] + let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + #[cfg(target_arch = "aarch64")] + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_kernel_path(edk2_path().to_str().unwrap()); + _test_virtio_vsock(&guest, true); } #[test] From c44b7679cf68f96e3190a57470197ed9b5fa814f Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 17:57:21 -0800 Subject: [PATCH 0780/1893] 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 --- cloud-hypervisor/tests/integration.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 144a8706d5..47c3c1f040 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14938,4 +14938,12 @@ mod common_cvm { GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); _test_power_button(&guest); } + + #[test] + fn test_virtio_vsock() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_virtio_vsock(&guest, false); + } } From 18ed8e61d201cc1de0cba0b523e5ed4a9853629e Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 18:38:10 -0800 Subject: [PATCH 0781/1893] 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 --- cloud-hypervisor/tests/integration.rs | 69 +++++++++++++-------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 47c3c1f040..ccc874bad0 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2471,6 +2471,37 @@ fn _test_simple_launch(guest: &Guest) { handle_child_output(r, &output); } +fn _test_multi_cpu(guest: &Guest) { + let mut cmd = GuestCommand::new(guest); + cmd.args(["--cpus", "boot=2,max=4"]) + .default_memory() + .default_kernel_cmdline() + .capture_output() + .default_disks() + .default_net(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + + assert_eq!( + guest + .ssh_command(r#"sudo dmesg | grep "smp: Brought up" | sed "s/\[\ *[0-9.]*\] //""#) + .unwrap() + .trim(), + "smp: Brought up 1 node, 2 CPUs" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + mod common_parallel { use std::cmp; use std::fs::{File, OpenOptions, copy}; @@ -2502,41 +2533,9 @@ mod common_parallel { #[test] fn test_multi_cpu() { - let jammy_image = JAMMY_IMAGE_NAME.to_string(); - let disk_config = UbuntuDiskConfig::new(jammy_image); - let guest = Guest::new(Box::new(disk_config)); - - let mut cmd = GuestCommand::new(&guest); - cmd.args(["--cpus", "boot=2,max=4"]) - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .capture_output() - .default_disks() - .default_net(); - - let mut child = cmd.spawn().unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); - - assert_eq!( - guest - .ssh_command( - r#"sudo dmesg | grep "smp: Brought up" | sed "s/\[\ *[0-9.]*\] //""# - ) - .unwrap() - .trim(), - "smp: Brought up 1 node, 2 CPUs" - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + _test_multi_cpu(&guest); } #[test] From 848a28048364f5d77789cfec5b6c3c1188935a1a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 18:40:16 -0800 Subject: [PATCH 0782/1893] 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 --- cloud-hypervisor/tests/integration.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index ccc874bad0..d138e184a3 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14945,4 +14945,12 @@ mod common_cvm { GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); _test_virtio_vsock(&guest, false); } + + #[test] + fn test_multi_cpu() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_multi_cpu(&guest); + } } From 9059fb902d5bf896fe0fffcf9b603ede50d9c075 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 19:44:59 -0800 Subject: [PATCH 0783/1893] 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 --- cloud-hypervisor/tests/integration.rs | 76 ++++++++++++++------------- test_infra/src/lib.rs | 18 +++++++ 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index d138e184a3..50d9f4e4c7 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2502,6 +2502,42 @@ fn _test_multi_cpu(guest: &Guest) { handle_child_output(r, &output); } +fn _test_cpu_affinity(guest: &Guest) { + // We need the host to have at least 4 CPUs if we want to be able + // to run this test. + let host_cpus_count = exec_host_command_output("nproc"); + assert!( + String::from_utf8_lossy(&host_cpus_count.stdout) + .trim() + .parse::() + .unwrap_or(0) + >= 4 + ); + + let mut child = GuestCommand::new(guest) + .default_cpus_with_affinity() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + let pid = child.id(); + let taskset_vcpu0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_vcpu0.stdout).trim(), "0,2"); + let taskset_vcpu1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_vcpu1.stdout).trim(), "1,3"); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); +} + mod common_parallel { use std::cmp; use std::fs::{File, OpenOptions, copy}; @@ -2638,42 +2674,10 @@ mod common_parallel { #[test] fn test_cpu_affinity() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - // We need the host to have at least 4 CPUs if we want to be able - // to run this test. - let host_cpus_count = exec_host_command_output("nproc"); - assert!( - String::from_utf8_lossy(&host_cpus_count.stdout) - .trim() - .parse::() - .unwrap_or(0) - >= 4 - ); - - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=2,affinity=[0@[0,2],1@[1,3]]"]) - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - let pid = child.id(); - let taskset_vcpu0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_vcpu0.stdout).trim(), "0,2"); - let taskset_vcpu1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_vcpu1.stdout).trim(), "1,3"); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(2); + _test_cpu_affinity(&guest); } #[test] diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index c96e568997..e1af41bc0f 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1442,6 +1442,14 @@ impl Guest { ) } + pub fn default_cpus_with_affinity_string(&self) -> String { + format!( + "boot={},affinity=[0@[0,2],1@[1,3]]{}", + self.num_cpu, + if self.nested { "" } else { ",nested=off" } + ) + } + pub fn default_memory_string(&self) -> String { format!("size={}", self.mem_size_str) } @@ -1716,6 +1724,16 @@ impl<'a> GuestCommand<'a> { self.args(["--cpus", self.guest.default_cpus_string().as_str()]) } + pub fn default_cpus_with_affinity(&mut self) -> &mut Self { + // Only support cpu affinity for 2 VCPUs for now, + // as it is only used in a test that validates cpu affinity is applied correctly. + assert_eq!(self.guest.num_cpu, 2); + self.args([ + "--cpus", + self.guest.default_cpus_with_affinity_string().as_str(), + ]) + } + pub fn default_memory(&mut self) -> &mut Self { self.args(["--memory", self.guest.default_memory_string().as_str()]) } From 7bf439975f0a23cf23b51245fc18cd560c55adf8 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 19:45:48 -0800 Subject: [PATCH 0784/1893] 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 --- cloud-hypervisor/tests/integration.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 50d9f4e4c7..dbb29b675a 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14957,4 +14957,13 @@ mod common_cvm { GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); _test_multi_cpu(&guest); } + + #[test] + fn test_cpu_affinity() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(2); + _test_cpu_affinity(&guest); + } } From c761d741bf7b696849d952ea0391e2f2fce01c5d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 21:02:38 -0800 Subject: [PATCH 0785/1893] 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 --- cloud-hypervisor/tests/integration.rs | 108 +++++++++++++------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index dbb29b675a..ddb4ea00a7 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2538,6 +2538,58 @@ fn _test_cpu_affinity(guest: &Guest) { handle_child_output(r, &output); } +fn _test_virtio_queue_affinity(guest: &Guest) { + // We need the host to have at least 4 CPUs if we want to be able + // to run this test. + let host_cpus_count = exec_host_command_output("nproc"); + assert!( + String::from_utf8_lossy(&host_cpus_count.stdout) + .trim() + .parse::() + .unwrap_or(0) + >= 4 + ); + + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={},num_queues=4,queue_affinity=[0@[0,2],1@[1,3],2@[1],3@[3]]", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + let pid = child.id(); + let taskset_q0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_q0.stdout).trim(), "0,2"); + let taskset_q1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_q1.stdout).trim(), "1,3"); + let taskset_q2 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q2 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_q2.stdout).trim(), "1"); + let taskset_q3 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q3 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_q3.stdout).trim(), "3"); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); +} + mod common_parallel { use std::cmp; use std::fs::{File, OpenOptions, copy}; @@ -2683,58 +2735,10 @@ mod common_parallel { #[test] fn test_virtio_queue_affinity() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - // We need the host to have at least 4 CPUs if we want to be able - // to run this test. - let host_cpus_count = exec_host_command_output("nproc"); - assert!( - String::from_utf8_lossy(&host_cpus_count.stdout) - .trim() - .parse::() - .unwrap_or(0) - >= 4 - ); - - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=4"]) - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args([ - "--disk", - format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - format!( - "path={},num_queues=4,queue_affinity=[0@[0,2],1@[1,3],2@[1],3@[3]]", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - ]) - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - let pid = child.id(); - let taskset_q0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_q0.stdout).trim(), "0,2"); - let taskset_q1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_q1.stdout).trim(), "1,3"); - let taskset_q2 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q2 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_q2.stdout).trim(), "1"); - let taskset_q3 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q3 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_q3.stdout).trim(), "3"); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + _test_virtio_queue_affinity(&guest); } #[test] From bad5da622ef013f4db229ae93c2b63c185b209bc Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 21:03:44 -0800 Subject: [PATCH 0786/1893] 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 --- cloud-hypervisor/tests/integration.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index ddb4ea00a7..cd5118642f 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14970,4 +14970,13 @@ mod common_cvm { .with_cpu(2); _test_cpu_affinity(&guest); } + + #[test] + fn test_virtio_queue_affinity() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + _test_virtio_queue_affinity(&guest); + } } From acec5b00d64f1268cfc69703a287029a3eb261f9 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 22:31:34 -0800 Subject: [PATCH 0787/1893] 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 --- cloud-hypervisor/tests/integration.rs | 67 ++++++++++++++------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index cd5118642f..4e919884bf 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2590,6 +2590,39 @@ fn _test_virtio_queue_affinity(guest: &Guest) { handle_child_output(r, &output); } +fn _test_pci_msi(guest: &Guest) { + let mut cmd = GuestCommand::new(guest); + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline() + .capture_output() + .default_disks() + .default_net(); + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); + + let r = std::panic::catch_unwind(|| { + assert_eq!( + guest + .ssh_command(&grep_cmd) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 12 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + mod common_parallel { use std::cmp; use std::fs::{File, OpenOptions, copy}; @@ -2972,38 +3005,8 @@ mod common_parallel { #[test] fn test_pci_msi() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let mut cmd = GuestCommand::new(&guest); - cmd.default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .capture_output() - .default_disks() - .default_net(); - - let mut child = cmd.spawn().unwrap(); - - guest.wait_vm_boot().unwrap(); - - let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); - - let r = std::panic::catch_unwind(|| { - assert_eq!( - guest - .ssh_command(&grep_cmd) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 12 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + _test_pci_msi(&guest); } #[test] From 31f1e67be73648c62704a598e1e3b8cdfca5f36d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 22:32:32 -0800 Subject: [PATCH 0788/1893] 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 --- cloud-hypervisor/tests/integration.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 4e919884bf..9ea1e0d882 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14982,4 +14982,12 @@ mod common_cvm { .with_cpu(4); _test_virtio_queue_affinity(&guest); } + + #[test] + fn test_pci_msi() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_pci_msi(&guest); + } } From 5378f8d6146698f2a249b8b25b9b1e8ca7a28835 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 22:45:00 -0800 Subject: [PATCH 0789/1893] 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 --- cloud-hypervisor/tests/integration.rs | 87 ++++++++++++++------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 9ea1e0d882..5ee867c81c 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2623,6 +2623,49 @@ fn _test_pci_msi(guest: &Guest) { handle_child_output(r, &output); } +fn _test_virtio_net_ctrl_queue(guest: &Guest) { + let mut cmd = GuestCommand::new(guest); + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline() + .args(["--net", guest.default_net_string_w_mtu(3000).as_str()]) + .capture_output() + .default_disks(); + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + #[cfg(target_arch = "aarch64")] + let iface = "enp0s4"; + #[cfg(target_arch = "x86_64")] + let iface = "ens4"; + + let r = std::panic::catch_unwind(|| { + assert_eq!( + guest + .ssh_command( + format!("sudo ethtool -K {iface} rx-gro-hw off && echo success").as_str() + ) + .unwrap() + .trim(), + "success" + ); + assert_eq!( + guest + .ssh_command(format!("cat /sys/class/net/{iface}/mtu").as_str()) + .unwrap() + .trim(), + "3000" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + mod common_parallel { use std::cmp; use std::fs::{File, OpenOptions, copy}; @@ -3012,48 +3055,8 @@ mod common_parallel { #[test] fn test_virtio_net_ctrl_queue() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let mut cmd = GuestCommand::new(&guest); - cmd.default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(["--net", guest.default_net_string_w_mtu(3000).as_str()]) - .capture_output() - .default_disks(); - - let mut child = cmd.spawn().unwrap(); - - guest.wait_vm_boot().unwrap(); - - #[cfg(target_arch = "aarch64")] - let iface = "enp0s4"; - #[cfg(target_arch = "x86_64")] - let iface = "ens4"; - - let r = std::panic::catch_unwind(|| { - assert_eq!( - guest - .ssh_command( - format!("sudo ethtool -K {iface} rx-gro-hw off && echo success").as_str() - ) - .unwrap() - .trim(), - "success" - ); - assert_eq!( - guest - .ssh_command(format!("cat /sys/class/net/{iface}/mtu").as_str()) - .unwrap() - .trim(), - "3000" - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + _test_virtio_net_ctrl_queue(&guest); } #[test] From 6f8776ac50bebbedd60ee155e1f42fecb9c28812 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 5 Mar 2026 22:46:19 -0800 Subject: [PATCH 0790/1893] 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 --- cloud-hypervisor/tests/integration.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5ee867c81c..2ffe0bef04 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -14993,4 +14993,12 @@ mod common_cvm { GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); _test_pci_msi(&guest); } + + #[test] + fn test_virtio_net_ctrl_queue() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_virtio_net_ctrl_queue(&guest); + } } From 2aa92bf4b4ec1c717caccbb10e2edb81902bdaa9 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 7 Mar 2026 22:07:18 -0800 Subject: [PATCH 0791/1893] 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 --- test_infra/src/lib.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index e1af41bc0f..b6d7641f6b 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1694,7 +1694,7 @@ impl<'a> GuestCommand<'a> { self.args(["--net", self.guest.default_net_string().as_str()]) } - pub fn default_kernel_cmdline(&mut self) -> &mut Self { + pub fn default_kernel_cmdline_with_platform(&mut self, platform: Option<&str>) -> &mut Self { if self.guest.vm_type == GuestVmType::Confidential { let console_str = if let Some(c) = &self.guest.console_type { c.as_str() @@ -1709,17 +1709,34 @@ impl<'a> GuestCommand<'a> { ]); self.command .args(["--host-data", generate_host_data().as_str()]); - self.command.args(["--platform", "sev_snp=on"]); + self.command.args([ + "--platform", + &format!( + "{}sev_snp=on", + if let Some(p) = platform { + format!("{p},") + } else { + String::new() + } + ), + ]); } else if let Some(kernel) = &self.guest.kernel_path { self.command.args(["--kernel", kernel.as_str()]); if let Some(cmdline) = &self.guest.kernel_cmdline { self.command.args(["--cmdline", cmdline]); } + if let Some(platform_arg) = platform { + self.command.args(["--platform", platform_arg]); + } } self } + pub fn default_kernel_cmdline(&mut self) -> &mut Self { + self.default_kernel_cmdline_with_platform(None) + } + pub fn default_cpus(&mut self) -> &mut Self { self.args(["--cpus", self.guest.default_cpus_string().as_str()]) } From 62bb7bfe452366cbd2434e7e9b6c9869bfa94c78 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 7 Mar 2026 15:47:20 -0800 Subject: [PATCH 0792/1893] 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 --- cloud-hypervisor/tests/integration.rs | 203 +++++++++++++------------- 1 file changed, 104 insertions(+), 99 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 2ffe0bef04..745128ce6c 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2666,6 +2666,108 @@ fn _test_virtio_net_ctrl_queue(guest: &Guest) { handle_child_output(r, &output); } +fn _test_pci_multiple_segments( + guest: &Guest, + max_num_pci_segments: u16, + pci_segments_for_disk: u16, +) { + // Prepare another disk file for the virtio-disk device + let test_disk_path = String::from( + guest + .tmp_dir + .as_path() + .join("test-disk.raw") + .to_str() + .unwrap(), + ); + assert!( + exec_host_command_status(format!("truncate {test_disk_path} -s 4M").as_str()).success() + ); + assert!(exec_host_command_status(format!("mkfs.ext4 {test_disk_path}").as_str()).success()); + + let mut cmd = GuestCommand::new(guest); + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline_with_platform(Some(&format!( + "num_pci_segments={max_num_pci_segments}" + ))) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={test_disk_path},pci_segment={pci_segments_for_disk},image_type=raw") + .as_str(), + ]) + .capture_output() + .default_net(); + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + let grep_cmd = "lspci | grep \"Host bridge\" | wc -l"; + + let r = std::panic::catch_unwind(|| { + // There should be MAX_NUM_PCI_SEGMENTS PCI host bridges in the guest. + assert_eq!( + guest + .ssh_command(grep_cmd) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + max_num_pci_segments + ); + + // Check both if /dev/vdc exists and if the block size is 4M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 4M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Mount the device. + guest.ssh_command("mkdir mount_image").unwrap(); + guest + .ssh_command("sudo mount -o rw -t ext4 /dev/vdc mount_image/") + .unwrap(); + // Grant all users with write permission. + guest.ssh_command("sudo chmod a+w mount_image/").unwrap(); + + // Write something to the device. + guest + .ssh_command("sudo echo \"bar\" >> mount_image/foo") + .unwrap(); + + // Check the content of the block device. The file "foo" should + // contain "bar". + assert_eq!( + guest + .ssh_command("sudo cat mount_image/foo") + .unwrap() + .trim(), + "bar" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + mod common_parallel { use std::cmp; use std::fs::{File, OpenOptions, copy}; @@ -3062,105 +3164,8 @@ mod common_parallel { #[test] fn test_pci_multiple_segments() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - // Prepare another disk file for the virtio-disk device - let test_disk_path = String::from( - guest - .tmp_dir - .as_path() - .join("test-disk.raw") - .to_str() - .unwrap(), - ); - assert!( - exec_host_command_status(format!("truncate {test_disk_path} -s 4M").as_str()).success() - ); - assert!(exec_host_command_status(format!("mkfs.ext4 {test_disk_path}").as_str()).success()); - - let mut cmd = GuestCommand::new(&guest); - cmd.default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args([ - "--platform", - &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS}"), - ]) - .args([ - "--disk", - format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - format!( - "path={}", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - format!("path={test_disk_path},pci_segment=15,image_type=raw").as_str(), - ]) - .capture_output() - .default_net(); - - let mut child = cmd.spawn().unwrap(); - - guest.wait_vm_boot().unwrap(); - - let grep_cmd = "lspci | grep \"Host bridge\" | wc -l"; - - let r = std::panic::catch_unwind(|| { - // There should be MAX_NUM_PCI_SEGMENTS PCI host bridges in the guest. - assert_eq!( - guest - .ssh_command(grep_cmd) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - MAX_NUM_PCI_SEGMENTS - ); - - // Check both if /dev/vdc exists and if the block size is 4M. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 4M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Mount the device. - guest.ssh_command("mkdir mount_image").unwrap(); - guest - .ssh_command("sudo mount -o rw -t ext4 /dev/vdc mount_image/") - .unwrap(); - // Grant all users with write permission. - guest.ssh_command("sudo chmod a+w mount_image/").unwrap(); - - // Write something to the device. - guest - .ssh_command("sudo echo \"bar\" >> mount_image/foo") - .unwrap(); - - // Check the content of the block device. The file "foo" should - // contain "bar". - assert_eq!( - guest - .ssh_command("sudo cat mount_image/foo") - .unwrap() - .trim(), - "bar" - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + _test_pci_multiple_segments(&guest, MAX_NUM_PCI_SEGMENTS, 15u16); } #[test] From 38dc35cc337e5e079b2a222f9f43cabcab7a037f Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 7 Mar 2026 15:49:27 -0800 Subject: [PATCH 0793/1893] 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 --- cloud-hypervisor/tests/integration.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 745128ce6c..a064a5bbb0 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -15006,4 +15006,16 @@ mod common_cvm { GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); _test_virtio_net_ctrl_queue(&guest); } + + #[test] + fn test_pci_multiple_segments() { + // Use 8 segments to test the multiple segment support since it's more than the default 6 + // supported by Linux + // IGVM file used by Sev-Snp Guest now support up to 8 segments, so we can use 8 segments for testing. + let num_pci_segments: u16 = 8; + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_pci_multiple_segments(&guest, num_pci_segments, 5); + } } From 3b56ec240a32af6456456e30fdfb99050d4ca1d3 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 7 Mar 2026 15:55:24 -0800 Subject: [PATCH 0794/1893] 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 --- cloud-hypervisor/tests/integration.rs | 74 +++++++++++++-------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index a064a5bbb0..2fc61ef51c 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2768,6 +2768,41 @@ fn _test_pci_multiple_segments( handle_child_output(r, &output); } +fn _test_direct_kernel_boot(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + guest.validate_cpu_count(None); + guest.validate_memory(None); + + let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); + assert_eq!( + guest + .ssh_command(&grep_cmd) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 12 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + mod common_parallel { use std::cmp; use std::fs::{File, OpenOptions, copy}; @@ -3250,43 +3285,8 @@ mod common_parallel { #[test] fn test_direct_kernel_boot() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let kernel_path = direct_kernel_boot_path(); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); - - let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); - assert_eq!( - guest - .ssh_command(&grep_cmd) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 12 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + _test_direct_kernel_boot(&guest); } #[test] From af4a14fa70d0f21e6386f0d7db81757bbd65cc3e Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 7 Mar 2026 15:56:23 -0800 Subject: [PATCH 0795/1893] 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 --- cloud-hypervisor/tests/integration.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 2fc61ef51c..fed59fbe7e 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -15018,4 +15018,12 @@ mod common_cvm { GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); _test_pci_multiple_segments(&guest, num_pci_segments, 5); } + + #[test] + fn test_direct_kernel_boot() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_direct_kernel_boot(&guest); + } } From ec730fde21701f3a466bac816702e19ad4ab3331 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 7 Mar 2026 22:50:03 -0800 Subject: [PATCH 0796/1893] 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 --- cloud-hypervisor/tests/integration.rs | 692 +++++++++++++------------- 1 file changed, 337 insertions(+), 355 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index fed59fbe7e..de2eee6fbe 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use std::ffi::CStr; -use std::fs::OpenOptions; +use std::fs::{File, OpenOptions, copy}; use std::io::{BufRead, Read, Seek, SeekFrom, Write}; use std::net::TcpListener; use std::os::unix::io::AsRawFd; @@ -21,8 +21,9 @@ use std::string::String; use std::sync::mpsc::Receiver; use std::sync::{Mutex, mpsc}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use std::{fs, io, thread}; +use std::{cmp, fs, io, thread}; +use block::ImageType; use net_util::MacAddr; use test_infra::*; use vmm_sys_util::tempdir::TempDir; @@ -2803,13 +2804,305 @@ fn _test_direct_kernel_boot(guest: &Guest) { handle_child_output(r, &output); } +fn _test_virtio_block( + guest: &Guest, + disable_io_uring: bool, + disable_aio: bool, + verify_os_disk: bool, + backing_files: bool, + image_type: ImageType, +) { + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + + let mut blk_file_path = workload_path; + blk_file_path.push("blk.img"); + + let initial_backing_checksum = if verify_os_disk { + compute_backing_checksum(guest.disk_config.disk(DiskType::OperatingSystem).unwrap()) + } else { + None + }; + assert!( + guest.num_cpu >= 4, + "_test_virtio_block requires at least 4 CPUs to match num_queues=4" + ); + let mut cloud_child = GuestCommand::new(guest) + .default_cpus() + .args(["--memory", "size=512M,shared=on"]) + .default_kernel_cmdline() + .args([ + "--disk", + format!( + "path={},backing_files={},image_type={image_type}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), + if backing_files { "on" } else { "off" }, + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!( + "path={},readonly=on,direct=on,num_queues=4,_disable_io_uring={},_disable_aio={}", + blk_file_path.to_str().unwrap(), + disable_io_uring, + disable_aio, + ) + .as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Check both if /dev/vdc exists and if the block size is 16M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 16M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Check both if /dev/vdc exists and if this block is RO. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | awk '{print $5}'") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Check if the number of queues is 4. + assert_eq!( + guest + .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4 + ); + }); + + if verify_os_disk { + // Use clean shutdown to allow cloud-hypervisor to clear + // the dirty bit in the QCOW2 v3 image. + kill_child(&mut cloud_child); + } else { + let _ = cloud_child.kill(); + } + let output = cloud_child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + + if verify_os_disk { + disk_check_consistency( + guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), + initial_backing_checksum, + ); + } +} + +fn compute_backing_checksum( + path_or_image_name: impl AsRef, +) -> Option<(std::path::PathBuf, String, u32)> { + let path = resolve_disk_path(path_or_image_name); + + let mut file = File::open(&path).ok()?; + if !matches!( + block::detect_image_type(&mut file).ok()?, + block::ImageType::Qcow2 + ) { + return None; + } + + let info = get_image_info(&path)?; + + let backing_file = info["backing-filename"].as_str()?; + let backing_path = if std::path::Path::new(backing_file).is_absolute() { + std::path::PathBuf::from(backing_file) + } else { + path.parent() + .unwrap_or_else(|| std::path::Path::new(".")) + .join(backing_file) + }; + + let backing_info = get_image_info(&backing_path)?; + let backing_format = backing_info["format"].as_str()?.to_string(); + let mut file = File::open(&backing_path).ok()?; + let file_size = file.metadata().ok()?.len(); + let checksum = compute_file_checksum(&mut file, file_size); + + Some((backing_path, backing_format, checksum)) +} + +/// Uses `qemu-img check` to verify disk image consistency. +/// +/// Supported formats are `qcow2` (compressed and uncompressed), +/// `vhdx`, `qed`, `parallels`, `vmdk`, and `vdi`. See man page +/// for more details. +/// +/// It takes either a full path to the image or just the name of +/// the image located in the `workloads` directory. +/// +/// For QCOW2 images with backing files, also verifies the backing file +/// integrity and checks that the backing file hasn't been modified +/// during the test. +/// +/// For QCOW2 v3 images, also verifies the dirty bit is cleared. +fn disk_check_consistency( + path_or_image_name: impl AsRef, + initial_backing_checksum: Option<(std::path::PathBuf, String, u32)>, +) { + let path = resolve_disk_path(path_or_image_name); + let output = run_qemu_img(&path, &["check"], None); + + assert!( + output.status.success(), + "qemu-img check failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + match check_dirty_flag(&path) { + Ok(Some(dirty)) => { + assert!(!dirty, "QCOW2 image shutdown unclean"); + } + Ok(None) => {} // Not a QCOW2 v3 image, skip dirty flag check + Err(e) => panic!("Failed to check dirty flag: {e}"), + } + + if let Some((backing_path, format, initial_checksum)) = initial_backing_checksum { + if format.parse::().ok() != Some(block::qcow::ImageType::Raw) { + let output = run_qemu_img(&backing_path, &["check"], None); + + assert!( + output.status.success(), + "qemu-img check of backing file failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + let mut file = File::open(&backing_path).unwrap(); + let file_size = file.metadata().unwrap().len(); + assert_eq!( + initial_checksum, + compute_file_checksum(&mut file, file_size) + ); + } +} + +fn run_qemu_img( + path: &std::path::Path, + args: &[&str], + trailing_args: Option<&[&str]>, +) -> std::process::Output { + let mut cmd = std::process::Command::new("qemu-img"); + cmd.arg(args[0]) + .args(&args[1..]) + .arg(path.to_str().unwrap()); + if let Some(extra) = trailing_args { + cmd.args(extra); + } + cmd.output().unwrap() +} + +fn get_image_info(path: &std::path::Path) -> Option { + let output = run_qemu_img(path, &["info", "-U", "--output=json"], None); + + output.status.success().then_some(())?; + serde_json::from_slice(&output.stdout).ok() +} + +fn get_qcow2_v3_info(path: &Path) -> Result, String> { + let info = get_image_info(path) + .ok_or_else(|| format!("qemu-img info failed for {}", path.display()))?; + if info["format"].as_str() != Some("qcow2") { + return Ok(None); + } + // QCOW2 v3 has compat "1.1", v2 has "0.10" + if info["format-specific"]["data"]["compat"].as_str() != Some("1.1") { + return Ok(None); + } + Ok(Some(info)) +} + +fn check_dirty_flag(path: &Path) -> Result, String> { + Ok(get_qcow2_v3_info(path)?.and_then(|info| info["dirty-flag"].as_bool())) +} + +fn check_corrupt_flag(path: &Path) -> Result, String> { + Ok(get_qcow2_v3_info(path)? + .and_then(|info| info["format-specific"]["data"]["corrupt"].as_bool())) +} + +const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72; + +fn set_corrupt_flag(path: &Path, corrupt: bool) -> io::Result<()> { + let mut file = OpenOptions::new().read(true).write(true).open(path)?; + + file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; + let mut buf = [0u8; 8]; + file.read_exact(&mut buf)?; + let mut features = u64::from_be_bytes(buf); + + if corrupt { + features |= 0x02; + } else { + features &= !0x02; + } + + file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; + file.write_all(&features.to_be_bytes())?; + file.sync_all()?; + Ok(()) +} + +fn resolve_disk_path(path_or_image_name: impl AsRef) -> std::path::PathBuf { + if path_or_image_name.as_ref().exists() { + // A full path is provided + path_or_image_name.as_ref().to_path_buf() + } else { + // An image name is provided + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + workload_path.as_path().join(path_or_image_name.as_ref()) + } +} + +fn compute_file_checksum(reader: &mut dyn std::io::Read, size: u64) -> u32 { + // Read first 16MB or entire data if smaller + let read_size = cmp::min(size, 16 * 1024 * 1024) as usize; + + let mut buffer = vec![0u8; read_size]; + reader.read_exact(&mut buffer).unwrap(); + + // DJB2 hash + let mut hash: u32 = 5381; + for byte in buffer.iter() { + hash = hash.wrapping_mul(33).wrapping_add(*byte as u32); + } + hash +} + +fn make_virtio_block_guest(factory: &GuestFactory, image_name: &str) -> Guest { + let disk_config = UbuntuDiskConfig::new(image_name.to_string()); + factory.create_guest(Box::new(disk_config)).with_cpu(4) +} + mod common_parallel { - use std::cmp; - use std::fs::{File, OpenOptions, copy}; use std::io::{self, SeekFrom}; use std::process::Command; - use block::ImageType; use test_infra::GuestFactory; use crate::*; @@ -3335,224 +3628,27 @@ mod common_parallel { handle_child_output(r, &output); } - fn _test_virtio_block( - image_name: &str, - disable_io_uring: bool, - disable_aio: bool, - verify_os_disk: bool, - backing_files: bool, - image_type: ImageType, - ) { - let disk_config = UbuntuDiskConfig::new(image_name.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - - let mut blk_file_path = workload_path; - blk_file_path.push("blk.img"); - - let kernel_path = direct_kernel_boot_path(); - - let initial_backing_checksum = if verify_os_disk { - compute_backing_checksum(guest.disk_config.disk(DiskType::OperatingSystem).unwrap()) - } else { - None - }; - - let mut cloud_child = GuestCommand::new(&guest) - .args(["--cpus", "boot=4"]) - .args(["--memory", "size=512M,shared=on"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args([ - "--disk", - format!( - "path={},backing_files={},image_type={image_type}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), - if backing_files { "on"} else {"off"}, - ) - .as_str(), - format!( - "path={}", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - format!( - "path={},readonly=on,direct=on,num_queues=4,_disable_io_uring={},_disable_aio={}", - blk_file_path.to_str().unwrap(), - disable_io_uring, - disable_aio, - ) - .as_str(), - ]) - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Check both if /dev/vdc exists and if the block size is 16M. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 16M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Check both if /dev/vdc exists and if this block is RO. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | awk '{print $5}'") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Check if the number of queues is 4. - assert_eq!( - guest - .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 4 - ); - }); - - if verify_os_disk { - // Use clean shutdown to allow cloud-hypervisor to clear - // the dirty bit in the QCOW2 v3 image. - kill_child(&mut cloud_child); - } else { - let _ = cloud_child.kill(); - } - let output = cloud_child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - - if verify_os_disk { - disk_check_consistency( - guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), - initial_backing_checksum, - ); - } - } - #[test] fn test_virtio_block_io_uring() { - _test_virtio_block(FOCAL_IMAGE_NAME, false, true, false, false, ImageType::Raw); + let guest = + make_virtio_block_guest(&GuestFactory::new_regular_guest_factory(), FOCAL_IMAGE_NAME); + _test_virtio_block(&guest, false, true, false, false, ImageType::Raw); } #[test] fn test_virtio_block_aio() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, false, false, false, ImageType::Raw); + let guest = + make_virtio_block_guest(&GuestFactory::new_regular_guest_factory(), FOCAL_IMAGE_NAME) + .with_cpu(4); + _test_virtio_block(&guest, true, false, false, false, ImageType::Raw); } #[test] fn test_virtio_block_sync() { - _test_virtio_block(FOCAL_IMAGE_NAME, true, true, false, false, ImageType::Raw); - } - - fn run_qemu_img( - path: &std::path::Path, - args: &[&str], - trailing_args: Option<&[&str]>, - ) -> std::process::Output { - let mut cmd = std::process::Command::new("qemu-img"); - cmd.arg(args[0]) - .args(&args[1..]) - .arg(path.to_str().unwrap()); - if let Some(extra) = trailing_args { - cmd.args(extra); - } - cmd.output().unwrap() - } - - fn get_image_info(path: &std::path::Path) -> Option { - let output = run_qemu_img(path, &["info", "-U", "--output=json"], None); - - output.status.success().then_some(())?; - serde_json::from_slice(&output.stdout).ok() - } - - fn get_qcow2_v3_info(path: &Path) -> Result, String> { - let info = get_image_info(path) - .ok_or_else(|| format!("qemu-img info failed for {}", path.display()))?; - if info["format"].as_str() != Some("qcow2") { - return Ok(None); - } - // QCOW2 v3 has compat "1.1", v2 has "0.10" - if info["format-specific"]["data"]["compat"].as_str() != Some("1.1") { - return Ok(None); - } - Ok(Some(info)) - } - - fn check_dirty_flag(path: &Path) -> Result, String> { - Ok(get_qcow2_v3_info(path)?.and_then(|info| info["dirty-flag"].as_bool())) - } - - fn check_corrupt_flag(path: &Path) -> Result, String> { - Ok(get_qcow2_v3_info(path)? - .and_then(|info| info["format-specific"]["data"]["corrupt"].as_bool())) - } - - const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72; - - fn set_corrupt_flag(path: &Path, corrupt: bool) -> io::Result<()> { - let mut file = OpenOptions::new().read(true).write(true).open(path)?; - - file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; - let mut buf = [0u8; 8]; - file.read_exact(&mut buf)?; - let mut features = u64::from_be_bytes(buf); - - if corrupt { - features |= 0x02; - } else { - features &= !0x02; - } - - file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; - file.write_all(&features.to_be_bytes())?; - file.sync_all()?; - Ok(()) - } - - fn resolve_disk_path(path_or_image_name: impl AsRef) -> std::path::PathBuf { - if path_or_image_name.as_ref().exists() { - // A full path is provided - path_or_image_name.as_ref().to_path_buf() - } else { - // An image name is provided - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - workload_path.as_path().join(path_or_image_name.as_ref()) - } - } - - fn compute_file_checksum(reader: &mut dyn std::io::Read, size: u64) -> u32 { - // Read first 16MB or entire data if smaller - let read_size = cmp::min(size, 16 * 1024 * 1024) as usize; - - let mut buffer = vec![0u8; read_size]; - reader.read_exact(&mut buffer).unwrap(); - - // DJB2 hash - let mut hash: u32 = 5381; - for byte in buffer.iter() { - hash = hash.wrapping_mul(33).wrapping_add(*byte as u32); - } - hash + let guest = + make_virtio_block_guest(&GuestFactory::new_regular_guest_factory(), FOCAL_IMAGE_NAME) + .with_cpu(4); + _test_virtio_block(&guest, true, true, false, false, ImageType::Raw); } #[test] @@ -3605,164 +3701,58 @@ mod common_parallel { assert_eq!(position, 16 * 1024 * 1024); } - fn compute_backing_checksum( - path_or_image_name: impl AsRef, - ) -> Option<(std::path::PathBuf, String, u32)> { - let path = resolve_disk_path(path_or_image_name); - - let mut file = File::open(&path).ok()?; - if !matches!( - block::detect_image_type(&mut file).ok()?, - block::ImageType::Qcow2 - ) { - return None; - } - - let info = get_image_info(&path)?; - - let backing_file = info["backing-filename"].as_str()?; - let backing_path = if std::path::Path::new(backing_file).is_absolute() { - std::path::PathBuf::from(backing_file) - } else { - path.parent() - .unwrap_or_else(|| std::path::Path::new(".")) - .join(backing_file) - }; - - let backing_info = get_image_info(&backing_path)?; - let backing_format = backing_info["format"].as_str()?.to_string(); - let mut file = File::open(&backing_path).ok()?; - let file_size = file.metadata().ok()?.len(); - let checksum = compute_file_checksum(&mut file, file_size); - - Some((backing_path, backing_format, checksum)) - } - - /// Uses `qemu-img check` to verify disk image consistency. - /// - /// Supported formats are `qcow2` (compressed and uncompressed), - /// `vhdx`, `qed`, `parallels`, `vmdk`, and `vdi`. See man page - /// for more details. - /// - /// It takes either a full path to the image or just the name of - /// the image located in the `workloads` directory. - /// - /// For QCOW2 images with backing files, also verifies the backing file - /// integrity and checks that the backing file hasn't been modified - /// during the test. - /// - /// For QCOW2 v3 images, also verifies the dirty bit is cleared. - fn disk_check_consistency( - path_or_image_name: impl AsRef, - initial_backing_checksum: Option<(std::path::PathBuf, String, u32)>, - ) { - let path = resolve_disk_path(path_or_image_name); - let output = run_qemu_img(&path, &["check"], None); - - assert!( - output.status.success(), - "qemu-img check failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - match check_dirty_flag(&path) { - Ok(Some(dirty)) => { - assert!(!dirty, "QCOW2 image shutdown unclean"); - } - Ok(None) => {} // Not a QCOW2 v3 image, skip dirty flag check - Err(e) => panic!("Failed to check dirty flag: {e}"), - } - - if let Some((backing_path, format, initial_checksum)) = initial_backing_checksum { - if format.parse::().ok() != Some(block::qcow::ImageType::Raw) { - let output = run_qemu_img(&backing_path, &["check"], None); - - assert!( - output.status.success(), - "qemu-img check of backing file failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - let mut file = File::open(&backing_path).unwrap(); - let file_size = file.metadata().unwrap().len(); - assert_eq!( - initial_checksum, - compute_file_checksum(&mut file, file_size) - ); - } - } - #[test] fn test_virtio_block_qcow2() { - _test_virtio_block( - JAMMY_IMAGE_NAME_QCOW2, - false, - false, - true, - false, - ImageType::Qcow2, - ); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); } #[test] fn test_virtio_block_qcow2_zlib() { - _test_virtio_block( - JAMMY_IMAGE_NAME_QCOW2_ZLIB, - false, - false, - true, - false, - ImageType::Qcow2, - ); + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2_ZLIB.to_string()); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); } #[test] fn test_virtio_block_qcow2_zstd() { - _test_virtio_block( + let guest = make_virtio_block_guest( + &GuestFactory::new_regular_guest_factory(), JAMMY_IMAGE_NAME_QCOW2_ZSTD, - false, - false, - true, - false, - ImageType::Qcow2, ); + _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); } #[test] fn test_virtio_block_qcow2_backing_zstd_file() { - _test_virtio_block( + let guest = make_virtio_block_guest( + &GuestFactory::new_regular_guest_factory(), JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE, - false, - false, - true, - true, - ImageType::Qcow2, ); + _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); } #[test] fn test_virtio_block_qcow2_backing_uncompressed_file() { - _test_virtio_block( + let guest = make_virtio_block_guest( + &GuestFactory::new_regular_guest_factory(), JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE, - false, - false, - true, - true, - ImageType::Qcow2, ); + _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); } #[test] fn test_virtio_block_qcow2_backing_raw_file() { - _test_virtio_block( + let guest = make_virtio_block_guest( + &GuestFactory::new_regular_guest_factory(), JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE, - false, - false, - true, - true, - ImageType::Qcow2, ); + _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); } /// Configuration for QCOW2 multiqueue test image setup @@ -4642,15 +4632,11 @@ mod common_parallel { .arg(vhd_file_path.to_str().unwrap()) .output() .expect("Expect generating VHD image from RAW image"); - - _test_virtio_block( + let guest = make_virtio_block_guest( + &GuestFactory::new_regular_guest_factory(), FOCAL_IMAGE_NAME_VHD, - false, - false, - false, - false, - ImageType::FixedVhd, ); + _test_virtio_block(&guest, false, false, false, false, ImageType::FixedVhd); } #[test] @@ -4673,15 +4659,11 @@ mod common_parallel { .arg(vhdx_file_path.to_str().unwrap()) .output() .expect("Expect generating dynamic VHDx image from RAW image"); - - _test_virtio_block( + let guest = make_virtio_block_guest( + &GuestFactory::new_regular_guest_factory(), FOCAL_IMAGE_NAME_VHDX, - false, - false, - true, - false, - ImageType::Vhdx, ); + _test_virtio_block(&guest, false, false, true, false, ImageType::Vhdx); } #[test] From 297b683fcb6c9a9b225d04213dc04b0faf8f9715 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 7 Mar 2026 23:07:59 -0800 Subject: [PATCH 0797/1893] 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 --- cloud-hypervisor/tests/integration.rs | 83 +++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index de2eee6fbe..8f6dec37a9 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -15008,4 +15008,87 @@ mod common_cvm { GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); _test_direct_kernel_boot(&guest); } + + #[test] + fn test_virtio_block_io_uring() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + FOCAL_IMAGE_NAME, + ); + _test_virtio_block(&guest, false, true, false, false, ImageType::Raw); + } + + #[test] + fn test_virtio_block_aio() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + FOCAL_IMAGE_NAME, + ); + _test_virtio_block(&guest, true, false, false, false, ImageType::Raw); + } + + #[test] + fn test_virtio_block_sync() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + FOCAL_IMAGE_NAME, + ); + _test_virtio_block(&guest, true, true, false, false, ImageType::Raw); + } + + #[test] + fn test_virtio_block_qcow2() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2, + ); + _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_zlib() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_ZLIB, + ); + _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_zstd() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_ZSTD, + ); + _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_backing_zstd_file() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE, + ); + + _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_backing_uncompressed_file() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE, + ); + + _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_backing_raw_file() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE, + ); + _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); + } } From a15bfcfee6ce7dc1e42b131ea9d1a7ec1722ad8a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:13:01 +0100 Subject: [PATCH 0798/1893] 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 --- block/src/disk_file.rs | 34 ++++++++++++++++++++++++++++++++++ block/src/lib.rs | 1 + 2 files changed, 35 insertions(+) create mode 100644 block/src/disk_file.rs diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs new file mode 100644 index 0000000000..30efaff551 --- /dev/null +++ b/block/src/disk_file.rs @@ -0,0 +1,34 @@ +// 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, new_async_io +//! Resizable +//! \ / +//! AsyncFullDiskFile: FullDiskFile + AsyncDiskFile +//! ``` +//! +//! Readonly accessors take `&self`. Only [`Resizable::resize`] requires +//! `&mut self`. Errors are returned as [`BlockResult`]. diff --git a/block/src/lib.rs b/block/src/lib.rs index 4a6ea4979d..288db3fbfd 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -9,6 +9,7 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause pub mod async_io; +pub mod disk_file; pub mod error; pub mod fcntl; pub mod fixed_vhd; From 85e4e5027c42afa79273221935ff8d1cc26c12d7 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:13:47 +0100 Subject: [PATCH 0799/1893] 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 --- block/src/disk_file.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 30efaff551..3f7edfabf1 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -32,3 +32,13 @@ //! //! Readonly accessors take `&self`. Only [`Resizable::resize`] requires //! `&mut self`. Errors are returned as [`BlockResult`]. + +use std::fmt::Debug; + +use crate::BlockResult; + +/// 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; +} From 2df731b8d3817de0aab0732cdfccdb9e532b1f96 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:14:57 +0100 Subject: [PATCH 0800/1893] 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 --- block/src/disk_file.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 3f7edfabf1..1dbaf19458 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -42,3 +42,9 @@ pub trait DiskSize: Send + Debug { /// Virtual size of the disk image in bytes (reported capacity). fn logical_size(&self) -> BlockResult; } + +/// 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; +} From 2fa877e087b86c4d03aacbb46b82827ab6b72336 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:19:00 +0100 Subject: [PATCH 0801/1893] 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 --- block/src/disk_file.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 1dbaf19458..31c5ee085e 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -36,6 +36,7 @@ use std::fmt::Debug; use crate::BlockResult; +use crate::async_io::BorrowedDiskFd; /// Reported capacity of a disk image. pub trait DiskSize: Send + Debug { @@ -48,3 +49,9 @@ pub trait PhysicalSize: Send + Debug { /// Actual bytes occupied on the host filesystem. fn physical_size(&self) -> BlockResult; } + +/// 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<'_>; +} From b98a5cc58dcd9951e1333d38a59a82f910158cca Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:22:55 +0100 Subject: [PATCH 0802/1893] 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 --- block/src/disk_file.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 31c5ee085e..9dd52be6dc 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -35,8 +35,8 @@ use std::fmt::Debug; -use crate::BlockResult; use crate::async_io::BorrowedDiskFd; +use crate::{BlockResult, DiskTopology}; /// Reported capacity of a disk image. pub trait DiskSize: Send + Debug { @@ -55,3 +55,13 @@ 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() + } +} From a0a5718b420c73ca233802aa1b775178509eca5b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:24:11 +0100 Subject: [PATCH 0803/1893] 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 --- block/src/disk_file.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 9dd52be6dc..3b3aa0fec3 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -65,3 +65,19 @@ pub trait Geometry: Send + Debug { 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 + } +} From da1b1044937e946a3d382737a1986cede158e822 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:25:01 +0100 Subject: [PATCH 0804/1893] 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 --- block/src/disk_file.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 3b3aa0fec3..69a1152c85 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -81,3 +81,12 @@ pub trait SparseCapable: Send + Debug { 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<()>; +} From 5d50d646715b9447ab39c61d8a21ced3a857fb2d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:35:13 +0100 Subject: [PATCH 0805/1893] block: disk_file: Add DiskFile supertrait Bundles DiskSize and Geometry as the universal disk capabilities every format must implement. Adds Sync so that Arc can be shared across threads for concurrent readonly access. Signed-off-by: Anatol Belski --- block/src/disk_file.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 69a1152c85..632b370e7c 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -90,3 +90,10 @@ 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` can be shared +/// across threads for concurrent readonly access. +pub trait DiskFile: DiskSize + Geometry + Sync {} From 50741cca2a7bfaf4814eb4b95d4a4025042b90c9 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:45:16 +0100 Subject: [PATCH 0806/1893] 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 --- block/src/disk_file.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 632b370e7c..2f280e3315 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -35,7 +35,7 @@ use std::fmt::Debug; -use crate::async_io::BorrowedDiskFd; +use crate::async_io::{AsyncIo, BorrowedDiskFd}; use crate::{BlockResult, DiskTopology}; /// Reported capacity of a disk image. @@ -97,3 +97,33 @@ pub trait Resizable: Send + Debug { /// `Sync` is required so that `Arc` can be shared /// across threads for concurrent readonly access. pub trait DiskFile: DiskSize + Geometry + Sync {} + +/// 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`) 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` (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>; + + /// 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 new_async_io(&self, ring_depth: u32) -> BlockResult>; +} From 8623edb8aae2c4acaff56afd62c2b08f32521b51 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 22:56:44 +0100 Subject: [PATCH 0807/1893] 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 --- block/src/disk_file.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 2f280e3315..f76a99662f 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -98,6 +98,18 @@ pub trait Resizable: Send + Debug { /// 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 FullDiskFile for T {} + /// Extended disk file trait for virtio queue workers. /// /// Adds cloning and async I/O construction on top of [`DiskFile`]. From dac8707c35e1cddf13c7df9848d31e3e05d36451 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 22:57:36 +0100 Subject: [PATCH 0808/1893] 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 --- block/src/disk_file.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index f76a99662f..9e057bd05b 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -139,3 +139,20 @@ pub trait AsyncDiskFile: DiskFile + Unpin { /// fallback implementations) may ignore this value. fn new_async_io(&self, ring_depth: u32) -> BlockResult>; } + +/// 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 AsyncFullDiskFile for T {} From ad789024f906c8610cb3b338dd71210cf27e0b90 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:55:47 +0100 Subject: [PATCH 0809/1893] 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 --- block/src/qcow_sync.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index e1ad08f4ab..73e7c89ec0 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -7,7 +7,7 @@ use std::collections::VecDeque; use std::fs::File; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; use std::sync::Arc; -use std::{io, ptr, slice}; +use std::{fmt, io, ptr, slice}; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; @@ -180,6 +180,15 @@ pub struct QcowDiskSync { data_raw_file: QcowRawFile, } +impl fmt::Debug for QcowDiskSync { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("QcowDiskSync") + .field("sparse", &self.sparse) + .field("has_backing", &self.backing_file.is_some()) + .finish_non_exhaustive() + } +} + impl QcowDiskSync { pub fn new( file: File, From 0a7b6b089bcbb243a3d4b2e3b15ce9c81595da17 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:56:32 +0100 Subject: [PATCH 0810/1893] 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 --- block/src/qcow_sync.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 73e7c89ec0..ce06ad6816 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -15,6 +15,7 @@ use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; +use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, @@ -277,6 +278,12 @@ impl Drop for QcowDiskSync { } } +impl disk_file::DiskSize for QcowDiskSync { + fn logical_size(&self) -> BlockResult { + Ok(self.metadata.virtual_size()) + } +} + pub struct QcowSync { metadata: Arc, data_file: QcowRawFile, From 4b731ee771387d7efec2cd12e5261a07dfa10431 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:57:38 +0100 Subject: [PATCH 0811/1893] 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 --- block/src/qcow_sync.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index ce06ad6816..3ff5a64723 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -284,6 +284,12 @@ impl disk_file::DiskSize for QcowDiskSync { } } +impl disk_file::PhysicalSize for QcowDiskSync { + fn physical_size(&self) -> BlockResult { + Ok(self.data_raw_file.physical_size()?) + } +} + pub struct QcowSync { metadata: Arc, data_file: QcowRawFile, From b305a7670bb88712ae9e0c1d3fe48f49d38b1b76 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:58:26 +0100 Subject: [PATCH 0812/1893] 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 --- block/src/qcow_sync.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 3ff5a64723..a2898a49bc 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -290,6 +290,12 @@ impl disk_file::PhysicalSize for QcowDiskSync { } } +impl disk_file::DiskFd for QcowDiskSync { + fn fd(&self) -> BorrowedDiskFd<'_> { + BorrowedDiskFd::new(self.data_raw_file.as_fd().as_raw_fd()) + } +} + pub struct QcowSync { metadata: Arc, data_file: QcowRawFile, From 63f5e6e97f781563679b271df2da6f743f1b7794 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 20:59:46 +0100 Subject: [PATCH 0813/1893] 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 --- block/src/qcow_sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index a2898a49bc..56c19307f3 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -296,6 +296,8 @@ impl disk_file::DiskFd for QcowDiskSync { } } +impl disk_file::Geometry for QcowDiskSync {} + pub struct QcowSync { metadata: Arc, data_file: QcowRawFile, From 554562fef24d49cfab2dccee5e163c8da5bb5e7d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 21:06:48 +0100 Subject: [PATCH 0814/1893] 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 --- block/src/qcow_sync.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 56c19307f3..c75e895e8b 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -298,6 +298,16 @@ impl disk_file::DiskFd for QcowDiskSync { impl disk_file::Geometry for QcowDiskSync {} +impl disk_file::SparseCapable for QcowDiskSync { + fn supports_sparse_operations(&self) -> bool { + true + } + + fn supports_zero_flag(&self) -> bool { + true + } +} + pub struct QcowSync { metadata: Arc, data_file: QcowRawFile, From f35bec19e8b2d5085874d2c2bd7eafa77d338c79 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 21:20:08 +0100 Subject: [PATCH 0815/1893] 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 --- block/src/error.rs | 3 +++ block/src/qcow_sync.rs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/block/src/error.rs b/block/src/error.rs index 4b89bbb212..ebaa33ec5b 100644 --- a/block/src/error.rs +++ b/block/src/error.rs @@ -68,6 +68,8 @@ pub enum ErrorOp { DetectImageType, /// Duplicating a backing-file descriptor. DupBackingFd, + /// Resizing a disk image. + Resize, } impl Display for ErrorOp { @@ -76,6 +78,7 @@ impl Display for ErrorOp { Self::Open => write!(f, "open"), Self::DetectImageType => write!(f, "detect_image_type"), Self::DupBackingFd => write!(f, "dup_backing_fd"), + Self::Resize => write!(f, "resize"), } } } diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index c75e895e8b..4c2611a5a4 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -308,6 +308,24 @@ impl disk_file::SparseCapable for QcowDiskSync { } } +impl disk_file::Resizable for QcowDiskSync { + 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 file", + )), + ) + .with_op(ErrorOp::Resize)); + } + self.metadata.resize(size).map_err(|e| { + BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)) + .with_op(ErrorOp::Resize) + }) + } +} + pub struct QcowSync { metadata: Arc, data_file: QcowRawFile, From 1fc8e4adb6c7aebd890ef270e7006ca749b580af Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 21:25:40 +0100 Subject: [PATCH 0816/1893] block: qcow_sync: impl DiskFile for QcowDiskSync Marker impl binding the DiskSize and HasTopology supertraits. Signed-off-by: Anatol Belski --- block/src/qcow_sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 4c2611a5a4..e9b64726ee 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -326,6 +326,8 @@ impl disk_file::Resizable for QcowDiskSync { } } +impl disk_file::DiskFile for QcowDiskSync {} + pub struct QcowSync { metadata: Arc, data_file: QcowRawFile, From fe6e3e8feff2fc5b9266c1cfb4e8ee18d30c14ec Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 21:30:43 +0100 Subject: [PATCH 0817/1893] 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 --- block/src/qcow_sync.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index e9b64726ee..e539754fcb 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -328,6 +328,28 @@ impl disk_file::Resizable for QcowDiskSync { impl disk_file::DiskFile for QcowDiskSync {} +impl disk_file::AsyncDiskFile for QcowDiskSync { + fn try_clone(&self) -> BlockResult> { + Ok(Box::new(QcowDiskSync { + 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(), + })) + } + + // ring_depth is unused - this sync backend performs blocking I/O + // instead of submitting to an async ring. + fn new_async_io(&self, _ring_depth: u32) -> BlockResult> { + Ok(Box::new(QcowSync::new( + Arc::clone(&self.metadata), + self.data_raw_file.clone(), + self.backing_file.as_ref().map(Arc::clone), + self.sparse, + ))) + } +} + pub struct QcowSync { metadata: Arc, data_file: QcowRawFile, From 264013b4243c00355dab68aedd7e9c868c104326 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 23:45:35 +0100 Subject: [PATCH 0818/1893] block: disk_file: Add DiskBackend dispatch enum Introduce DiskBackend with two variants: - Legacy: wraps Box for existing formats - Next: wraps Box 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 directly. Signed-off-by: Anatol Belski --- block/src/disk_file.rs | 83 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 9e057bd05b..fea7243abb 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -34,8 +34,10 @@ //! `&mut self`. Errors are returned as [`BlockResult`]. use std::fmt::Debug; +use std::io; -use crate::async_io::{AsyncIo, BorrowedDiskFd}; +use crate::async_io::{self, AsyncIo, BorrowedDiskFd}; +use crate::error::{BlockError, BlockErrorKind}; use crate::{BlockResult, DiskTopology}; /// Reported capacity of a disk image. @@ -156,3 +158,82 @@ pub trait AsyncFullDiskFile: FullDiskFile + AsyncDiskFile {} /// Blanket implementation: any type implementing both [`FullDiskFile`] /// and [`AsyncDiskFile`] automatically satisfies [`AsyncFullDiskFile`]. impl AsyncFullDiskFile for T {} + +/// A disk backend that dispatches to either the existing [`async_io::DiskFile`] +/// trait or the next-generation [`AsyncFullDiskFile`] trait. +pub enum DiskBackend { + /// Existing disk file backend (raw, vhd, vhdx, etc.). + Legacy(Box), + /// Next-generation disk file backend (qcow2, and more formats as they migrate). + Next(Box), +} + +impl DiskBackend { + pub fn logical_size(&mut self) -> BlockResult { + match self { + Self::Legacy(d) => d + .logical_size() + .map_err(|e| BlockError::new(BlockErrorKind::Io, io::Error::other(e))), + Self::Next(d) => d.logical_size(), + } + } + + pub fn physical_size(&mut self) -> BlockResult { + match self { + Self::Legacy(d) => d + .physical_size() + .map_err(|e| BlockError::new(BlockErrorKind::Io, io::Error::other(e))), + Self::Next(d) => d.physical_size(), + } + } + + pub fn topology(&mut self) -> DiskTopology { + match self { + Self::Legacy(d) => d.topology(), + Self::Next(d) => d.topology(), + } + } + + pub fn supports_sparse_operations(&self) -> bool { + match self { + Self::Legacy(d) => d.supports_sparse_operations(), + Self::Next(d) => d.supports_sparse_operations(), + } + } + + pub fn supports_zero_flag(&self) -> bool { + match self { + Self::Legacy(d) => d.supports_zero_flag(), + Self::Next(d) => d.supports_zero_flag(), + } + } + + pub fn fd(&mut self) -> BorrowedDiskFd<'_> { + match self { + Self::Legacy(d) => d.fd(), + Self::Next(d) => d.fd(), + } + } + + pub fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + match self { + Self::Legacy(d) => d + .new_async_io(ring_depth) + .map_err(|e| BlockError::new(BlockErrorKind::Io, io::Error::other(e))), + Self::Next(d) => d.new_async_io(ring_depth), + } + } + + pub fn resize(&mut self, new_size: u64) -> BlockResult<()> { + match self { + Self::Legacy(d) => d.resize(new_size).map_err(|e| match e { + async_io::DiskFileError::Unsupported => BlockError::new( + BlockErrorKind::UnsupportedFeature, + io::Error::other("resize not supported"), + ), + _ => BlockError::new(BlockErrorKind::Io, io::Error::other(e)), + }), + Self::Next(d) => d.resize(new_size), + } + } +} From b4dad66d35f13d8f398c4a4350ca85a4f9000e5d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 23:51:58 +0100 Subject: [PATCH 0819/1893] virtio-devices: vmm: fuzz: Switch to DiskBackend Change Block to hold DiskBackend instead of Box. 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 --- fuzz/fuzz_targets/block.rs | 3 ++- virtio-devices/src/block.rs | 10 ++++++---- vmm/src/device_manager.rs | 27 ++++++++++++++++----------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index 952011b55b..35d59c9850 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -16,6 +16,7 @@ use std::sync::Arc; use std::{ffi, io}; use block::async_io::DiskFile; +use block::disk_file::DiskBackend; use block::fcntl::LockGranularityChoice; use block::raw_sync::RawFileDiskSync; use libfuzzer_sys::{fuzz_target, Corpus}; @@ -56,7 +57,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { let queue_affinity = BTreeMap::new(); let mut block = Block::new( "tmp".to_owned(), - qcow_disk, + DiskBackend::Legacy(qcow_disk), PathBuf::from(""), false, false, diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 2adbff74f8..edd1327f45 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -18,7 +18,9 @@ use std::sync::{Arc, Barrier}; use std::{io, result}; use anyhow::anyhow; -use block::async_io::{AsyncIo, AsyncIoError, DiskFile, DiskFileError}; +use block::async_io::{AsyncIo, AsyncIoError}; +use block::disk_file::DiskBackend; +use block::error::BlockError; use block::fcntl::{LockError, LockGranularity, LockGranularityChoice, LockType, get_lock_state}; use block::{ ExecuteAsync, ExecuteError, Request, RequestType, VirtioBlockConfig, build_serial, fcntl, @@ -104,7 +106,7 @@ pub enum Error { #[error("Failed signal config interrupt")] ConfigChange(#[source] io::Error), #[error("Disk resize failed")] - DiskResize(#[source] DiskFileError), + DiskResize(#[source] BlockError), } pub type Result = result::Result; @@ -697,7 +699,7 @@ impl EpollHelperHandler for BlockEpollHandler { pub struct Block { common: VirtioCommon, id: String, - disk_image: Box, + disk_image: DiskBackend, disk_path: PathBuf, disk_nsectors: Arc, config: VirtioBlockConfig, @@ -727,7 +729,7 @@ impl Block { #[allow(clippy::too_many_arguments)] pub fn new( id: String, - mut disk_image: Box, + mut disk_image: DiskBackend, disk_path: PathBuf, read_only: bool, iommu: bool, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 958d3086b1..51a5e476a7 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -33,6 +33,7 @@ use arch::layout::{APIC_START, IOAPIC_SIZE, IOAPIC_START}; use arch::{DeviceType, MmioDeviceInfo}; use arch::{NumaNodes, layout}; use block::async_io::DiskFile; +use block::disk_file::DiskBackend; use block::error::BlockError; use block::fixed_vhd_sync::FixedVhdDiskSync; use block::qcow_sync::QcowDiskSync; @@ -2721,17 +2722,17 @@ impl DeviceManager { unreachable!("Checked in if statement above"); #[cfg(feature = "io_uring")] { - Box::new( + DiskBackend::Legacy(Box::new( FixedVhdDiskAsync::new(file) .map_err(DeviceManagerError::CreateFixedVhdDiskAsync)?, - ) as Box + ) as Box) } } else { info!("Using synchronous fixed VHD disk file"); - Box::new( + DiskBackend::Legacy(Box::new( FixedVhdDiskSync::new(file) .map_err(DeviceManagerError::CreateFixedVhdDiskSync)?, - ) as Box + ) as Box) } } ImageType::Raw => { @@ -2755,19 +2756,23 @@ impl DeviceManager { unreachable!("Checked in if statement above"); #[cfg(feature = "io_uring")] { - Box::new(RawFileDisk::new(file)) as Box + DiskBackend::Legacy( + Box::new(RawFileDisk::new(file)) as Box + ) } } else if !disk_cfg.disable_aio && self.aio_is_supported() { info!("Using asynchronous RAW disk file (aio)"); - Box::new(RawFileDiskAio::new(file)) as Box + DiskBackend::Legacy(Box::new(RawFileDiskAio::new(file)) as Box) } else { info!("Using synchronous RAW disk file"); - Box::new(RawFileDiskSync::new(file)) as Box + DiskBackend::Legacy( + Box::new(RawFileDiskSync::new(file)) as Box + ) } } ImageType::Qcow2 => { info!("Using synchronous QCOW2 disk file"); - Box::new( + DiskBackend::Next(Box::new( QcowDiskSync::new( file, disk_cfg.direct, @@ -2779,14 +2784,14 @@ impl DeviceManager { None => e, }) .map_err(DeviceManagerError::CreateQcowDiskSync)?, - ) as Box + )) } ImageType::Vhdx => { info!("Using synchronous VHDX disk file"); - Box::new( + DiskBackend::Legacy(Box::new( VhdxDiskSync::new(file) .map_err(DeviceManagerError::CreateFixedVhdxDiskSync)?, - ) as Box + ) as Box) } ImageType::Unknown => unreachable!(), }; From 0d062962acc3dde1b61f30ddd50fc62c46e5039c Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 12 Mar 2026 23:56:41 +0100 Subject: [PATCH 0820/1893] 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 --- block/src/qcow_sync.rs | 50 ++---------------------------------------- 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index e539754fcb..cce4c192d7 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -12,9 +12,7 @@ use std::{fmt, io, ptr, slice}; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; -use crate::async_io::{ - AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, -}; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::metadata::{ @@ -228,50 +226,6 @@ impl QcowDiskSync { } } -impl DiskFile for QcowDiskSync { - fn logical_size(&mut self) -> DiskFileResult { - Ok(self.metadata.virtual_size()) - } - - fn physical_size(&mut self) -> DiskFileResult { - self.data_raw_file - .physical_size() - .map_err(DiskFileError::Size) - } - - fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { - Ok(Box::new(QcowSync::new( - Arc::clone(&self.metadata), - self.data_raw_file.clone(), - self.backing_file.as_ref().map(Arc::clone), - self.sparse, - )) as Box) - } - - fn resize(&mut self, size: u64) -> DiskFileResult<()> { - if self.backing_file.is_some() { - return Err(DiskFileError::ResizeError(io::Error::other( - "resize not supported with backing file", - ))); - } - self.metadata - .resize(size) - .map_err(DiskFileError::ResizeError) - } - - fn supports_sparse_operations(&self) -> bool { - true - } - - fn supports_zero_flag(&self) -> bool { - true - } - - fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.data_raw_file.as_raw_fd()) - } -} - impl Drop for QcowDiskSync { fn drop(&mut self) { self.metadata.shutdown(); @@ -734,7 +688,7 @@ mod unit_tests { use vmm_sys_util::tempfile::TempFile; use super::*; - use crate::async_io::DiskFile; + use crate::disk_file::{AsyncDiskFile, DiskSize, Resizable}; use crate::qcow::{BackingFileConfig, ImageType, QcowFile, RawFile}; fn create_disk_with_data( From 5724a0189c86417660d11a9c74c32eebd786773d Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 19 Mar 2026 16:26:59 +0000 Subject: [PATCH 0821/1893] 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 --- cloud-hypervisor/tests/integration.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 8f6dec37a9..3339c99d0d 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -3460,12 +3460,11 @@ mod common_parallel { .does_device_vendor_pair_match("0x1057", "0x1af4") .unwrap_or_default() ); - assert_eq!( + assert!( guest - .ssh_command("ls /sys/kernel/iommu_groups/1/devices") + .ssh_command("ls /sys/kernel/iommu_groups/*/devices") .unwrap() - .trim(), - "0001:00:01.0" + .contains("0001:00:01.0") ); }); From 6e6127bbda8c4b61e1278d509e7cb503d3abc9ee Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 19 Mar 2026 16:43:50 +0000 Subject: [PATCH 0822/1893] 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 --- cloud-hypervisor/tests/integration.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 3339c99d0d..a0f3333761 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -9921,12 +9921,11 @@ mod common_parallel { .does_device_vendor_pair_match("0x1057", "0x1af4") .unwrap_or_default() ); - assert_eq!( + assert!( guest - .ssh_command("ls /sys/kernel/iommu_groups/1/devices") + .ssh_command("ls /sys/kernel/iommu_groups/*/devices") .unwrap() - .trim(), - "0001:00:01.0" + .contains("0001:00:01.0") ); // Check both if /dev/vdd exists and if the block size is 128M. From 43642d8df8d3edefd09f1bbed89693645ffc225d Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 19 Mar 2026 17:01:32 +0000 Subject: [PATCH 0823/1893] 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 --- cloud-hypervisor/tests/integration.rs | 38 ++++++++++----------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index a0f3333761..3e2a33da70 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2128,7 +2128,7 @@ fn vm_state(api_socket: &str) -> String { // The last interesting part of this test is that it exercises the network // interface attached to the virtual IOMMU since this is the one used to // send all commands through SSH. -fn _test_virtio_iommu(acpi: bool) { +fn _test_virtio_iommu(_acpi: bool /* not needed on x86_64 */) { // Virtio-iommu support is ready in recent kernel (v5.14). But the kernel in // Focal image is still old. // So if ACPI is enabled on AArch64, we use a modified Focal image in which @@ -2143,7 +2143,7 @@ fn _test_virtio_iommu(acpi: bool) { #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); #[cfg(target_arch = "aarch64")] - let kernel_path = if acpi { + let kernel_path = if _acpi { edk2_path() } else { direct_kernel_boot_path() @@ -2187,39 +2187,29 @@ fn _test_virtio_iommu(acpi: bool) { // All devices on the PCI bus will be attached to the virtual IOMMU, except the // virtio-iommu device itself. So these devices will all be added to IOMMU groups, // and appear under folder '/sys/kernel/iommu_groups/'. - // The result is, in the case of FDT, IOMMU group '0' contains "0000:00:01.0" - // which is the console. The first disk "0000:00:02.0" is in group '1'. - // While on ACPI, console device is not attached to IOMMU. So the IOMMU group '0' - // contains "0000:00:02.0" which is the first disk. // - // Verify the iommu group of the first disk. - let iommu_group = if acpi { 0 } else { 2 }; - assert_eq!( + // Verify the first disk is in an iommu group. + assert!( guest - .ssh_command(format!("ls /sys/kernel/iommu_groups/{iommu_group}/devices").as_str()) + .ssh_command("ls /sys/kernel/iommu_groups/*/devices") .unwrap() - .trim(), - "0000:00:02.0" + .contains("0000:00:02.0") ); - // Verify the iommu group of the second disk. - let iommu_group = if acpi { 1 } else { 3 }; - assert_eq!( + // Verify the second disk is in an iommu group. + assert!( guest - .ssh_command(format!("ls /sys/kernel/iommu_groups/{iommu_group}/devices").as_str()) + .ssh_command("ls /sys/kernel/iommu_groups/*/devices") .unwrap() - .trim(), - "0000:00:03.0" + .contains("0000:00:03.0") ); - // Verify the iommu group of the network card. - let iommu_group = if acpi { 2 } else { 4 }; - assert_eq!( + // Verify the network card is in an iommu group. + assert!( guest - .ssh_command(format!("ls /sys/kernel/iommu_groups/{iommu_group}/devices").as_str()) + .ssh_command("ls /sys/kernel/iommu_groups/*/devices") .unwrap() - .trim(), - "0000:00:04.0" + .contains("0000:00:04.0") ); }); From 0c3249b14f718f1b3aae10a7ae3618930e76c1e5 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 19 Mar 2026 23:30:42 -0700 Subject: [PATCH 0824/1893] 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 --- cloud-hypervisor/tests/common/mod.rs | 6 + .../tests/common/tests_wrappers.rs | 2059 +++++++++++ cloud-hypervisor/tests/common/utils.rs | 1045 ++++++ cloud-hypervisor/tests/integration.rs | 3074 +---------------- 4 files changed, 3117 insertions(+), 3067 deletions(-) create mode 100644 cloud-hypervisor/tests/common/mod.rs create mode 100644 cloud-hypervisor/tests/common/tests_wrappers.rs create mode 100644 cloud-hypervisor/tests/common/utils.rs diff --git a/cloud-hypervisor/tests/common/mod.rs b/cloud-hypervisor/tests/common/mod.rs new file mode 100644 index 0000000000..da58f907e8 --- /dev/null +++ b/cloud-hypervisor/tests/common/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod tests_wrappers; +pub(crate) mod utils; diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs new file mode 100644 index 0000000000..afe54ed5ef --- /dev/null +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -0,0 +1,2059 @@ +// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +use std::ffi::CStr; +use std::fs::{self, OpenOptions}; +use std::io::{Seek, SeekFrom, Write}; +use std::path::{Path, PathBuf}; +use std::string::String; +use std::sync::mpsc; +use std::thread; + +use block::ImageType; +use net_util::MacAddr; +use test_infra::*; +use vmm_sys_util::tempdir::TempDir; +use vmm_sys_util::tempfile::TempFile; + +use crate::common::utils::{TargetApi, *}; + +// Start cloud-hypervisor with no VM parameters, only the API server running. +// From the API: Create a VM, boot it and check that it looks as expected. +pub(crate) fn _test_api_create_boot(target_api: &TargetApi, guest: &Guest) { + let mut child = GuestCommand::new(guest) + .args(target_api.guest_args()) + .capture_output() + .spawn() + .unwrap(); + + thread::sleep(std::time::Duration::new(1, 0)); + + // Verify API server is running + assert!(target_api.remote_command("ping", None)); + + // Create the VM first + let request_body = guest.api_create_body(); + + let temp_config_path = guest.tmp_dir.as_path().join("config"); + std::fs::write(&temp_config_path, request_body).unwrap(); + let create_config = temp_config_path.as_os_str().to_str().unwrap(); + + assert!(target_api.remote_command("create", Some(create_config),)); + + // Then boot it + assert!(target_api.remote_command("boot", None)); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + // Check that the VM booted as expected + guest.validate_cpu_count(None); + guest.validate_memory(None); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +// Start cloud-hypervisor with no VM parameters, only the API server running. +// From the API: Create a VM, boot it and check it can be shutdown and then +// booted again +pub(crate) fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { + let mut child = GuestCommand::new(guest) + .args(target_api.guest_args()) + .capture_output() + .spawn() + .unwrap(); + + thread::sleep(std::time::Duration::new(1, 0)); + + // Verify API server is running + assert!(target_api.remote_command("ping", None)); + + // Create the VM first + let request_body = guest.api_create_body(); + + let temp_config_path = guest.tmp_dir.as_path().join("config"); + std::fs::write(&temp_config_path, request_body).unwrap(); + let create_config = temp_config_path.as_os_str().to_str().unwrap(); + + let r = std::panic::catch_unwind(|| { + assert!(target_api.remote_command("create", Some(create_config))); + + // Then boot it + assert!(target_api.remote_command("boot", None)); + + guest.wait_vm_boot().unwrap(); + + // Check that the VM booted as expected + guest.validate_cpu_count(None); + guest.validate_memory(None); + + // Sync and shutdown without powering off to prevent filesystem + // corruption. + guest.ssh_command("sync").unwrap(); + guest.ssh_command("sudo shutdown -H now").unwrap(); + + // Wait for the guest to be fully shutdown + thread::sleep(std::time::Duration::new(20, 0)); + + // Then shut it down + assert!(target_api.remote_command("shutdown", None)); + + // Then boot it again + assert!(target_api.remote_command("boot", None)); + + guest.wait_vm_boot().unwrap(); + + // Check that the VM booted as expected + guest.validate_cpu_count(None); + guest.validate_memory(None); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +// Start cloud-hypervisor with no VM parameters, only the API server running. +// From the API: Create a VM, boot it and check it can be deleted and then recreated +// booted again. +pub(crate) fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { + let mut child = GuestCommand::new(guest) + .args(target_api.guest_args()) + .capture_output() + .spawn() + .unwrap(); + + thread::sleep(std::time::Duration::new(1, 0)); + + // Verify API server is running + assert!(target_api.remote_command("ping", None)); + + // Create the VM first + let request_body = guest.api_create_body(); + + let temp_config_path = guest.tmp_dir.as_path().join("config"); + std::fs::write(&temp_config_path, request_body).unwrap(); + let create_config = temp_config_path.as_os_str().to_str().unwrap(); + + let r = std::panic::catch_unwind(|| { + assert!(target_api.remote_command("create", Some(create_config))); + + // Then boot it + assert!(target_api.remote_command("boot", None)); + + guest.wait_vm_boot().unwrap(); + + // Check that the VM booted as expected + guest.validate_cpu_count(None); + guest.validate_memory(None); + + // Sync and shutdown without powering off to prevent filesystem + // corruption. + guest.ssh_command("sync").unwrap(); + guest.ssh_command("sudo shutdown -H now").unwrap(); + + // Wait for the guest to be fully shutdown + thread::sleep(std::time::Duration::new(20, 0)); + + // Then delete it + assert!(target_api.remote_command("delete", None)); + + assert!(target_api.remote_command("create", Some(create_config))); + + // Then boot it again + assert!(target_api.remote_command("boot", None)); + + guest.wait_vm_boot().unwrap(); + + // Check that the VM booted as expected + guest.validate_cpu_count(None); + guest.validate_memory(None); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +// Start cloud-hypervisor with no VM parameters, only the API server running. +// From the API: Create a VM, boot it and check that it looks as expected. +// Then we pause the VM, check that it's no longer available. +// Finally we resume the VM and check that it's available. +pub(crate) fn _test_api_pause_resume(target_api: &TargetApi, guest: &Guest) { + let mut child = GuestCommand::new(guest) + .args(target_api.guest_args()) + .capture_output() + .spawn() + .unwrap(); + + thread::sleep(std::time::Duration::new(1, 0)); + + // Verify API server is running + assert!(target_api.remote_command("ping", None)); + + // Create the VM first + let request_body = guest.api_create_body(); + + let temp_config_path = guest.tmp_dir.as_path().join("config"); + std::fs::write(&temp_config_path, request_body).unwrap(); + let create_config = temp_config_path.as_os_str().to_str().unwrap(); + + assert!(target_api.remote_command("create", Some(create_config))); + + // Then boot it + assert!(target_api.remote_command("boot", None)); + thread::sleep(std::time::Duration::new(20, 0)); + + let r = std::panic::catch_unwind(|| { + // Check that the VM booted as expected + guest.validate_cpu_count(None); + guest.validate_memory(None); + + // We now pause the VM + assert!(target_api.remote_command("pause", None)); + + // Check pausing again fails + assert!(!target_api.remote_command("pause", None)); + + thread::sleep(std::time::Duration::new(2, 0)); + + // SSH into the VM should fail + ssh_command_ip( + "grep -c processor /proc/cpuinfo", + &guest.network.guest_ip0, + 2, + 5, + ) + .unwrap_err(); + + // Resume the VM + assert!(target_api.remote_command("resume", None)); + + // Check resuming again fails + assert!(!target_api.remote_command("resume", None)); + + thread::sleep(std::time::Duration::new(2, 0)); + + // Now we should be able to SSH back in and get the right number of CPUs + guest.validate_cpu_count(None); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +pub(crate) fn _test_pty_interaction(pty_path: PathBuf) { + let mut cf = std::fs::OpenOptions::new() + .write(true) + .read(true) + .open(pty_path) + .unwrap(); + + // Some dumb sleeps but we don't want to write + // before the console is up and we don't want + // to try and write the next line before the + // login process is ready. + thread::sleep(std::time::Duration::new(5, 0)); + assert_eq!(cf.write(b"cloud\n").unwrap(), 6); + thread::sleep(std::time::Duration::new(2, 0)); + assert_eq!(cf.write(b"cloud123\n").unwrap(), 9); + thread::sleep(std::time::Duration::new(2, 0)); + assert_eq!(cf.write(b"echo test_pty_console\n").unwrap(), 22); + thread::sleep(std::time::Duration::new(2, 0)); + + // read pty and ensure they have a login shell + // some fairly hacky workarounds to avoid looping + // forever in case the channel is blocked getting output + let ptyc = pty_read(cf); + let mut empty = 0; + let mut prev = String::new(); + loop { + thread::sleep(std::time::Duration::new(2, 0)); + match ptyc.try_recv() { + Ok(line) => { + empty = 0; + prev = prev + &line; + if prev.contains("test_pty_console") { + break; + } + } + Err(mpsc::TryRecvError::Empty) => { + empty += 1; + assert!(empty <= 5, "No login on pty"); + } + _ => { + panic!("No login on pty") + } + } + } +} + +pub(crate) fn test_cpu_topology( + threads_per_core: u8, + cores_per_package: u8, + packages: u8, + use_fw: bool, +) { + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let total_vcpus = threads_per_core * cores_per_package * packages; + let direct_kernel_boot_path = direct_kernel_boot_path(); + let mut kernel_path = direct_kernel_boot_path.to_str().unwrap(); + let fw_path = fw_path(FwType::RustHypervisorFirmware); + if use_fw { + kernel_path = fw_path.as_str(); + } + + let mut child = GuestCommand::new(&guest) + .args([ + "--cpus", + &format!( + "boot={total_vcpus},topology={threads_per_core}:{cores_per_package}:1:{packages}" + ), + ]) + .default_memory() + .args(["--kernel", kernel_path]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + assert_eq!( + guest.get_cpu_count().unwrap_or_default(), + u32::from(total_vcpus) + ); + assert_eq!( + guest + .ssh_command("lscpu | grep \"per core\" | cut -f 2 -d \":\" | sed \"s# *##\"") + .unwrap() + .trim() + .parse::() + .unwrap_or(0), + threads_per_core + ); + + assert_eq!( + guest + .ssh_command("lscpu | grep \"per socket\" | cut -f 2 -d \":\" | sed \"s# *##\"") + .unwrap() + .trim() + .parse::() + .unwrap_or(0), + cores_per_package + ); + + assert_eq!( + guest + .ssh_command("lscpu | grep \"Socket\" | cut -f 2 -d \":\" | sed \"s# *##\"") + .unwrap() + .trim() + .parse::() + .unwrap_or(0), + packages + ); + + #[cfg(target_arch = "x86_64")] + { + let mut cpu_id = 0; + for package_id in 0..packages { + for core_id in 0..cores_per_package { + for _ in 0..threads_per_core { + assert_eq!( + guest + .ssh_command(&format!("cat /sys/devices/system/cpu/cpu{cpu_id}/topology/physical_package_id")) + .unwrap() + .trim() + .parse::() + .unwrap_or(0), + package_id + ); + + assert_eq!( + guest + .ssh_command(&format!( + "cat /sys/devices/system/cpu/cpu{cpu_id}/topology/core_id" + )) + .unwrap() + .trim() + .parse::() + .unwrap_or(0), + core_id + ); + + cpu_id += 1; + } + } + } + } + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +#[allow(unused_variables)] +pub(crate) fn _test_guest_numa_nodes(acpi: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = if acpi { + edk2_path() + } else { + direct_kernel_boot_path() + }; + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=6,max=12"]) + .args(["--memory", "size=0,hotplug_method=virtio-mem"]) + .args([ + "--memory-zone", + "id=mem0,size=1G,hotplug_size=3G", + "id=mem1,size=2G,hotplug_size=3G", + "id=mem2,size=3G,hotplug_size=3G", + ]) + .args([ + "--numa", + "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0", + "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1", + "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2", + ]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args(["--api-socket", &api_socket]) + .capture_output() + .default_disks() + .default_net() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + guest.check_numa_common( + Some(&[960_000, 1_920_000, 2_880_000]), + Some(&[&[0, 1, 2], &[3, 4], &[5]]), + Some(&["10 15 20", "20 10 25", "25 30 10"]), + ); + + // AArch64 currently does not support hotplug, and therefore we only + // test hotplug-related function on x86_64 here. + #[cfg(target_arch = "x86_64")] + { + guest.enable_memory_hotplug(); + + // Resize every memory zone and check each associated NUMA node + // has been assigned the right amount of memory. + resize_zone_command(&api_socket, "mem0", "4G"); + resize_zone_command(&api_socket, "mem1", "4G"); + resize_zone_command(&api_socket, "mem2", "4G"); + // Resize to the maximum amount of CPUs and check each NUMA + // node has been assigned the right CPUs set. + resize_command(&api_socket, Some(12), None, None, None); + thread::sleep(std::time::Duration::new(5, 0)); + + guest.check_numa_common( + Some(&[3_840_000, 3_840_000, 3_840_000]), + Some(&[&[0, 1, 2, 9], &[3, 4, 6, 7, 8], &[5, 10, 11]]), + None, + ); + } + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +#[allow(unused_variables)] +pub(crate) fn _test_power_button(guest: &Guest) { + let mut cmd = GuestCommand::new(guest); + let api_socket = temp_api_path(&guest.tmp_dir); + + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline() + .capture_output() + .default_disks() + .default_net() + .args(["--api-socket", &api_socket]); + + let child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + assert!(remote_command(&api_socket, "power-button", None)); + }); + + let output = child.wait_with_output().unwrap(); + assert!(output.status.success()); + handle_child_output(r, &output); +} + +pub(crate) fn test_vhost_user_net( + tap: Option<&str>, + num_queues: usize, + prepare_daemon: &PrepareNetDaemon, + generate_host_mac: bool, + client_mode_daemon: bool, +) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + + let kernel_path = direct_kernel_boot_path(); + + let host_mac = if generate_host_mac { + Some(MacAddr::local_random()) + } else { + None + }; + + let mtu = Some(3000); + + let (mut daemon_command, vunet_socket_path) = prepare_daemon( + &guest.tmp_dir, + &guest.network.host_ip0, + tap, + mtu, + num_queues, + client_mode_daemon, + ); + + let net_params = format!( + "vhost_user=true,mac={},socket={},num_queues={},queue_size=1024{},vhost_mode={},mtu=3000", + guest.network.guest_mac0, + vunet_socket_path, + num_queues, + if let Some(host_mac) = host_mac { + format!(",host_mac={host_mac}") + } else { + String::new() + }, + if client_mode_daemon { + "server" + } else { + "client" + }, + ); + + let mut ch_command = GuestCommand::new(&guest); + ch_command + .args(["--cpus", format!("boot={}", num_queues / 2).as_str()]) + .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--net", net_params.as_str()]) + .args(["--api-socket", &api_socket]) + .capture_output(); + + let mut daemon_child: std::process::Child; + let mut child: std::process::Child; + + if client_mode_daemon { + child = ch_command.spawn().unwrap(); + // Make sure the VMM is waiting for the backend to connect + thread::sleep(std::time::Duration::new(10, 0)); + daemon_child = daemon_command.spawn().unwrap(); + } else { + daemon_child = daemon_command.spawn().unwrap(); + // Make sure the backend is waiting for the VMM to connect + thread::sleep(std::time::Duration::new(10, 0)); + child = ch_command.spawn().unwrap(); + } + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + if let Some(tap_name) = tap { + let tap_count = exec_host_command_output(&format!("ip link | grep -c {tap_name}")); + assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1"); + } + + if let Some(host_mac) = tap { + let mac_count = exec_host_command_output(&format!("ip link | grep -c {host_mac}")); + assert_eq!(String::from_utf8_lossy(&mac_count.stdout).trim(), "1"); + } + + #[cfg(target_arch = "aarch64")] + let iface = "enp0s4"; + #[cfg(target_arch = "x86_64")] + let iface = "ens4"; + + assert_eq!( + guest + .ssh_command(format!("cat /sys/class/net/{iface}/mtu").as_str()) + .unwrap() + .trim(), + "3000" + ); + + // 1 network interface + default localhost ==> 2 interfaces + // It's important to note that this test is fully exercising the + // vhost-user-net implementation and the associated backend since + // it does not define any --net network interface. That means all + // the ssh communication in that test happens through the network + // interface backed by vhost-user-net. + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 2 + ); + + // The following pci devices will appear on guest with PCI-MSI + // interrupt vectors assigned. + // 1 virtio-console with 3 vectors: config, Rx, Tx + // 1 virtio-blk with 2 vectors: config, Request + // 1 virtio-blk with 2 vectors: config, Request + // 1 virtio-rng with 2 vectors: config, Request + // Since virtio-net has 2 queue pairs, its vectors is as follows: + // 1 virtio-net with 5 vectors: config, Rx (2), Tx (2) + // Based on the above, the total vectors should 14. + let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); + + assert_eq!( + guest + .ssh_command(&grep_cmd) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 10 + (num_queues as u32) + ); + + // ACPI feature is needed. + #[cfg(target_arch = "x86_64")] + { + guest.enable_memory_hotplug(); + + // Add RAM to the VM + let desired_ram = 1024 << 20; + resize_command(&api_socket, None, Some(desired_ram), None, None); + + thread::sleep(std::time::Duration::new(10, 0)); + + // Here by simply checking the size (through ssh), we validate + // the connection is still working, which means vhost-user-net + // keeps working after the resize. + assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + } + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + thread::sleep(std::time::Duration::new(5, 0)); + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); + + handle_child_output(r, &output); +} + +type PrepareBlkDaemon = dyn Fn(&TempDir, &str, usize, bool, bool) -> (std::process::Child, String); + +pub(crate) fn test_vhost_user_blk( + num_queues: usize, + readonly: bool, + direct: bool, + prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>, +) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + + let kernel_path = direct_kernel_boot_path(); + + let (blk_params, daemon_child) = { + let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap(); + // Start the daemon + let (daemon_child, vubd_socket_path) = + prepare_daemon(&guest.tmp_dir, "blk.img", num_queues, readonly, direct); + + ( + format!( + "vhost_user=true,socket={vubd_socket_path},num_queues={num_queues},queue_size=128", + ), + Some(daemon_child), + ) + }; + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", format!("boot={num_queues}").as_str()]) + .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + blk_params.as_str(), + ]) + .default_net() + .args(["--api-socket", &api_socket]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Check both if /dev/vdc exists and if the block size is 16M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 16M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Check if this block is RO or RW. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | awk '{print $5}'") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + readonly as u32 + ); + + // Check if the number of queues in /sys/block/vdc/mq matches the + // expected num_queues. + assert_eq!( + guest + .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + num_queues as u32 + ); + + // Mount the device + let mount_ro_rw_flag = if readonly { "ro,noload" } else { "rw" }; + guest.ssh_command("mkdir mount_image").unwrap(); + guest + .ssh_command( + format!("sudo mount -o {mount_ro_rw_flag} -t ext4 /dev/vdc mount_image/").as_str(), + ) + .unwrap(); + + // Check the content of the block device. The file "foo" should + // contain "bar". + assert_eq!( + guest.ssh_command("cat mount_image/foo").unwrap().trim(), + "bar" + ); + + // ACPI feature is needed. + #[cfg(target_arch = "x86_64")] + { + guest.enable_memory_hotplug(); + + // Add RAM to the VM + let desired_ram = 1024 << 20; + resize_command(&api_socket, None, Some(desired_ram), None, None); + + thread::sleep(std::time::Duration::new(10, 0)); + + assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + + // Check again the content of the block device after the resize + // has been performed. + assert_eq!( + guest.ssh_command("cat mount_image/foo").unwrap().trim(), + "bar" + ); + } + + // Unmount the device + guest.ssh_command("sudo umount /dev/vdc").unwrap(); + guest.ssh_command("rm -r mount_image").unwrap(); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + if let Some(mut daemon_child) = daemon_child { + thread::sleep(std::time::Duration::new(5, 0)); + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); + } + + handle_child_output(r, &output); +} + +pub(crate) fn test_boot_from_vhost_user_blk( + num_queues: usize, + readonly: bool, + direct: bool, + prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>, +) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + + let kernel_path = direct_kernel_boot_path(); + + let disk_path = guest.disk_config.disk(DiskType::OperatingSystem).unwrap(); + + let (blk_boot_params, daemon_child) = { + let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap(); + // Start the daemon + let (daemon_child, vubd_socket_path) = prepare_daemon( + &guest.tmp_dir, + disk_path.as_str(), + num_queues, + readonly, + direct, + ); + + ( + format!( + "vhost_user=true,socket={vubd_socket_path},num_queues={num_queues},queue_size=128", + ), + Some(daemon_child), + ) + }; + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", format!("boot={num_queues}").as_str()]) + .args(["--memory", "size=512M,shared=on"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + blk_boot_params.as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Just check the VM booted correctly. + assert_eq!(guest.get_cpu_count().unwrap_or_default(), num_queues as u32); + assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + }); + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + if let Some(mut daemon_child) = daemon_child { + thread::sleep(std::time::Duration::new(5, 0)); + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); + } + + handle_child_output(r, &output); +} + +pub(crate) fn _test_virtio_fs( + prepare_daemon: &dyn Fn(&TempDir, &str) -> (std::process::Child, String), + hotplug: bool, + use_generic_vhost_user: bool, + pci_segment: Option, +) { + #[cfg(target_arch = "aarch64")] + let focal_image = if hotplug { + FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string() + } else { + FOCAL_IMAGE_NAME.to_string() + }; + #[cfg(target_arch = "x86_64")] + let focal_image = FOCAL_IMAGE_NAME.to_string(); + let disk_config = UbuntuDiskConfig::new(focal_image); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + + let mut shared_dir = workload_path; + shared_dir.push("shared_dir"); + + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = if hotplug { + edk2_path() + } else { + direct_kernel_boot_path() + }; + + let (mut daemon_child, virtiofsd_socket_path) = + prepare_daemon(&guest.tmp_dir, shared_dir.to_str().unwrap()); + + let mut guest_command = GuestCommand::new(&guest); + guest_command + .default_cpus() + .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .args(["--api-socket", &api_socket]); + if pci_segment.is_some() { + guest_command.args([ + "--platform", + &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS}"), + ]); + } + + let fs_params = format!( + "socket={},id=myfs0,{}{}", + virtiofsd_socket_path, + if use_generic_vhost_user { + "queue_sizes=[1024,1024],virtio_id=26" + } else { + "tag=myfs,num_queues=1,queue_size=1024" + }, + if let Some(pci_segment) = pci_segment { + format!(",pci_segment={pci_segment}") + } else { + String::new() + } + ); + + if !hotplug { + guest_command.args([ + if use_generic_vhost_user { + "--generic-vhost-user" + } else { + "--fs" + }, + fs_params.as_str(), + ]); + } + + let mut child = guest_command.capture_output().spawn().unwrap(); + let add_arg = if use_generic_vhost_user { + "add-generic-vhost-user" + } else { + "add-fs" + }; + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + if hotplug { + // Add fs to the VM + let (cmd_success, cmd_output) = + remote_command_w_output(&api_socket, add_arg, Some(&fs_params)); + assert!(cmd_success); + + if let Some(pci_segment) = pci_segment { + assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( + "{{\"id\":\"myfs0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" + ))); + } else { + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}") + ); + } + + thread::sleep(std::time::Duration::new(10, 0)); + } + + // Mount shared directory through virtio_fs filesystem + guest + .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") + .unwrap(); + + // Check file1 exists and its content is "foo" + assert_eq!( + guest.ssh_command("cat mount_dir/file1").unwrap().trim(), + "foo" + ); + // Check file2 does not exist + guest + .ssh_command("[ ! -f 'mount_dir/file2' ] || true") + .unwrap(); + + // Check file3 exists and its content is "bar" + assert_eq!( + guest.ssh_command("cat mount_dir/file3").unwrap().trim(), + "bar" + ); + + // ACPI feature is needed. + #[cfg(target_arch = "x86_64")] + { + guest.enable_memory_hotplug(); + + // Add RAM to the VM + let desired_ram = 1024 << 20; + resize_command(&api_socket, None, Some(desired_ram), None, None); + + thread::sleep(std::time::Duration::new(30, 0)); + assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + + // After the resize, check again that file1 exists and its + // content is "foo". + assert_eq!( + guest.ssh_command("cat mount_dir/file1").unwrap().trim(), + "foo" + ); + } + + if hotplug { + // Remove from VM + guest.ssh_command("sudo umount mount_dir").unwrap(); + assert!(remote_command(&api_socket, "remove-device", Some("myfs0"))); + } + }); + + let (r, hotplug_daemon_child) = if r.is_ok() && hotplug { + thread::sleep(std::time::Duration::new(10, 0)); + let (daemon_child, virtiofsd_socket_path) = + prepare_daemon(&guest.tmp_dir, shared_dir.to_str().unwrap()); + + let r = std::panic::catch_unwind(|| { + thread::sleep(std::time::Duration::new(10, 0)); + let fs_params = format!( + "id=myfs0,socket={},{}{}", + virtiofsd_socket_path, + if use_generic_vhost_user { + "queue_sizes=[1024,1024],virtio_id=26" + } else { + "tag=myfs,num_queues=1,queue_size=1024" + }, + if let Some(pci_segment) = pci_segment { + format!(",pci_segment={pci_segment}") + } else { + String::new() + } + ); + + // Add back and check it works + let (cmd_success, cmd_output) = + remote_command_w_output(&api_socket, add_arg, Some(&fs_params)); + assert!(cmd_success); + if let Some(pci_segment) = pci_segment { + assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( + "{{\"id\":\"myfs0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" + ))); + } else { + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}") + ); + } + + thread::sleep(std::time::Duration::new(10, 0)); + // Mount shared directory through virtio_fs filesystem + guest + .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") + .unwrap(); + + // Check file1 exists and its content is "foo" + assert_eq!( + guest.ssh_command("cat mount_dir/file1").unwrap().trim(), + "foo" + ); + }); + + (r, Some(daemon_child)) + } else { + (r, None) + }; + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); + + if let Some(mut daemon_child) = hotplug_daemon_child { + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); + } + + handle_child_output(r, &output); +} + +pub(crate) fn test_virtio_pmem(discard_writes: bool, specify_size: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + + let kernel_path = direct_kernel_boot_path(); + + let pmem_temp_file = TempFile::new().unwrap(); + pmem_temp_file.as_file().set_len(128 << 20).unwrap(); + + std::process::Command::new("mkfs.ext4") + .arg(pmem_temp_file.as_path()) + .output() + .expect("Expect creating disk image to succeed"); + + let mut child = GuestCommand::new(&guest) + .default_cpus() + .default_memory() + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .args([ + "--pmem", + format!( + "file={}{}{}", + pmem_temp_file.as_path().to_str().unwrap(), + if specify_size { ",size=128M" } else { "" }, + if discard_writes { + ",discard_writes=on" + } else { + "" + } + ) + .as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Check for the presence of /dev/pmem0 + assert_eq!( + guest.ssh_command("ls /dev/pmem0").unwrap().trim(), + "/dev/pmem0" + ); + + // Check changes persist after reboot + assert_eq!(guest.ssh_command("sudo mount /dev/pmem0 /mnt").unwrap(), ""); + assert_eq!(guest.ssh_command("ls /mnt").unwrap(), "lost+found\n"); + guest + .ssh_command("echo test123 | sudo tee /mnt/test") + .unwrap(); + assert_eq!(guest.ssh_command("sudo umount /mnt").unwrap(), ""); + assert_eq!(guest.ssh_command("ls /mnt").unwrap(), ""); + + guest.reboot_linux(0); + assert_eq!(guest.ssh_command("sudo mount /dev/pmem0 /mnt").unwrap(), ""); + assert_eq!( + guest + .ssh_command("sudo cat /mnt/test || true") + .unwrap() + .trim(), + if discard_writes { "" } else { "test123" } + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +pub(crate) fn _test_virtio_vsock(guest: &Guest, hotplug: bool) { + let socket = temp_vsock_path(&guest.tmp_dir); + let api_socket = temp_api_path(&guest.tmp_dir); + + let mut cmd = GuestCommand::new(guest); + cmd.args(["--api-socket", &api_socket]); + cmd.default_cpus(); + cmd.default_memory(); + cmd.default_kernel_cmdline(); + cmd.default_disks(); + cmd.default_net(); + + if !hotplug { + cmd.args(["--vsock", format!("cid=3,socket={socket}").as_str()]); + } + + let mut child = cmd.capture_output().spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + if hotplug { + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-vsock", + Some(format!("cid=3,socket={socket},id=test0").as_str()), + ); + assert!(cmd_success); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); + thread::sleep(std::time::Duration::new(10, 0)); + // Check adding a second one fails + assert!(!remote_command( + &api_socket, + "add-vsock", + Some("cid=1234,socket=/tmp/fail") + )); + } + + // Validate vsock works as expected. + guest.check_vsock(socket.as_str()); + guest.reboot_linux(0); + // Validate vsock still works after a reboot. + guest.check_vsock(socket.as_str()); + + if hotplug { + assert!(remote_command(&api_socket, "remove-device", Some("test0"))); + } + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +pub(crate) fn test_memory_mergeable(mergeable: bool) { + let memory_param = if mergeable { + "mergeable=on" + } else { + "mergeable=off" + }; + + // We assume the number of shared pages in the rest of the system to be constant + let ksm_ps_init = get_ksm_pages_shared(); + + let disk_config1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest1 = Guest::new(Box::new(disk_config1)); + let mut child1 = GuestCommand::new(&guest1) + .default_cpus() + .args(["--memory", format!("size=512M,{memory_param}").as_str()]) + .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--net", guest1.default_net_string().as_str()]) + .args(["--serial", "tty", "--console", "off"]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest1.wait_vm_boot().unwrap(); + }); + if r.is_err() { + kill_child(&mut child1); + let output = child1.wait_with_output().unwrap(); + handle_child_output(r, &output); + panic!("Test should already be failed/panicked"); // To explicitly mark this block never return + } + + let ksm_ps_guest1 = get_ksm_pages_shared(); + + let disk_config2 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest2 = Guest::new(Box::new(disk_config2)); + let mut child2 = GuestCommand::new(&guest2) + .default_cpus() + .args(["--memory", format!("size=512M,{memory_param}").as_str()]) + .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--net", guest2.default_net_string().as_str()]) + .args(["--serial", "tty", "--console", "off"]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest2.wait_vm_boot().unwrap(); + let ksm_ps_guest2 = get_ksm_pages_shared(); + + if mergeable { + println!( + "ksm pages_shared after vm1 booted '{ksm_ps_guest1}', ksm pages_shared after vm2 booted '{ksm_ps_guest2}'" + ); + // We are expecting the number of shared pages to increase as the number of VM increases + assert!(ksm_ps_guest1 < ksm_ps_guest2); + } else { + assert!(ksm_ps_guest1 == ksm_ps_init); + assert!(ksm_ps_guest2 == ksm_ps_init); + } + }); + + kill_child(&mut child1); + kill_child(&mut child2); + + let output = child1.wait_with_output().unwrap(); + child2.wait().unwrap(); + + handle_child_output(r, &output); +} + +// This test validates that it can find the virtio-iommu device at first. +// It also verifies that both disks and the network card are attached to +// the virtual IOMMU by looking at /sys/kernel/iommu_groups directory. +// The last interesting part of this test is that it exercises the network +// interface attached to the virtual IOMMU since this is the one used to +// send all commands through SSH. +pub(crate) fn _test_virtio_iommu(_acpi: bool /* not needed on x86_64 */) { + // Virtio-iommu support is ready in recent kernel (v5.14). But the kernel in + // Focal image is still old. + // So if ACPI is enabled on AArch64, we use a modified Focal image in which + // the kernel binary has been updated. + #[cfg(target_arch = "aarch64")] + let focal_image = FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string(); + #[cfg(target_arch = "x86_64")] + let focal_image = FOCAL_IMAGE_NAME.to_string(); + let disk_config = UbuntuDiskConfig::new(focal_image); + let guest = Guest::new(Box::new(disk_config)); + + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = if _acpi { + edk2_path() + } else { + direct_kernel_boot_path() + }; + + let mut child = GuestCommand::new(&guest) + .default_cpus() + .default_memory() + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={},iommu=on", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={},iommu=on", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + ]) + .args(["--net", guest.default_net_string_w_iommu().as_str()]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Verify the virtio-iommu device is present. + assert!( + guest + .does_device_vendor_pair_match("0x1057", "0x1af4") + .unwrap_or_default() + ); + + // On AArch64, if the guest system boots from FDT, the behavior of IOMMU is a bit + // different with ACPI. + // All devices on the PCI bus will be attached to the virtual IOMMU, except the + // virtio-iommu device itself. So these devices will all be added to IOMMU groups, + // and appear under folder '/sys/kernel/iommu_groups/'. + // + // Verify the first disk is in an iommu group. + assert!( + guest + .ssh_command("ls /sys/kernel/iommu_groups/*/devices") + .unwrap() + .contains("0000:00:02.0") + ); + + // Verify the second disk is in an iommu group. + assert!( + guest + .ssh_command("ls /sys/kernel/iommu_groups/*/devices") + .unwrap() + .contains("0000:00:03.0") + ); + + // Verify the network card is in an iommu group. + assert!( + guest + .ssh_command("ls /sys/kernel/iommu_groups/*/devices") + .unwrap() + .contains("0000:00:04.0") + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +// ivshmem test +// This case validates that read data from host(host write data to ivshmem backend file, +// guest read data from ivshmem pci bar2 memory) +// and write data to host(guest write data to ivshmem pci bar2 memory, host read it from +// ivshmem backend file). +// It also checks the size of the shared memory region. +pub(crate) fn _test_ivshmem(guest: &Guest, ivshmem_file_path: impl AsRef, file_size: &str) { + let ivshmem_file_path = ivshmem_file_path.as_ref(); + let test_message_read = String::from("ivshmem device test data read"); + // Modify backend file data before function test + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open(ivshmem_file_path) + .unwrap(); + file.seek(SeekFrom::Start(0)).unwrap(); + file.write_all(test_message_read.as_bytes()).unwrap(); + file.write_all(b"\0").unwrap(); + file.flush().unwrap(); + + let output = fs::read_to_string(ivshmem_file_path).unwrap(); + let nul_pos = output.as_bytes().iter().position(|&b| b == 0).unwrap(); + let c_str = CStr::from_bytes_until_nul(&output.as_bytes()[..=nul_pos]).unwrap(); + let file_message = c_str.to_string_lossy().to_string(); + // Check if the backend file data is correct + assert_eq!(test_message_read, file_message); + + let device_id_line = String::from( + guest + .ssh_command("lspci -D | grep \"Inter-VM shared memory\"") + .unwrap() + .trim(), + ); + // Check if ivshmem exists + assert!(!device_id_line.is_empty()); + let device_id = device_id_line.split(" ").next().unwrap(); + // Check shard memory size + assert_eq!( + guest + .ssh_command( + format!("lspci -vv -s {device_id} | grep -c \"Region 2.*size={file_size}\"") + .as_str(), + ) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // guest don't have gcc or g++, try to use python to test :( + // This python program try to mmap the ivshmem pci bar2 memory and read the data from it. + let ivshmem_test_read = format!( + r#" +import os +import mmap +from ctypes import create_string_buffer, c_char, memmove + +if __name__ == "__main__": + device_path = f"/sys/bus/pci/devices/{device_id}/resource2" + fd = os.open(device_path, os.O_RDWR | os.O_SYNC) + + PAGE_SIZE = os.sysconf('SC_PAGESIZE') + + with mmap.mmap(fd, PAGE_SIZE, flags=mmap.MAP_SHARED, + prot=mmap.PROT_READ | mmap.PROT_WRITE, offset=0) as shmem: + c_buf = (c_char * PAGE_SIZE).from_buffer(shmem) + null_pos = c_buf.raw.find(b'\x00') + valid_data = c_buf.raw[:null_pos] if null_pos != -1 else c_buf.raw + print(valid_data.decode('utf-8', errors='replace'), end="") + shmem.flush() + del c_buf + + os.close(fd) + "# + ); + guest + .ssh_command( + format!( + r#"cat << EOF > test_read.py +{ivshmem_test_read} +EOF +"# + ) + .as_str(), + ) + .unwrap(); + let guest_message = guest.ssh_command("sudo python3 test_read.py").unwrap(); + + // Check the probe message in host and guest + assert_eq!(test_message_read, guest_message); + + let test_message_write = "ivshmem device test data write"; + // Then the program writes a test message to the memory and flush it. + let ivshmem_test_write = format!( + r#" +import os +import mmap +from ctypes import create_string_buffer, c_char, memmove + +if __name__ == "__main__": + device_path = f"/sys/bus/pci/devices/{device_id}/resource2" + test_message = "{test_message_write}" + fd = os.open(device_path, os.O_RDWR | os.O_SYNC) + + PAGE_SIZE = os.sysconf('SC_PAGESIZE') + + with mmap.mmap(fd, PAGE_SIZE, flags=mmap.MAP_SHARED, + prot=mmap.PROT_READ | mmap.PROT_WRITE, offset=0) as shmem: + shmem.flush() + c_buf = (c_char * PAGE_SIZE).from_buffer(shmem) + encoded_msg = test_message.encode('utf-8').ljust(1000, b'\x00') + memmove(c_buf, encoded_msg, len(encoded_msg)) + shmem.flush() + del c_buf + + os.close(fd) + "# + ); + + guest + .ssh_command( + format!( + r#"cat << EOF > test_write.py +{ivshmem_test_write} +EOF +"# + ) + .as_str(), + ) + .unwrap(); + + let _ = guest.ssh_command("sudo python3 test_write.py").unwrap(); + + let output = fs::read_to_string(ivshmem_file_path).unwrap(); + let nul_pos = output.as_bytes().iter().position(|&b| b == 0).unwrap(); + let c_str = CStr::from_bytes_until_nul(&output.as_bytes()[..=nul_pos]).unwrap(); + let file_message = c_str.to_string_lossy().to_string(); + // Check to send data from guest to host + assert_eq!(test_message_write, file_message); +} + +pub(crate) fn _test_simple_launch(guest: &Guest) { + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .args(["--serial", "tty", "--console", "off"]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + guest.validate_cpu_count(None); + guest.validate_memory(None); + assert_eq!(guest.get_pci_bridge_class().unwrap_or_default(), "0x060000"); + assert!(check_sequential_events( + &guest + .get_expected_seq_events_for_simple_launch() + .iter() + .collect::>(), + &event_path + )); + + // It's been observed on the Bionic image that udev and snapd + // services can cause some delay in the VM's shutdown. Disabling + // them improves the reliability of this test. + let _ = guest.ssh_command("sudo systemctl disable udev"); + let _ = guest.ssh_command("sudo systemctl stop udev"); + let _ = guest.ssh_command("sudo systemctl disable snapd"); + let _ = guest.ssh_command("sudo systemctl stop snapd"); + + guest.ssh_command("sudo poweroff").unwrap(); + thread::sleep(std::time::Duration::new(20, 0)); + let latest_events = [ + &MetaEvent { + event: "shutdown".to_string(), + device_id: None, + }, + &MetaEvent { + event: "deleted".to_string(), + device_id: None, + }, + &MetaEvent { + event: "shutdown".to_string(), + device_id: None, + }, + ]; + assert!(check_latest_events_exact(&latest_events, &event_path)); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +pub(crate) fn _test_multi_cpu(guest: &Guest) { + let mut cmd = GuestCommand::new(guest); + cmd.args(["--cpus", "boot=2,max=4"]) + .default_memory() + .default_kernel_cmdline() + .capture_output() + .default_disks() + .default_net(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + + assert_eq!( + guest + .ssh_command(r#"sudo dmesg | grep "smp: Brought up" | sed "s/\[\ *[0-9.]*\] //""#) + .unwrap() + .trim(), + "smp: Brought up 1 node, 2 CPUs" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +pub(crate) fn _test_cpu_affinity(guest: &Guest) { + // We need the host to have at least 4 CPUs if we want to be able + // to run this test. + let host_cpus_count = exec_host_command_output("nproc"); + assert!( + String::from_utf8_lossy(&host_cpus_count.stdout) + .trim() + .parse::() + .unwrap_or(0) + >= 4 + ); + + let mut child = GuestCommand::new(guest) + .default_cpus_with_affinity() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + let pid = child.id(); + let taskset_vcpu0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_vcpu0.stdout).trim(), "0,2"); + let taskset_vcpu1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_vcpu1.stdout).trim(), "1,3"); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); +} + +pub(crate) fn _test_virtio_queue_affinity(guest: &Guest) { + // We need the host to have at least 4 CPUs if we want to be able + // to run this test. + let host_cpus_count = exec_host_command_output("nproc"); + assert!( + String::from_utf8_lossy(&host_cpus_count.stdout) + .trim() + .parse::() + .unwrap_or(0) + >= 4 + ); + + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={},num_queues=4,queue_affinity=[0@[0,2],1@[1,3],2@[1],3@[3]]", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + let pid = child.id(); + let taskset_q0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_q0.stdout).trim(), "0,2"); + let taskset_q1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_q1.stdout).trim(), "1,3"); + let taskset_q2 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q2 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_q2.stdout).trim(), "1"); + let taskset_q3 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q3 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); + assert_eq!(String::from_utf8_lossy(&taskset_q3.stdout).trim(), "3"); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); +} + +pub(crate) fn _test_pci_msi(guest: &Guest) { + let mut cmd = GuestCommand::new(guest); + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline() + .capture_output() + .default_disks() + .default_net(); + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); + + let r = std::panic::catch_unwind(|| { + assert_eq!( + guest + .ssh_command(&grep_cmd) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 12 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +pub(crate) fn _test_virtio_net_ctrl_queue(guest: &Guest) { + let mut cmd = GuestCommand::new(guest); + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline() + .args(["--net", guest.default_net_string_w_mtu(3000).as_str()]) + .capture_output() + .default_disks(); + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + #[cfg(target_arch = "aarch64")] + let iface = "enp0s4"; + #[cfg(target_arch = "x86_64")] + let iface = "ens4"; + + let r = std::panic::catch_unwind(|| { + assert_eq!( + guest + .ssh_command( + format!("sudo ethtool -K {iface} rx-gro-hw off && echo success").as_str() + ) + .unwrap() + .trim(), + "success" + ); + assert_eq!( + guest + .ssh_command(format!("cat /sys/class/net/{iface}/mtu").as_str()) + .unwrap() + .trim(), + "3000" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +pub(crate) fn _test_pci_multiple_segments( + guest: &Guest, + max_num_pci_segments: u16, + pci_segments_for_disk: u16, +) { + // Prepare another disk file for the virtio-disk device + let test_disk_path = String::from( + guest + .tmp_dir + .as_path() + .join("test-disk.raw") + .to_str() + .unwrap(), + ); + assert!( + exec_host_command_status(format!("truncate {test_disk_path} -s 4M").as_str()).success() + ); + assert!(exec_host_command_status(format!("mkfs.ext4 {test_disk_path}").as_str()).success()); + + let mut cmd = GuestCommand::new(guest); + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline_with_platform(Some(&format!( + "num_pci_segments={max_num_pci_segments}" + ))) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={test_disk_path},pci_segment={pci_segments_for_disk},image_type=raw") + .as_str(), + ]) + .capture_output() + .default_net(); + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + let grep_cmd = "lspci | grep \"Host bridge\" | wc -l"; + + let r = std::panic::catch_unwind(|| { + // There should be MAX_NUM_PCI_SEGMENTS PCI host bridges in the guest. + assert_eq!( + guest + .ssh_command(grep_cmd) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + max_num_pci_segments + ); + + // Check both if /dev/vdc exists and if the block size is 4M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 4M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Mount the device. + guest.ssh_command("mkdir mount_image").unwrap(); + guest + .ssh_command("sudo mount -o rw -t ext4 /dev/vdc mount_image/") + .unwrap(); + // Grant all users with write permission. + guest.ssh_command("sudo chmod a+w mount_image/").unwrap(); + + // Write something to the device. + guest + .ssh_command("sudo echo \"bar\" >> mount_image/foo") + .unwrap(); + + // Check the content of the block device. The file "foo" should + // contain "bar". + assert_eq!( + guest + .ssh_command("sudo cat mount_image/foo") + .unwrap() + .trim(), + "bar" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +pub(crate) fn _test_direct_kernel_boot(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + guest.validate_cpu_count(None); + guest.validate_memory(None); + + let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); + assert_eq!( + guest + .ssh_command(&grep_cmd) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 12 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} + +pub(crate) fn _test_virtio_block( + guest: &Guest, + disable_io_uring: bool, + disable_aio: bool, + verify_os_disk: bool, + backing_files: bool, + image_type: ImageType, +) { + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + + let mut blk_file_path = workload_path; + blk_file_path.push("blk.img"); + + let initial_backing_checksum = if verify_os_disk { + compute_backing_checksum(guest.disk_config.disk(DiskType::OperatingSystem).unwrap()) + } else { + None + }; + assert!( + guest.num_cpu >= 4, + "_test_virtio_block requires at least 4 CPUs to match num_queues=4" + ); + let mut cloud_child = GuestCommand::new(guest) + .default_cpus() + .args(["--memory", "size=512M,shared=on"]) + .default_kernel_cmdline() + .args([ + "--disk", + format!( + "path={},backing_files={},image_type={image_type}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), + if backing_files { "on" } else { "off" }, + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!( + "path={},readonly=on,direct=on,num_queues=4,_disable_io_uring={},_disable_aio={}", + blk_file_path.to_str().unwrap(), + disable_io_uring, + disable_aio, + ) + .as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Check both if /dev/vdc exists and if the block size is 16M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 16M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Check both if /dev/vdc exists and if this block is RO. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | awk '{print $5}'") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Check if the number of queues is 4. + assert_eq!( + guest + .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4 + ); + }); + + if verify_os_disk { + // Use clean shutdown to allow cloud-hypervisor to clear + // the dirty bit in the QCOW2 v3 image. + kill_child(&mut cloud_child); + } else { + let _ = cloud_child.kill(); + } + let output = cloud_child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + + if verify_os_disk { + disk_check_consistency( + guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), + initial_backing_checksum, + ); + } +} diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs new file mode 100644 index 0000000000..ac48641826 --- /dev/null +++ b/cloud-hypervisor/tests/common/utils.rs @@ -0,0 +1,1045 @@ +// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; +use std::fs::{File, OpenOptions}; +use std::io::{BufRead, Read, Seek, SeekFrom, Write}; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command}; +use std::string::String; +use std::sync::mpsc; +use std::sync::mpsc::Receiver; +use std::{cmp, fs, io, thread}; + +use test_infra::*; +use vmm_sys_util::tempdir::TempDir; + +const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72; +// 10MB is our maximum accepted overhead. +pub(crate) const MAXIMUM_VMM_OVERHEAD_KB: u32 = 10 * 1024; + +// This enum exists to make it more convenient to +// implement test for both D-Bus and REST APIs. +pub(crate) enum TargetApi { + // API socket + HttpApi(String), + // well known service name, object path + DBusApi(String, String), +} + +impl TargetApi { + pub(crate) fn new_http_api(tmp_dir: &TempDir) -> Self { + Self::HttpApi(temp_api_path(tmp_dir)) + } + + pub(crate) fn new_dbus_api(tmp_dir: &TempDir) -> Self { + // `tmp_dir` is in the form of "/tmp/chXXXXXX" + // and we take the `chXXXXXX` part as a unique identifier for the guest + let id = tmp_dir.as_path().file_name().unwrap().to_str().unwrap(); + + Self::DBusApi( + format!("org.cloudhypervisor.{id}"), + format!("/org/cloudhypervisor/{id}"), + ) + } + + pub(crate) fn guest_args(&self) -> Vec { + match self { + TargetApi::HttpApi(api_socket) => { + vec![format!("--api-socket={}", api_socket.as_str())] + } + TargetApi::DBusApi(service_name, object_path) => { + vec![ + format!("--dbus-service-name={}", service_name.as_str()), + format!("--dbus-object-path={}", object_path.as_str()), + ] + } + } + } + + pub(crate) fn remote_args(&self) -> Vec { + // `guest_args` and `remote_args` are consistent with each other + self.guest_args() + } + + pub(crate) fn remote_command(&self, command: &str, arg: Option<&str>) -> bool { + let mut cmd = Command::new(clh_command("ch-remote")); + cmd.args(self.remote_args()); + cmd.arg(command); + + if let Some(arg) = arg { + cmd.arg(arg); + } + + let output = cmd.output().unwrap(); + if output.status.success() { + true + } else { + eprintln!("Error running ch-remote command: {:?}", &cmd); + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!("stderr: {stderr}"); + false + } + } +} + +pub(crate) fn temp_api_path(tmp_dir: &TempDir) -> String { + String::from( + tmp_dir + .as_path() + .join("cloud-hypervisor.sock") + .to_str() + .unwrap(), + ) +} + +pub(crate) fn prepare_virtiofsd( + tmp_dir: &TempDir, + shared_dir: &str, +) -> (std::process::Child, String) { + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + + let mut virtiofsd_path = workload_path; + virtiofsd_path.push("virtiofsd"); + let virtiofsd_path = String::from(virtiofsd_path.to_str().unwrap()); + + let virtiofsd_socket_path = + String::from(tmp_dir.as_path().join("virtiofs.sock").to_str().unwrap()); + + // Start the daemon + let child = Command::new(virtiofsd_path.as_str()) + .args(["--shared-dir", shared_dir]) + .args(["--socket-path", virtiofsd_socket_path.as_str()]) + .args(["--cache", "never"]) + .args(["--tag", "myfs"]) + .spawn() + .unwrap(); + + thread::sleep(std::time::Duration::new(10, 0)); + + (child, virtiofsd_socket_path) +} + +pub(crate) fn prepare_vubd( + tmp_dir: &TempDir, + blk_img: &str, + num_queues: usize, + rdonly: bool, + direct: bool, +) -> (std::process::Child, String) { + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + + let mut blk_file_path = workload_path; + blk_file_path.push(blk_img); + let blk_file_path = String::from(blk_file_path.to_str().unwrap()); + + let vubd_socket_path = String::from(tmp_dir.as_path().join("vub.sock").to_str().unwrap()); + + // Start the daemon + let child = Command::new(clh_command("vhost_user_block")) + .args([ + "--block-backend", + format!( + "path={blk_file_path},socket={vubd_socket_path},num_queues={num_queues},readonly={rdonly},direct={direct}" + ) + .as_str(), + ]) + .spawn() + .unwrap(); + + thread::sleep(std::time::Duration::new(10, 0)); + + (child, vubd_socket_path) +} + +pub(crate) fn temp_vsock_path(tmp_dir: &TempDir) -> String { + String::from(tmp_dir.as_path().join("vsock").to_str().unwrap()) +} + +pub(crate) fn temp_event_monitor_path(tmp_dir: &TempDir) -> String { + String::from(tmp_dir.as_path().join("event.json").to_str().unwrap()) +} + +// Creates the directory and returns the path. +pub(crate) fn temp_snapshot_dir_path(tmp_dir: &TempDir) -> String { + let snapshot_dir = String::from(tmp_dir.as_path().join("snapshot").to_str().unwrap()); + std::fs::create_dir(&snapshot_dir).unwrap(); + snapshot_dir +} + +pub(crate) fn temp_vmcore_file_path(tmp_dir: &TempDir) -> String { + String::from(tmp_dir.as_path().join("vmcore").to_str().unwrap()) +} + +pub(crate) fn cloud_hypervisor_release_path() -> String { + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + + let mut ch_release_path = workload_path; + #[cfg(target_arch = "x86_64")] + ch_release_path.push("cloud-hypervisor-static"); + #[cfg(target_arch = "aarch64")] + ch_release_path.push("cloud-hypervisor-static-aarch64"); + + ch_release_path.into_os_string().into_string().unwrap() +} + +pub(crate) fn prepare_vhost_user_net_daemon( + tmp_dir: &TempDir, + ip: &str, + tap: Option<&str>, + mtu: Option, + num_queues: usize, + client_mode: bool, +) -> (std::process::Command, String) { + let vunet_socket_path = String::from(tmp_dir.as_path().join("vunet.sock").to_str().unwrap()); + + // Start the daemon + let mut net_params = format!( + "ip={ip},mask=255.255.255.128,socket={vunet_socket_path},num_queues={num_queues},queue_size=1024,client={client_mode}" + ); + + if let Some(tap) = tap { + net_params.push_str(format!(",tap={tap}").as_str()); + } + + if let Some(mtu) = mtu { + net_params.push_str(format!(",mtu={mtu}").as_str()); + } + + let mut command = Command::new(clh_command("vhost_user_net")); + command.args(["--net-backend", net_params.as_str()]); + + (command, vunet_socket_path) +} + +pub(crate) fn prepare_swtpm_daemon(tmp_dir: &TempDir) -> (std::process::Command, String) { + let swtpm_tpm_dir = String::from(tmp_dir.as_path().join("swtpm").to_str().unwrap()); + let swtpm_socket_path = String::from( + tmp_dir + .as_path() + .join("swtpm") + .join("swtpm.sock") + .to_str() + .unwrap(), + ); + std::fs::create_dir(&swtpm_tpm_dir).unwrap(); + + let mut swtpm_command = Command::new("swtpm"); + let swtpm_args = [ + "socket", + "--tpmstate", + &format!("dir={swtpm_tpm_dir}"), + "--ctrl", + &format!("type=unixio,path={swtpm_socket_path}"), + "--flags", + "startup-clear", + "--tpm2", + ]; + swtpm_command.args(swtpm_args); + + (swtpm_command, swtpm_socket_path) +} + +pub(crate) fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool { + let mut cmd = Command::new(clh_command("ch-remote")); + cmd.args([&format!("--api-socket={api_socket}"), command]); + + if let Some(arg) = arg { + cmd.arg(arg); + } + let output = cmd.output().unwrap(); + if output.status.success() { + true + } else { + eprintln!("Error running ch-remote command: {:?}", &cmd); + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!("stderr: {stderr}"); + false + } +} + +pub(crate) fn remote_command_w_output( + api_socket: &str, + command: &str, + arg: Option<&str>, +) -> (bool, Vec) { + let mut cmd = Command::new(clh_command("ch-remote")); + cmd.args([&format!("--api-socket={api_socket}"), command]); + + if let Some(arg) = arg { + cmd.arg(arg); + } + + let output = cmd.output().expect("Failed to launch ch-remote"); + + (output.status.success(), output.stdout) +} + +pub(crate) fn resize_command( + api_socket: &str, + desired_vcpus: Option, + desired_ram: Option, + desired_balloon: Option, + event_file: Option<&str>, +) -> bool { + let mut cmd = Command::new(clh_command("ch-remote")); + cmd.args([&format!("--api-socket={api_socket}"), "resize"]); + + if let Some(desired_vcpus) = desired_vcpus { + cmd.arg(format!("--cpus={desired_vcpus}")); + } + + if let Some(desired_ram) = desired_ram { + cmd.arg(format!("--memory={desired_ram}")); + } + + if let Some(desired_balloon) = desired_balloon { + cmd.arg(format!("--balloon={desired_balloon}")); + } + + let ret = cmd.status().expect("Failed to launch ch-remote").success(); + + if let Some(event_path) = event_file { + let latest_events = [ + &MetaEvent { + event: "resizing".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resized".to_string(), + device_id: None, + }, + ]; + // See: #5938 + thread::sleep(std::time::Duration::new(1, 0)); + assert!(check_latest_events_exact(&latest_events, event_path)); + } + + ret +} + +pub(crate) fn resize_zone_command(api_socket: &str, id: &str, desired_size: &str) -> bool { + let mut cmd = Command::new(clh_command("ch-remote")); + cmd.args([ + &format!("--api-socket={api_socket}"), + "resize-zone", + &format!("--id={id}"), + &format!("--size={desired_size}"), + ]); + + cmd.status().expect("Failed to launch ch-remote").success() +} + +pub(crate) fn resize_disk_command(api_socket: &str, id: &str, desired_size: &str) -> bool { + let mut cmd = Command::new(clh_command("ch-remote")); + cmd.args([ + &format!("--api-socket={api_socket}"), + "resize-disk", + &format!("--disk={id}"), + &format!("--size={desired_size}"), + ]); + + cmd.status().expect("Failed to launch ch-remote").success() +} + +// setup OVS-DPDK bridge and ports +pub(crate) fn setup_ovs_dpdk() { + // setup OVS-DPDK + assert!(exec_host_command_status("service openvswitch-switch start").success()); + assert!(exec_host_command_status("ovs-vsctl init").success()); + assert!( + exec_host_command_status("ovs-vsctl set Open_vSwitch . other_config:dpdk-init=true") + .success() + ); + assert!(exec_host_command_status("service openvswitch-switch restart").success()); + + // Create OVS-DPDK bridge and ports + assert!( + exec_host_command_status( + "ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev", + ) + .success() + ); + assert!(exec_host_command_status("ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuserclient options:vhost-server-path=/tmp/dpdkvhostclient1").success()); + assert!(exec_host_command_status("ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuserclient options:vhost-server-path=/tmp/dpdkvhostclient2").success()); + assert!(exec_host_command_status("ip link set up dev ovsbr0").success()); + assert!(exec_host_command_status("service openvswitch-switch restart").success()); +} + +pub(crate) fn cleanup_ovs_dpdk() { + assert!(exec_host_command_status("ovs-vsctl del-br ovsbr0").success()); + exec_host_command_status("rm -f ovs-vsctl /tmp/dpdkvhostclient1 /tmp/dpdkvhostclient2"); +} + +// Setup two guests and ensure they are connected through ovs-dpdk +pub(crate) fn setup_ovs_dpdk_guests( + guest1: &Guest, + guest2: &Guest, + api_socket: &str, + release_binary: bool, +) -> (Child, Child) { + setup_ovs_dpdk(); + + let clh_path = if release_binary { + cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") + }; + + let mut child1 = GuestCommand::new_with_binary_path(guest1, &clh_path) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=0,shared=on"]) + .args(["--memory-zone", "id=mem0,size=1G,shared=on,host_numa_node=0"]) + .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--net", guest1.default_net_string().as_str(), "vhost_user=true,socket=/tmp/dpdkvhostclient1,num_queues=2,queue_size=256,vhost_mode=server"]) + .capture_output() + .spawn() + .unwrap(); + + #[cfg(target_arch = "x86_64")] + let guest_net_iface = "ens5"; + #[cfg(target_arch = "aarch64")] + let guest_net_iface = "enp0s5"; + + let r = std::panic::catch_unwind(|| { + guest1.wait_vm_boot().unwrap(); + + guest1 + .ssh_command(&format!( + "sudo ip addr add 172.100.0.1/24 dev {guest_net_iface}" + )) + .unwrap(); + guest1 + .ssh_command(&format!("sudo ip link set up dev {guest_net_iface}")) + .unwrap(); + + let guest_ip = guest1.network.guest_ip0.clone(); + thread::spawn(move || { + ssh_command_ip( + "nc -l 12345", + &guest_ip, + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT, + ) + .unwrap(); + }); + }); + if r.is_err() { + cleanup_ovs_dpdk(); + + let _ = child1.kill(); + let output = child1.wait_with_output().unwrap(); + handle_child_output(r, &output); + panic!("Test should already be failed/panicked"); // To explicitly mark this block never return + } + + let mut child2 = GuestCommand::new_with_binary_path(guest2, &clh_path) + .args(["--api-socket", api_socket]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=0,shared=on"]) + .args(["--memory-zone", "id=mem0,size=1G,shared=on,host_numa_node=0"]) + .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--net", guest2.default_net_string().as_str(), "vhost_user=true,socket=/tmp/dpdkvhostclient2,num_queues=2,queue_size=256,vhost_mode=server"]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest2.wait_vm_boot().unwrap(); + + guest2 + .ssh_command(&format!( + "sudo ip addr add 172.100.0.2/24 dev {guest_net_iface}" + )) + .unwrap(); + guest2 + .ssh_command(&format!("sudo ip link set up dev {guest_net_iface}")) + .unwrap(); + + // Check the connection works properly between the two VMs + guest2.ssh_command("nc -vz 172.100.0.1 12345").unwrap(); + }); + if r.is_err() { + cleanup_ovs_dpdk(); + + let _ = child1.kill(); + let _ = child2.kill(); + let output = child2.wait_with_output().unwrap(); + handle_child_output(r, &output); + panic!("Test should already be failed/panicked"); // To explicitly mark this block never return + } + + (child1, child2) +} + +pub enum FwType { + Ovmf, + RustHypervisorFirmware, +} + +pub(crate) fn fw_path(_fw_type: FwType) -> String { + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + + let mut fw_path = workload_path; + #[cfg(target_arch = "aarch64")] + fw_path.push("CLOUDHV_EFI.fd"); + #[cfg(target_arch = "x86_64")] + { + match _fw_type { + FwType::Ovmf => fw_path.push(OVMF_NAME), + FwType::RustHypervisorFirmware => fw_path.push("hypervisor-fw"), + } + } + + fw_path.to_str().unwrap().to_string() +} + +// Parse the event_monitor file based on the format that each event +// is followed by a double newline +fn parse_event_file(event_file: &str) -> Vec { + let content = fs::read(event_file).unwrap(); + let mut ret = Vec::new(); + for entry in String::from_utf8_lossy(&content) + .trim() + .split("\n\n") + .collect::>() + { + ret.push(serde_json::from_str(entry).unwrap()); + } + + ret +} + +// Return true if all events from the input 'expected_events' are matched sequentially +// with events from the 'event_file' +pub(crate) fn check_sequential_events(expected_events: &[&MetaEvent], event_file: &str) -> bool { + let json_events = parse_event_file(event_file); + let len = expected_events.len(); + let mut idx = 0; + for e in &json_events { + if idx == len { + break; + } + if expected_events[idx].match_with_json_event(e) { + idx += 1; + } + } + + let ret = idx == len; + + if !ret { + eprintln!( + "\n\n==== Start 'check_sequential_events' failed ==== \ + \n\nexpected_events={expected_events:?}\nactual_events={json_events:?} \ + \n\n==== End 'check_sequential_events' failed ====", + ); + } + + ret +} + +// Return true if all events from the input 'expected_events' are matched exactly +// with events from the 'event_file' +pub(crate) fn check_sequential_events_exact( + expected_events: &[&MetaEvent], + event_file: &str, +) -> bool { + let json_events = parse_event_file(event_file); + assert!(expected_events.len() <= json_events.len()); + let json_events = &json_events[..expected_events.len()]; + + for (idx, e) in json_events.iter().enumerate() { + if !expected_events[idx].match_with_json_event(e) { + eprintln!( + "\n\n==== Start 'check_sequential_events_exact' failed ==== \ + \n\nexpected_events={expected_events:?}\nactual_events={json_events:?} \ + \n\n==== End 'check_sequential_events_exact' failed ====", + ); + + return false; + } + } + + true +} + +// Return true if events from the input 'latest_events' are matched exactly +// with the most recent events from the 'event_file' +pub(crate) fn check_latest_events_exact(latest_events: &[&MetaEvent], event_file: &str) -> bool { + let json_events = parse_event_file(event_file); + assert!(latest_events.len() <= json_events.len()); + let json_events = &json_events[(json_events.len() - latest_events.len())..]; + + for (idx, e) in json_events.iter().enumerate() { + if !latest_events[idx].match_with_json_event(e) { + eprintln!( + "\n\n==== Start 'check_latest_events_exact' failed ==== \ + \n\nexpected_events={latest_events:?}\nactual_events={json_events:?} \ + \n\n==== End 'check_latest_events_exact' failed ====", + ); + + return false; + } + } + + true +} + +pub(super) fn get_msi_interrupt_pattern() -> String { + #[cfg(target_arch = "x86_64")] + { + "PCI-MSI".to_string() + } + #[cfg(target_arch = "aarch64")] + { + if cfg!(feature = "mshv") { + "GICv2m-PCI-MSIX".to_string() + } else { + "ITS-PCI-MSIX".to_string() + } + } +} + +pub(super) type PrepareNetDaemon = dyn Fn( + &TempDir, + &str, + Option<&str>, + Option, + usize, + bool, +) -> (std::process::Command, String); + +pub(super) fn get_ksm_pages_shared() -> u32 { + fs::read_to_string("/sys/kernel/mm/ksm/pages_shared") + .unwrap() + .trim() + .parse::() + .unwrap() +} + +fn _get_vmm_overhead(pid: u32, guest_memory_size: u32) -> HashMap { + let smaps = fs::File::open(format!("/proc/{pid}/smaps")).unwrap(); + let reader = io::BufReader::new(smaps); + + let mut skip_map: bool = false; + let mut region_name: String = String::new(); + let mut region_maps = HashMap::new(); + for line in reader.lines() { + let l = line.unwrap(); + + if l.contains('-') { + let values: Vec<&str> = l.split_whitespace().collect(); + region_name = values.last().unwrap().trim().to_string(); + if region_name == "0" { + region_name = "anonymous".to_string(); + } + } + + // Each section begins with something that looks like: + // Size: 2184 kB + if l.starts_with("Size:") { + let values: Vec<&str> = l.split_whitespace().collect(); + let map_size = values[1].parse::().unwrap(); + // We skip the assigned guest RAM map, its RSS is only + // dependent on the guest actual memory usage. + // Everything else can be added to the VMM overhead. + skip_map = map_size >= guest_memory_size; + continue; + } + + // If this is a map we're taking into account, then we only + // count the RSS. The sum of all counted RSS is the VMM overhead. + if !skip_map && l.starts_with("Rss:") { + let values: Vec<&str> = l.split_whitespace().collect(); + let value = values[1].trim().parse::().unwrap(); + *region_maps.entry(region_name.clone()).or_insert(0) += value; + } + } + + region_maps +} + +pub(crate) fn get_vmm_overhead(pid: u32, guest_memory_size: u32) -> u32 { + let mut total = 0; + + for (region_name, value) in &_get_vmm_overhead(pid, guest_memory_size) { + eprintln!("{region_name}: {value}"); + total += value; + } + + total +} + +pub(crate) fn process_rss_kib(pid: u32) -> usize { + let command = format!("ps -q {pid} -o rss="); + let rss = exec_host_command_output(&command); + String::from_utf8_lossy(&rss.stdout).trim().parse().unwrap() +} + +#[derive(PartialEq, Eq, PartialOrd)] +pub struct Counters { + rx_bytes: u64, + rx_frames: u64, + tx_bytes: u64, + tx_frames: u64, + read_bytes: u64, + write_bytes: u64, + read_ops: u64, + write_ops: u64, +} + +pub(crate) fn get_counters(api_socket: &str) -> Counters { + // Get counters + let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "counters", None); + assert!(cmd_success); + + let counters: HashMap<&str, HashMap<&str, u64>> = + serde_json::from_slice(&cmd_output).unwrap_or_default(); + + let rx_bytes = *counters.get("_net2").unwrap().get("rx_bytes").unwrap(); + let rx_frames = *counters.get("_net2").unwrap().get("rx_frames").unwrap(); + let tx_bytes = *counters.get("_net2").unwrap().get("tx_bytes").unwrap(); + let tx_frames = *counters.get("_net2").unwrap().get("tx_frames").unwrap(); + + let read_bytes = *counters.get("_disk0").unwrap().get("read_bytes").unwrap(); + let write_bytes = *counters.get("_disk0").unwrap().get("write_bytes").unwrap(); + let read_ops = *counters.get("_disk0").unwrap().get("read_ops").unwrap(); + let write_ops = *counters.get("_disk0").unwrap().get("write_ops").unwrap(); + + Counters { + rx_bytes, + rx_frames, + tx_bytes, + tx_frames, + read_bytes, + write_bytes, + read_ops, + write_ops, + } +} + +pub(super) fn pty_read(mut pty: std::fs::File) -> Receiver { + let (tx, rx) = mpsc::channel::(); + thread::spawn(move || { + loop { + thread::sleep(std::time::Duration::new(1, 0)); + let mut buf = [0; 512]; + match pty.read(&mut buf) { + Ok(_bytes) => { + let output = std::str::from_utf8(&buf).unwrap().to_string(); + match tx.send(output) { + Ok(_) => (), + Err(_) => break, + } + } + Err(_) => break, + } + } + }); + rx +} + +pub(crate) fn get_pty_path(api_socket: &str, pty_type: &str) -> PathBuf { + let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None); + assert!(cmd_success); + let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default(); + assert_eq!("Pty", info["config"][pty_type]["mode"]); + PathBuf::from( + info["config"][pty_type]["file"] + .as_str() + .expect("Missing pty path"), + ) +} + +// VFIO test network setup. +// We reserve a different IP class for it: 172.18.0.0/24. +#[cfg(target_arch = "x86_64")] +pub(crate) fn setup_vfio_network_interfaces() { + // 'vfio-br0' + assert!(exec_host_command_status("sudo ip link add name vfio-br0 type bridge").success()); + assert!(exec_host_command_status("sudo ip link set vfio-br0 up").success()); + assert!(exec_host_command_status("sudo ip addr add 172.18.0.1/24 dev vfio-br0").success()); + // 'vfio-tap0' + assert!(exec_host_command_status("sudo ip tuntap add vfio-tap0 mode tap").success()); + assert!(exec_host_command_status("sudo ip link set vfio-tap0 master vfio-br0").success()); + assert!(exec_host_command_status("sudo ip link set vfio-tap0 up").success()); + // 'vfio-tap1' + assert!(exec_host_command_status("sudo ip tuntap add vfio-tap1 mode tap").success()); + assert!(exec_host_command_status("sudo ip link set vfio-tap1 master vfio-br0").success()); + assert!(exec_host_command_status("sudo ip link set vfio-tap1 up").success()); + // 'vfio-tap2' + assert!(exec_host_command_status("sudo ip tuntap add vfio-tap2 mode tap").success()); + assert!(exec_host_command_status("sudo ip link set vfio-tap2 master vfio-br0").success()); + assert!(exec_host_command_status("sudo ip link set vfio-tap2 up").success()); + // 'vfio-tap3' + assert!(exec_host_command_status("sudo ip tuntap add vfio-tap3 mode tap").success()); + assert!(exec_host_command_status("sudo ip link set vfio-tap3 master vfio-br0").success()); + assert!(exec_host_command_status("sudo ip link set vfio-tap3 up").success()); +} + +// Tear VFIO test network down +#[cfg(target_arch = "x86_64")] +pub(crate) fn cleanup_vfio_network_interfaces() { + assert!(exec_host_command_status("sudo ip link del vfio-br0").success()); + assert!(exec_host_command_status("sudo ip link del vfio-tap0").success()); + assert!(exec_host_command_status("sudo ip link del vfio-tap1").success()); + assert!(exec_host_command_status("sudo ip link del vfio-tap2").success()); + assert!(exec_host_command_status("sudo ip link del vfio-tap3").success()); +} + +pub(crate) fn balloon_size(api_socket: &str) -> u64 { + let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None); + assert!(cmd_success); + + let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default(); + let total_mem = &info["config"]["memory"]["size"] + .to_string() + .parse::() + .unwrap(); + let actual_mem = &info["memory_actual_size"] + .to_string() + .parse::() + .unwrap(); + total_mem - actual_mem +} + +pub(crate) fn vm_state(api_socket: &str) -> String { + let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None); + assert!(cmd_success); + + let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default(); + let state = &info["state"].as_str().unwrap(); + + state.to_string() +} + +pub(crate) fn make_virtio_block_guest(factory: &GuestFactory, image_name: &str) -> Guest { + let disk_config = UbuntuDiskConfig::new(image_name.to_string()); + factory.create_guest(Box::new(disk_config)).with_cpu(4) +} + +pub(crate) fn compute_backing_checksum( + path_or_image_name: impl AsRef, +) -> Option<(std::path::PathBuf, String, u32)> { + let path = resolve_disk_path(path_or_image_name); + + let mut file = File::open(&path).ok()?; + if !matches!( + block::detect_image_type(&mut file).ok()?, + block::ImageType::Qcow2 + ) { + return None; + } + + let info = get_image_info(&path)?; + + let backing_file = info["backing-filename"].as_str()?; + let backing_path = if std::path::Path::new(backing_file).is_absolute() { + std::path::PathBuf::from(backing_file) + } else { + path.parent() + .unwrap_or_else(|| std::path::Path::new(".")) + .join(backing_file) + }; + + let backing_info = get_image_info(&backing_path)?; + let backing_format = backing_info["format"].as_str()?.to_string(); + let mut file = File::open(&backing_path).ok()?; + let file_size = file.metadata().ok()?.len(); + let checksum = compute_file_checksum(&mut file, file_size); + + Some((backing_path, backing_format, checksum)) +} + +/// Uses `qemu-img check` to verify disk image consistency. +/// +/// Supported formats are `qcow2` (compressed and uncompressed), +/// `vhdx`, `qed`, `parallels`, `vmdk`, and `vdi`. See man page +/// for more details. +/// +/// It takes either a full path to the image or just the name of +/// the image located in the `workloads` directory. +/// +/// For QCOW2 images with backing files, also verifies the backing file +/// integrity and checks that the backing file hasn't been modified +/// during the test. +/// +/// For QCOW2 v3 images, also verifies the dirty bit is cleared. +pub(crate) fn disk_check_consistency( + path_or_image_name: impl AsRef, + initial_backing_checksum: Option<(std::path::PathBuf, String, u32)>, +) { + let path = resolve_disk_path(path_or_image_name); + let output = run_qemu_img(&path, &["check"], None); + + assert!( + output.status.success(), + "qemu-img check failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + match check_dirty_flag(&path) { + Ok(Some(dirty)) => { + assert!(!dirty, "QCOW2 image shutdown unclean"); + } + Ok(None) => {} // Not a QCOW2 v3 image, skip dirty flag check + Err(e) => panic!("Failed to check dirty flag: {e}"), + } + + if let Some((backing_path, format, initial_checksum)) = initial_backing_checksum { + if format.parse::().ok() != Some(block::qcow::ImageType::Raw) { + let output = run_qemu_img(&backing_path, &["check"], None); + + assert!( + output.status.success(), + "qemu-img check of backing file failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + let mut file = File::open(&backing_path).unwrap(); + let file_size = file.metadata().unwrap().len(); + assert_eq!( + initial_checksum, + compute_file_checksum(&mut file, file_size) + ); + } +} + +pub(crate) fn run_qemu_img( + path: &std::path::Path, + args: &[&str], + trailing_args: Option<&[&str]>, +) -> std::process::Output { + let mut cmd = std::process::Command::new("qemu-img"); + cmd.arg(args[0]) + .args(&args[1..]) + .arg(path.to_str().unwrap()); + if let Some(extra) = trailing_args { + cmd.args(extra); + } + cmd.output().unwrap() +} + +fn get_image_info(path: &std::path::Path) -> Option { + let output = run_qemu_img(path, &["info", "-U", "--output=json"], None); + + output.status.success().then_some(())?; + serde_json::from_slice(&output.stdout).ok() +} + +fn get_qcow2_v3_info(path: &Path) -> Result, String> { + let info = get_image_info(path) + .ok_or_else(|| format!("qemu-img info failed for {}", path.display()))?; + if info["format"].as_str() != Some("qcow2") { + return Ok(None); + } + // QCOW2 v3 has compat "1.1", v2 has "0.10" + if info["format-specific"]["data"]["compat"].as_str() != Some("1.1") { + return Ok(None); + } + Ok(Some(info)) +} + +pub(crate) fn check_dirty_flag(path: &Path) -> Result, String> { + Ok(get_qcow2_v3_info(path)?.and_then(|info| info["dirty-flag"].as_bool())) +} + +pub(crate) fn check_corrupt_flag(path: &Path) -> Result, String> { + Ok(get_qcow2_v3_info(path)? + .and_then(|info| info["format-specific"]["data"]["corrupt"].as_bool())) +} + +pub(crate) fn set_corrupt_flag(path: &Path, corrupt: bool) -> io::Result<()> { + let mut file = OpenOptions::new().read(true).write(true).open(path)?; + + file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; + let mut buf = [0u8; 8]; + file.read_exact(&mut buf)?; + let mut features = u64::from_be_bytes(buf); + + if corrupt { + features |= 0x02; + } else { + features &= !0x02; + } + + file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; + file.write_all(&features.to_be_bytes())?; + file.sync_all()?; + Ok(()) +} + +fn resolve_disk_path(path_or_image_name: impl AsRef) -> std::path::PathBuf { + if path_or_image_name.as_ref().exists() { + // A full path is provided + path_or_image_name.as_ref().to_path_buf() + } else { + // An image name is provided + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + workload_path.as_path().join(path_or_image_name.as_ref()) + } +} + +pub(crate) fn compute_file_checksum(reader: &mut dyn std::io::Read, size: u64) -> u32 { + // Read first 16MB or entire data if smaller + let read_size = cmp::min(size, 16 * 1024 * 1024) as usize; + + let mut buffer = vec![0u8; read_size]; + reader.read_exact(&mut buffer).unwrap(); + + // DJB2 hash + let mut hash: u32 = 5381; + for byte in buffer.iter() { + hash = hash.wrapping_mul(33).wrapping_add(*byte as u32); + } + hash +} + +pub(crate) fn get_reboot_count(guest: &Guest) -> u32 { + guest + .ssh_command("sudo last | grep -c reboot") + .unwrap() + .trim() + .parse::() + .unwrap_or_default() +} + +pub(crate) fn enable_guest_watchdog(guest: &Guest, watchdog_sec: u32) { + // Check for PCI device + assert!( + guest + .does_device_vendor_pair_match("0x1063", "0x1af4") + .unwrap_or_default() + ); + + // Enable systemd watchdog + guest + .ssh_command(&format!( + "echo RuntimeWatchdogSec={watchdog_sec}s | sudo tee -a /etc/systemd/system.conf" + )) + .unwrap(); + + guest.ssh_command("sudo systemctl daemon-reexec").unwrap(); +} + +pub(crate) fn make_guest_panic(guest: &Guest) { + // Check for pvpanic device + assert!( + guest + .does_device_vendor_pair_match("0x0011", "0x1b36") + .unwrap_or_default() + ); + + // Trigger guest a panic + guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 3e2a33da70..4b8a6b2e37 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -8,3086 +8,26 @@ // hence have known dead-code. This annotation silences dead-code // related warnings for our quality workflow to pass. #![allow(dead_code)] - -use std::collections::HashMap; -use std::ffi::CStr; use std::fs::{File, OpenOptions, copy}; -use std::io::{BufRead, Read, Seek, SeekFrom, Write}; +use std::io::{Read, Seek, Write}; use std::net::TcpListener; use std::os::unix::io::AsRawFd; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process::{Child, Command, Stdio}; use std::string::String; -use std::sync::mpsc::Receiver; -use std::sync::{Mutex, mpsc}; +use std::sync::Mutex; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use std::{cmp, fs, io, thread}; +use std::{fs, thread}; use block::ImageType; -use net_util::MacAddr; use test_infra::*; use vmm_sys_util::tempdir::TempDir; use vmm_sys_util::tempfile::TempFile; use wait_timeout::ChildExt; -// This enum exists to make it more convenient to -// implement test for both D-Bus and REST APIs. -enum TargetApi { - // API socket - HttpApi(String), - // well known service name, object path - DBusApi(String, String), -} - -impl TargetApi { - fn new_http_api(tmp_dir: &TempDir) -> Self { - Self::HttpApi(temp_api_path(tmp_dir)) - } - - fn new_dbus_api(tmp_dir: &TempDir) -> Self { - // `tmp_dir` is in the form of "/tmp/chXXXXXX" - // and we take the `chXXXXXX` part as a unique identifier for the guest - let id = tmp_dir.as_path().file_name().unwrap().to_str().unwrap(); - - Self::DBusApi( - format!("org.cloudhypervisor.{id}"), - format!("/org/cloudhypervisor/{id}"), - ) - } - - fn guest_args(&self) -> Vec { - match self { - TargetApi::HttpApi(api_socket) => { - vec![format!("--api-socket={}", api_socket.as_str())] - } - TargetApi::DBusApi(service_name, object_path) => { - vec![ - format!("--dbus-service-name={}", service_name.as_str()), - format!("--dbus-object-path={}", object_path.as_str()), - ] - } - } - } - - fn remote_args(&self) -> Vec { - // `guest_args` and `remote_args` are consistent with each other - self.guest_args() - } - - fn remote_command(&self, command: &str, arg: Option<&str>) -> bool { - let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args(self.remote_args()); - cmd.arg(command); - - if let Some(arg) = arg { - cmd.arg(arg); - } - - let output = cmd.output().unwrap(); - if output.status.success() { - true - } else { - eprintln!("Error running ch-remote command: {:?}", &cmd); - let stderr = String::from_utf8_lossy(&output.stderr); - eprintln!("stderr: {stderr}"); - false - } - } -} - -// Start cloud-hypervisor with no VM parameters, only the API server running. -// From the API: Create a VM, boot it and check that it looks as expected. -fn _test_api_create_boot(target_api: &TargetApi, guest: &Guest) { - let mut child = GuestCommand::new(guest) - .args(target_api.guest_args()) - .capture_output() - .spawn() - .unwrap(); - - thread::sleep(std::time::Duration::new(1, 0)); - - // Verify API server is running - assert!(target_api.remote_command("ping", None)); - - // Create the VM first - let request_body = guest.api_create_body(); - - let temp_config_path = guest.tmp_dir.as_path().join("config"); - std::fs::write(&temp_config_path, request_body).unwrap(); - let create_config = temp_config_path.as_os_str().to_str().unwrap(); - - assert!(target_api.remote_command("create", Some(create_config),)); - - // Then boot it - assert!(target_api.remote_command("boot", None)); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - // Check that the VM booted as expected - guest.validate_cpu_count(None); - guest.validate_memory(None); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -// Start cloud-hypervisor with no VM parameters, only the API server running. -// From the API: Create a VM, boot it and check it can be shutdown and then -// booted again -fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { - let mut child = GuestCommand::new(guest) - .args(target_api.guest_args()) - .capture_output() - .spawn() - .unwrap(); - - thread::sleep(std::time::Duration::new(1, 0)); - - // Verify API server is running - assert!(target_api.remote_command("ping", None)); - - // Create the VM first - let request_body = guest.api_create_body(); - - let temp_config_path = guest.tmp_dir.as_path().join("config"); - std::fs::write(&temp_config_path, request_body).unwrap(); - let create_config = temp_config_path.as_os_str().to_str().unwrap(); - - let r = std::panic::catch_unwind(|| { - assert!(target_api.remote_command("create", Some(create_config))); - - // Then boot it - assert!(target_api.remote_command("boot", None)); - - guest.wait_vm_boot().unwrap(); - - // Check that the VM booted as expected - guest.validate_cpu_count(None); - guest.validate_memory(None); - - // Sync and shutdown without powering off to prevent filesystem - // corruption. - guest.ssh_command("sync").unwrap(); - guest.ssh_command("sudo shutdown -H now").unwrap(); - - // Wait for the guest to be fully shutdown - thread::sleep(std::time::Duration::new(20, 0)); - - // Then shut it down - assert!(target_api.remote_command("shutdown", None)); - - // Then boot it again - assert!(target_api.remote_command("boot", None)); - - guest.wait_vm_boot().unwrap(); - - // Check that the VM booted as expected - guest.validate_cpu_count(None); - guest.validate_memory(None); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -// Start cloud-hypervisor with no VM parameters, only the API server running. -// From the API: Create a VM, boot it and check it can be deleted and then recreated -// booted again. -fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { - let mut child = GuestCommand::new(guest) - .args(target_api.guest_args()) - .capture_output() - .spawn() - .unwrap(); - - thread::sleep(std::time::Duration::new(1, 0)); - - // Verify API server is running - assert!(target_api.remote_command("ping", None)); - - // Create the VM first - let request_body = guest.api_create_body(); - - let temp_config_path = guest.tmp_dir.as_path().join("config"); - std::fs::write(&temp_config_path, request_body).unwrap(); - let create_config = temp_config_path.as_os_str().to_str().unwrap(); - - let r = std::panic::catch_unwind(|| { - assert!(target_api.remote_command("create", Some(create_config))); - - // Then boot it - assert!(target_api.remote_command("boot", None)); - - guest.wait_vm_boot().unwrap(); - - // Check that the VM booted as expected - guest.validate_cpu_count(None); - guest.validate_memory(None); - - // Sync and shutdown without powering off to prevent filesystem - // corruption. - guest.ssh_command("sync").unwrap(); - guest.ssh_command("sudo shutdown -H now").unwrap(); - - // Wait for the guest to be fully shutdown - thread::sleep(std::time::Duration::new(20, 0)); - - // Then delete it - assert!(target_api.remote_command("delete", None)); - - assert!(target_api.remote_command("create", Some(create_config))); - - // Then boot it again - assert!(target_api.remote_command("boot", None)); - - guest.wait_vm_boot().unwrap(); - - // Check that the VM booted as expected - guest.validate_cpu_count(None); - guest.validate_memory(None); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -// Start cloud-hypervisor with no VM parameters, only the API server running. -// From the API: Create a VM, boot it and check that it looks as expected. -// Then we pause the VM, check that it's no longer available. -// Finally we resume the VM and check that it's available. -fn _test_api_pause_resume(target_api: &TargetApi, guest: &Guest) { - let mut child = GuestCommand::new(guest) - .args(target_api.guest_args()) - .capture_output() - .spawn() - .unwrap(); - - thread::sleep(std::time::Duration::new(1, 0)); - - // Verify API server is running - assert!(target_api.remote_command("ping", None)); - - // Create the VM first - let request_body = guest.api_create_body(); - - let temp_config_path = guest.tmp_dir.as_path().join("config"); - std::fs::write(&temp_config_path, request_body).unwrap(); - let create_config = temp_config_path.as_os_str().to_str().unwrap(); - - assert!(target_api.remote_command("create", Some(create_config))); - - // Then boot it - assert!(target_api.remote_command("boot", None)); - thread::sleep(std::time::Duration::new(20, 0)); - - let r = std::panic::catch_unwind(|| { - // Check that the VM booted as expected - guest.validate_cpu_count(None); - guest.validate_memory(None); - - // We now pause the VM - assert!(target_api.remote_command("pause", None)); - - // Check pausing again fails - assert!(!target_api.remote_command("pause", None)); - - thread::sleep(std::time::Duration::new(2, 0)); - - // SSH into the VM should fail - ssh_command_ip( - "grep -c processor /proc/cpuinfo", - &guest.network.guest_ip0, - 2, - 5, - ) - .unwrap_err(); - - // Resume the VM - assert!(target_api.remote_command("resume", None)); - - // Check resuming again fails - assert!(!target_api.remote_command("resume", None)); - - thread::sleep(std::time::Duration::new(2, 0)); - - // Now we should be able to SSH back in and get the right number of CPUs - guest.validate_cpu_count(None); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn _test_pty_interaction(pty_path: PathBuf) { - let mut cf = std::fs::OpenOptions::new() - .write(true) - .read(true) - .open(pty_path) - .unwrap(); - - // Some dumb sleeps but we don't want to write - // before the console is up and we don't want - // to try and write the next line before the - // login process is ready. - thread::sleep(std::time::Duration::new(5, 0)); - assert_eq!(cf.write(b"cloud\n").unwrap(), 6); - thread::sleep(std::time::Duration::new(2, 0)); - assert_eq!(cf.write(b"cloud123\n").unwrap(), 9); - thread::sleep(std::time::Duration::new(2, 0)); - assert_eq!(cf.write(b"echo test_pty_console\n").unwrap(), 22); - thread::sleep(std::time::Duration::new(2, 0)); - - // read pty and ensure they have a login shell - // some fairly hacky workarounds to avoid looping - // forever in case the channel is blocked getting output - let ptyc = pty_read(cf); - let mut empty = 0; - let mut prev = String::new(); - loop { - thread::sleep(std::time::Duration::new(2, 0)); - match ptyc.try_recv() { - Ok(line) => { - empty = 0; - prev = prev + &line; - if prev.contains("test_pty_console") { - break; - } - } - Err(mpsc::TryRecvError::Empty) => { - empty += 1; - assert!(empty <= 5, "No login on pty"); - } - _ => { - panic!("No login on pty") - } - } - } -} - -fn prepare_virtiofsd(tmp_dir: &TempDir, shared_dir: &str) -> (std::process::Child, String) { - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - - let mut virtiofsd_path = workload_path; - virtiofsd_path.push("virtiofsd"); - let virtiofsd_path = String::from(virtiofsd_path.to_str().unwrap()); - - let virtiofsd_socket_path = - String::from(tmp_dir.as_path().join("virtiofs.sock").to_str().unwrap()); - - // Start the daemon - let child = Command::new(virtiofsd_path.as_str()) - .args(["--shared-dir", shared_dir]) - .args(["--socket-path", virtiofsd_socket_path.as_str()]) - .args(["--cache", "never"]) - .args(["--tag", "myfs"]) - .spawn() - .unwrap(); - - thread::sleep(std::time::Duration::new(10, 0)); - - (child, virtiofsd_socket_path) -} - -fn prepare_vubd( - tmp_dir: &TempDir, - blk_img: &str, - num_queues: usize, - rdonly: bool, - direct: bool, -) -> (std::process::Child, String) { - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - - let mut blk_file_path = workload_path; - blk_file_path.push(blk_img); - let blk_file_path = String::from(blk_file_path.to_str().unwrap()); - - let vubd_socket_path = String::from(tmp_dir.as_path().join("vub.sock").to_str().unwrap()); - - // Start the daemon - let child = Command::new(clh_command("vhost_user_block")) - .args([ - "--block-backend", - format!( - "path={blk_file_path},socket={vubd_socket_path},num_queues={num_queues},readonly={rdonly},direct={direct}" - ) - .as_str(), - ]) - .spawn() - .unwrap(); - - thread::sleep(std::time::Duration::new(10, 0)); - - (child, vubd_socket_path) -} - -fn temp_vsock_path(tmp_dir: &TempDir) -> String { - String::from(tmp_dir.as_path().join("vsock").to_str().unwrap()) -} - -fn temp_api_path(tmp_dir: &TempDir) -> String { - String::from( - tmp_dir - .as_path() - .join("cloud-hypervisor.sock") - .to_str() - .unwrap(), - ) -} - -fn temp_event_monitor_path(tmp_dir: &TempDir) -> String { - String::from(tmp_dir.as_path().join("event.json").to_str().unwrap()) -} - -// Creates the directory and returns the path. -fn temp_snapshot_dir_path(tmp_dir: &TempDir) -> String { - let snapshot_dir = String::from(tmp_dir.as_path().join("snapshot").to_str().unwrap()); - std::fs::create_dir(&snapshot_dir).unwrap(); - snapshot_dir -} - -fn temp_vmcore_file_path(tmp_dir: &TempDir) -> String { - String::from(tmp_dir.as_path().join("vmcore").to_str().unwrap()) -} - -fn cloud_hypervisor_release_path() -> String { - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - - let mut ch_release_path = workload_path; - #[cfg(target_arch = "x86_64")] - ch_release_path.push("cloud-hypervisor-static"); - #[cfg(target_arch = "aarch64")] - ch_release_path.push("cloud-hypervisor-static-aarch64"); - - ch_release_path.into_os_string().into_string().unwrap() -} - -fn prepare_vhost_user_net_daemon( - tmp_dir: &TempDir, - ip: &str, - tap: Option<&str>, - mtu: Option, - num_queues: usize, - client_mode: bool, -) -> (std::process::Command, String) { - let vunet_socket_path = String::from(tmp_dir.as_path().join("vunet.sock").to_str().unwrap()); - - // Start the daemon - let mut net_params = format!( - "ip={ip},mask=255.255.255.128,socket={vunet_socket_path},num_queues={num_queues},queue_size=1024,client={client_mode}" - ); - - if let Some(tap) = tap { - net_params.push_str(format!(",tap={tap}").as_str()); - } - - if let Some(mtu) = mtu { - net_params.push_str(format!(",mtu={mtu}").as_str()); - } - - let mut command = Command::new(clh_command("vhost_user_net")); - command.args(["--net-backend", net_params.as_str()]); - - (command, vunet_socket_path) -} - -fn prepare_swtpm_daemon(tmp_dir: &TempDir) -> (std::process::Command, String) { - let swtpm_tpm_dir = String::from(tmp_dir.as_path().join("swtpm").to_str().unwrap()); - let swtpm_socket_path = String::from( - tmp_dir - .as_path() - .join("swtpm") - .join("swtpm.sock") - .to_str() - .unwrap(), - ); - std::fs::create_dir(&swtpm_tpm_dir).unwrap(); - - let mut swtpm_command = Command::new("swtpm"); - let swtpm_args = [ - "socket", - "--tpmstate", - &format!("dir={swtpm_tpm_dir}"), - "--ctrl", - &format!("type=unixio,path={swtpm_socket_path}"), - "--flags", - "startup-clear", - "--tpm2", - ]; - swtpm_command.args(swtpm_args); - - (swtpm_command, swtpm_socket_path) -} - -fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool { - let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args([&format!("--api-socket={api_socket}"), command]); - - if let Some(arg) = arg { - cmd.arg(arg); - } - let output = cmd.output().unwrap(); - if output.status.success() { - true - } else { - eprintln!("Error running ch-remote command: {:?}", &cmd); - let stderr = String::from_utf8_lossy(&output.stderr); - eprintln!("stderr: {stderr}"); - false - } -} - -fn remote_command_w_output(api_socket: &str, command: &str, arg: Option<&str>) -> (bool, Vec) { - let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args([&format!("--api-socket={api_socket}"), command]); - - if let Some(arg) = arg { - cmd.arg(arg); - } - - let output = cmd.output().expect("Failed to launch ch-remote"); - - (output.status.success(), output.stdout) -} - -fn resize_command( - api_socket: &str, - desired_vcpus: Option, - desired_ram: Option, - desired_balloon: Option, - event_file: Option<&str>, -) -> bool { - let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args([&format!("--api-socket={api_socket}"), "resize"]); - - if let Some(desired_vcpus) = desired_vcpus { - cmd.arg(format!("--cpus={desired_vcpus}")); - } - - if let Some(desired_ram) = desired_ram { - cmd.arg(format!("--memory={desired_ram}")); - } - - if let Some(desired_balloon) = desired_balloon { - cmd.arg(format!("--balloon={desired_balloon}")); - } - - let ret = cmd.status().expect("Failed to launch ch-remote").success(); - - if let Some(event_path) = event_file { - let latest_events = [ - &MetaEvent { - event: "resizing".to_string(), - device_id: None, - }, - &MetaEvent { - event: "resized".to_string(), - device_id: None, - }, - ]; - // See: #5938 - thread::sleep(std::time::Duration::new(1, 0)); - assert!(check_latest_events_exact(&latest_events, event_path)); - } - - ret -} - -fn resize_zone_command(api_socket: &str, id: &str, desired_size: &str) -> bool { - let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args([ - &format!("--api-socket={api_socket}"), - "resize-zone", - &format!("--id={id}"), - &format!("--size={desired_size}"), - ]); - - cmd.status().expect("Failed to launch ch-remote").success() -} - -fn resize_disk_command(api_socket: &str, id: &str, desired_size: &str) -> bool { - let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args([ - &format!("--api-socket={api_socket}"), - "resize-disk", - &format!("--disk={id}"), - &format!("--size={desired_size}"), - ]); - - cmd.status().expect("Failed to launch ch-remote").success() -} - -// setup OVS-DPDK bridge and ports -fn setup_ovs_dpdk() { - // setup OVS-DPDK - assert!(exec_host_command_status("service openvswitch-switch start").success()); - assert!(exec_host_command_status("ovs-vsctl init").success()); - assert!( - exec_host_command_status("ovs-vsctl set Open_vSwitch . other_config:dpdk-init=true") - .success() - ); - assert!(exec_host_command_status("service openvswitch-switch restart").success()); - - // Create OVS-DPDK bridge and ports - assert!( - exec_host_command_status( - "ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev", - ) - .success() - ); - assert!(exec_host_command_status("ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuserclient options:vhost-server-path=/tmp/dpdkvhostclient1").success()); - assert!(exec_host_command_status("ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuserclient options:vhost-server-path=/tmp/dpdkvhostclient2").success()); - assert!(exec_host_command_status("ip link set up dev ovsbr0").success()); - assert!(exec_host_command_status("service openvswitch-switch restart").success()); -} -fn cleanup_ovs_dpdk() { - assert!(exec_host_command_status("ovs-vsctl del-br ovsbr0").success()); - exec_host_command_status("rm -f ovs-vsctl /tmp/dpdkvhostclient1 /tmp/dpdkvhostclient2"); -} -// Setup two guests and ensure they are connected through ovs-dpdk -fn setup_ovs_dpdk_guests( - guest1: &Guest, - guest2: &Guest, - api_socket: &str, - release_binary: bool, -) -> (Child, Child) { - setup_ovs_dpdk(); - - let clh_path = if release_binary { - cloud_hypervisor_release_path() - } else { - clh_command("cloud-hypervisor") - }; - - let mut child1 = GuestCommand::new_with_binary_path(guest1, &clh_path) - .args(["--cpus", "boot=2"]) - .args(["--memory", "size=0,shared=on"]) - .args(["--memory-zone", "id=mem0,size=1G,shared=on,host_numa_node=0"]) - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", guest1.default_net_string().as_str(), "vhost_user=true,socket=/tmp/dpdkvhostclient1,num_queues=2,queue_size=256,vhost_mode=server"]) - .capture_output() - .spawn() - .unwrap(); - - #[cfg(target_arch = "x86_64")] - let guest_net_iface = "ens5"; - #[cfg(target_arch = "aarch64")] - let guest_net_iface = "enp0s5"; - - let r = std::panic::catch_unwind(|| { - guest1.wait_vm_boot().unwrap(); - - guest1 - .ssh_command(&format!( - "sudo ip addr add 172.100.0.1/24 dev {guest_net_iface}" - )) - .unwrap(); - guest1 - .ssh_command(&format!("sudo ip link set up dev {guest_net_iface}")) - .unwrap(); - - let guest_ip = guest1.network.guest_ip0.clone(); - thread::spawn(move || { - ssh_command_ip( - "nc -l 12345", - &guest_ip, - DEFAULT_SSH_RETRIES, - DEFAULT_SSH_TIMEOUT, - ) - .unwrap(); - }); - }); - if r.is_err() { - cleanup_ovs_dpdk(); - - let _ = child1.kill(); - let output = child1.wait_with_output().unwrap(); - handle_child_output(r, &output); - panic!("Test should already be failed/panicked"); // To explicitly mark this block never return - } - - let mut child2 = GuestCommand::new_with_binary_path(guest2, &clh_path) - .args(["--api-socket", api_socket]) - .args(["--cpus", "boot=2"]) - .args(["--memory", "size=0,shared=on"]) - .args(["--memory-zone", "id=mem0,size=1G,shared=on,host_numa_node=0"]) - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", guest2.default_net_string().as_str(), "vhost_user=true,socket=/tmp/dpdkvhostclient2,num_queues=2,queue_size=256,vhost_mode=server"]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest2.wait_vm_boot().unwrap(); - - guest2 - .ssh_command(&format!( - "sudo ip addr add 172.100.0.2/24 dev {guest_net_iface}" - )) - .unwrap(); - guest2 - .ssh_command(&format!("sudo ip link set up dev {guest_net_iface}")) - .unwrap(); - - // Check the connection works properly between the two VMs - guest2.ssh_command("nc -vz 172.100.0.1 12345").unwrap(); - }); - if r.is_err() { - cleanup_ovs_dpdk(); - - let _ = child1.kill(); - let _ = child2.kill(); - let output = child2.wait_with_output().unwrap(); - handle_child_output(r, &output); - panic!("Test should already be failed/panicked"); // To explicitly mark this block never return - } - - (child1, child2) -} - -enum FwType { - Ovmf, - RustHypervisorFirmware, -} - -fn fw_path(_fw_type: FwType) -> String { - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - - let mut fw_path = workload_path; - #[cfg(target_arch = "aarch64")] - fw_path.push("CLOUDHV_EFI.fd"); - #[cfg(target_arch = "x86_64")] - { - match _fw_type { - FwType::Ovmf => fw_path.push(OVMF_NAME), - FwType::RustHypervisorFirmware => fw_path.push("hypervisor-fw"), - } - } - - fw_path.to_str().unwrap().to_string() -} - -// Parse the event_monitor file based on the format that each event -// is followed by a double newline -fn parse_event_file(event_file: &str) -> Vec { - let content = fs::read(event_file).unwrap(); - let mut ret = Vec::new(); - for entry in String::from_utf8_lossy(&content) - .trim() - .split("\n\n") - .collect::>() - { - ret.push(serde_json::from_str(entry).unwrap()); - } - - ret -} - -// Return true if all events from the input 'expected_events' are matched sequentially -// with events from the 'event_file' -fn check_sequential_events(expected_events: &[&MetaEvent], event_file: &str) -> bool { - let json_events = parse_event_file(event_file); - let len = expected_events.len(); - let mut idx = 0; - for e in &json_events { - if idx == len { - break; - } - if expected_events[idx].match_with_json_event(e) { - idx += 1; - } - } - - let ret = idx == len; - - if !ret { - eprintln!( - "\n\n==== Start 'check_sequential_events' failed ==== \ - \n\nexpected_events={expected_events:?}\nactual_events={json_events:?} \ - \n\n==== End 'check_sequential_events' failed ====", - ); - } - - ret -} - -// Return true if all events from the input 'expected_events' are matched exactly -// with events from the 'event_file' -fn check_sequential_events_exact(expected_events: &[&MetaEvent], event_file: &str) -> bool { - let json_events = parse_event_file(event_file); - assert!(expected_events.len() <= json_events.len()); - let json_events = &json_events[..expected_events.len()]; - - for (idx, e) in json_events.iter().enumerate() { - if !expected_events[idx].match_with_json_event(e) { - eprintln!( - "\n\n==== Start 'check_sequential_events_exact' failed ==== \ - \n\nexpected_events={expected_events:?}\nactual_events={json_events:?} \ - \n\n==== End 'check_sequential_events_exact' failed ====", - ); - - return false; - } - } - - true -} - -// Return true if events from the input 'latest_events' are matched exactly -// with the most recent events from the 'event_file' -fn check_latest_events_exact(latest_events: &[&MetaEvent], event_file: &str) -> bool { - let json_events = parse_event_file(event_file); - assert!(latest_events.len() <= json_events.len()); - let json_events = &json_events[(json_events.len() - latest_events.len())..]; - - for (idx, e) in json_events.iter().enumerate() { - if !latest_events[idx].match_with_json_event(e) { - eprintln!( - "\n\n==== Start 'check_latest_events_exact' failed ==== \ - \n\nexpected_events={latest_events:?}\nactual_events={json_events:?} \ - \n\n==== End 'check_latest_events_exact' failed ====", - ); - - return false; - } - } - - true -} - -fn test_cpu_topology(threads_per_core: u8, cores_per_package: u8, packages: u8, use_fw: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let total_vcpus = threads_per_core * cores_per_package * packages; - let direct_kernel_boot_path = direct_kernel_boot_path(); - let mut kernel_path = direct_kernel_boot_path.to_str().unwrap(); - let fw_path = fw_path(FwType::RustHypervisorFirmware); - if use_fw { - kernel_path = fw_path.as_str(); - } - - let mut child = GuestCommand::new(&guest) - .args([ - "--cpus", - &format!( - "boot={total_vcpus},topology={threads_per_core}:{cores_per_package}:1:{packages}" - ), - ]) - .default_memory() - .args(["--kernel", kernel_path]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - assert_eq!( - guest.get_cpu_count().unwrap_or_default(), - u32::from(total_vcpus) - ); - assert_eq!( - guest - .ssh_command("lscpu | grep \"per core\" | cut -f 2 -d \":\" | sed \"s# *##\"") - .unwrap() - .trim() - .parse::() - .unwrap_or(0), - threads_per_core - ); - - assert_eq!( - guest - .ssh_command("lscpu | grep \"per socket\" | cut -f 2 -d \":\" | sed \"s# *##\"") - .unwrap() - .trim() - .parse::() - .unwrap_or(0), - cores_per_package - ); - - assert_eq!( - guest - .ssh_command("lscpu | grep \"Socket\" | cut -f 2 -d \":\" | sed \"s# *##\"") - .unwrap() - .trim() - .parse::() - .unwrap_or(0), - packages - ); - - #[cfg(target_arch = "x86_64")] - { - let mut cpu_id = 0; - for package_id in 0..packages { - for core_id in 0..cores_per_package { - for _ in 0..threads_per_core { - assert_eq!( - guest - .ssh_command(&format!("cat /sys/devices/system/cpu/cpu{cpu_id}/topology/physical_package_id")) - .unwrap() - .trim() - .parse::() - .unwrap_or(0), - package_id - ); - - assert_eq!( - guest - .ssh_command(&format!( - "cat /sys/devices/system/cpu/cpu{cpu_id}/topology/core_id" - )) - .unwrap() - .trim() - .parse::() - .unwrap_or(0), - core_id - ); - - cpu_id += 1; - } - } - } - } - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -#[allow(unused_variables)] -fn _test_guest_numa_nodes(acpi: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - #[cfg(target_arch = "x86_64")] - let kernel_path = direct_kernel_boot_path(); - #[cfg(target_arch = "aarch64")] - let kernel_path = if acpi { - edk2_path() - } else { - direct_kernel_boot_path() - }; - - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=6,max=12"]) - .args(["--memory", "size=0,hotplug_method=virtio-mem"]) - .args([ - "--memory-zone", - "id=mem0,size=1G,hotplug_size=3G", - "id=mem1,size=2G,hotplug_size=3G", - "id=mem2,size=3G,hotplug_size=3G", - ]) - .args([ - "--numa", - "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0", - "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1", - "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2", - ]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(["--api-socket", &api_socket]) - .capture_output() - .default_disks() - .default_net() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - guest.check_numa_common( - Some(&[960_000, 1_920_000, 2_880_000]), - Some(&[&[0, 1, 2], &[3, 4], &[5]]), - Some(&["10 15 20", "20 10 25", "25 30 10"]), - ); - - // AArch64 currently does not support hotplug, and therefore we only - // test hotplug-related function on x86_64 here. - #[cfg(target_arch = "x86_64")] - { - guest.enable_memory_hotplug(); - - // Resize every memory zone and check each associated NUMA node - // has been assigned the right amount of memory. - resize_zone_command(&api_socket, "mem0", "4G"); - resize_zone_command(&api_socket, "mem1", "4G"); - resize_zone_command(&api_socket, "mem2", "4G"); - // Resize to the maximum amount of CPUs and check each NUMA - // node has been assigned the right CPUs set. - resize_command(&api_socket, Some(12), None, None, None); - thread::sleep(std::time::Duration::new(5, 0)); - - guest.check_numa_common( - Some(&[3_840_000, 3_840_000, 3_840_000]), - Some(&[&[0, 1, 2, 9], &[3, 4, 6, 7, 8], &[5, 10, 11]]), - None, - ); - } - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -#[allow(unused_variables)] -fn _test_power_button(guest: &Guest) { - let mut cmd = GuestCommand::new(guest); - let api_socket = temp_api_path(&guest.tmp_dir); - - cmd.default_cpus() - .default_memory() - .default_kernel_cmdline() - .capture_output() - .default_disks() - .default_net() - .args(["--api-socket", &api_socket]); - - let child = cmd.spawn().unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - assert!(remote_command(&api_socket, "power-button", None)); - }); - - let output = child.wait_with_output().unwrap(); - assert!(output.status.success()); - handle_child_output(r, &output); -} - -fn get_msi_interrupt_pattern() -> String { - #[cfg(target_arch = "x86_64")] - { - "PCI-MSI".to_string() - } - #[cfg(target_arch = "aarch64")] - { - if cfg!(feature = "mshv") { - "GICv2m-PCI-MSIX".to_string() - } else { - "ITS-PCI-MSIX".to_string() - } - } -} - -type PrepareNetDaemon = dyn Fn( - &TempDir, - &str, - Option<&str>, - Option, - usize, - bool, -) -> (std::process::Command, String); - -fn test_vhost_user_net( - tap: Option<&str>, - num_queues: usize, - prepare_daemon: &PrepareNetDaemon, - generate_host_mac: bool, - client_mode_daemon: bool, -) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - - let kernel_path = direct_kernel_boot_path(); - - let host_mac = if generate_host_mac { - Some(MacAddr::local_random()) - } else { - None - }; - - let mtu = Some(3000); - - let (mut daemon_command, vunet_socket_path) = prepare_daemon( - &guest.tmp_dir, - &guest.network.host_ip0, - tap, - mtu, - num_queues, - client_mode_daemon, - ); - - let net_params = format!( - "vhost_user=true,mac={},socket={},num_queues={},queue_size=1024{},vhost_mode={},mtu=3000", - guest.network.guest_mac0, - vunet_socket_path, - num_queues, - if let Some(host_mac) = host_mac { - format!(",host_mac={host_mac}") - } else { - String::new() - }, - if client_mode_daemon { - "server" - } else { - "client" - }, - ); - - let mut ch_command = GuestCommand::new(&guest); - ch_command - .args(["--cpus", format!("boot={}", num_queues / 2).as_str()]) - .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", net_params.as_str()]) - .args(["--api-socket", &api_socket]) - .capture_output(); - - let mut daemon_child: std::process::Child; - let mut child: std::process::Child; - - if client_mode_daemon { - child = ch_command.spawn().unwrap(); - // Make sure the VMM is waiting for the backend to connect - thread::sleep(std::time::Duration::new(10, 0)); - daemon_child = daemon_command.spawn().unwrap(); - } else { - daemon_child = daemon_command.spawn().unwrap(); - // Make sure the backend is waiting for the VMM to connect - thread::sleep(std::time::Duration::new(10, 0)); - child = ch_command.spawn().unwrap(); - } - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - if let Some(tap_name) = tap { - let tap_count = exec_host_command_output(&format!("ip link | grep -c {tap_name}")); - assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1"); - } - - if let Some(host_mac) = tap { - let mac_count = exec_host_command_output(&format!("ip link | grep -c {host_mac}")); - assert_eq!(String::from_utf8_lossy(&mac_count.stdout).trim(), "1"); - } - - #[cfg(target_arch = "aarch64")] - let iface = "enp0s4"; - #[cfg(target_arch = "x86_64")] - let iface = "ens4"; - - assert_eq!( - guest - .ssh_command(format!("cat /sys/class/net/{iface}/mtu").as_str()) - .unwrap() - .trim(), - "3000" - ); - - // 1 network interface + default localhost ==> 2 interfaces - // It's important to note that this test is fully exercising the - // vhost-user-net implementation and the associated backend since - // it does not define any --net network interface. That means all - // the ssh communication in that test happens through the network - // interface backed by vhost-user-net. - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 2 - ); - - // The following pci devices will appear on guest with PCI-MSI - // interrupt vectors assigned. - // 1 virtio-console with 3 vectors: config, Rx, Tx - // 1 virtio-blk with 2 vectors: config, Request - // 1 virtio-blk with 2 vectors: config, Request - // 1 virtio-rng with 2 vectors: config, Request - // Since virtio-net has 2 queue pairs, its vectors is as follows: - // 1 virtio-net with 5 vectors: config, Rx (2), Tx (2) - // Based on the above, the total vectors should 14. - let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); - - assert_eq!( - guest - .ssh_command(&grep_cmd) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 10 + (num_queues as u32) - ); - - // ACPI feature is needed. - #[cfg(target_arch = "x86_64")] - { - guest.enable_memory_hotplug(); - - // Add RAM to the VM - let desired_ram = 1024 << 20; - resize_command(&api_socket, None, Some(desired_ram), None, None); - - thread::sleep(std::time::Duration::new(10, 0)); - - // Here by simply checking the size (through ssh), we validate - // the connection is still working, which means vhost-user-net - // keeps working after the resize. - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); - } - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - thread::sleep(std::time::Duration::new(5, 0)); - let _ = daemon_child.kill(); - let _ = daemon_child.wait(); - - handle_child_output(r, &output); -} - -type PrepareBlkDaemon = dyn Fn(&TempDir, &str, usize, bool, bool) -> (std::process::Child, String); - -fn test_vhost_user_blk( - num_queues: usize, - readonly: bool, - direct: bool, - prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>, -) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - - let kernel_path = direct_kernel_boot_path(); - - let (blk_params, daemon_child) = { - let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap(); - // Start the daemon - let (daemon_child, vubd_socket_path) = - prepare_daemon(&guest.tmp_dir, "blk.img", num_queues, readonly, direct); - - ( - format!( - "vhost_user=true,socket={vubd_socket_path},num_queues={num_queues},queue_size=128", - ), - Some(daemon_child), - ) - }; - - let mut child = GuestCommand::new(&guest) - .args(["--cpus", format!("boot={num_queues}").as_str()]) - .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args([ - "--disk", - format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - format!( - "path={}", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - blk_params.as_str(), - ]) - .default_net() - .args(["--api-socket", &api_socket]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Check both if /dev/vdc exists and if the block size is 16M. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 16M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Check if this block is RO or RW. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | awk '{print $5}'") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - readonly as u32 - ); - - // Check if the number of queues in /sys/block/vdc/mq matches the - // expected num_queues. - assert_eq!( - guest - .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - num_queues as u32 - ); - - // Mount the device - let mount_ro_rw_flag = if readonly { "ro,noload" } else { "rw" }; - guest.ssh_command("mkdir mount_image").unwrap(); - guest - .ssh_command( - format!("sudo mount -o {mount_ro_rw_flag} -t ext4 /dev/vdc mount_image/").as_str(), - ) - .unwrap(); - - // Check the content of the block device. The file "foo" should - // contain "bar". - assert_eq!( - guest.ssh_command("cat mount_image/foo").unwrap().trim(), - "bar" - ); - - // ACPI feature is needed. - #[cfg(target_arch = "x86_64")] - { - guest.enable_memory_hotplug(); - - // Add RAM to the VM - let desired_ram = 1024 << 20; - resize_command(&api_socket, None, Some(desired_ram), None, None); - - thread::sleep(std::time::Duration::new(10, 0)); - - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); - - // Check again the content of the block device after the resize - // has been performed. - assert_eq!( - guest.ssh_command("cat mount_image/foo").unwrap().trim(), - "bar" - ); - } - - // Unmount the device - guest.ssh_command("sudo umount /dev/vdc").unwrap(); - guest.ssh_command("rm -r mount_image").unwrap(); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - if let Some(mut daemon_child) = daemon_child { - thread::sleep(std::time::Duration::new(5, 0)); - let _ = daemon_child.kill(); - let _ = daemon_child.wait(); - } - - handle_child_output(r, &output); -} - -fn test_boot_from_vhost_user_blk( - num_queues: usize, - readonly: bool, - direct: bool, - prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>, -) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let kernel_path = direct_kernel_boot_path(); - - let disk_path = guest.disk_config.disk(DiskType::OperatingSystem).unwrap(); - - let (blk_boot_params, daemon_child) = { - let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap(); - // Start the daemon - let (daemon_child, vubd_socket_path) = prepare_daemon( - &guest.tmp_dir, - disk_path.as_str(), - num_queues, - readonly, - direct, - ); - - ( - format!( - "vhost_user=true,socket={vubd_socket_path},num_queues={num_queues},queue_size=128", - ), - Some(daemon_child), - ) - }; - - let mut child = GuestCommand::new(&guest) - .args(["--cpus", format!("boot={num_queues}").as_str()]) - .args(["--memory", "size=512M,shared=on"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args([ - "--disk", - blk_boot_params.as_str(), - format!( - "path={}", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - ]) - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Just check the VM booted correctly. - assert_eq!(guest.get_cpu_count().unwrap_or_default(), num_queues as u32); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); - }); - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - if let Some(mut daemon_child) = daemon_child { - thread::sleep(std::time::Duration::new(5, 0)); - let _ = daemon_child.kill(); - let _ = daemon_child.wait(); - } - - handle_child_output(r, &output); -} - -fn _test_virtio_fs( - prepare_daemon: &dyn Fn(&TempDir, &str) -> (std::process::Child, String), - hotplug: bool, - use_generic_vhost_user: bool, - pci_segment: Option, -) { - #[cfg(target_arch = "aarch64")] - let focal_image = if hotplug { - FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string() - } else { - FOCAL_IMAGE_NAME.to_string() - }; - #[cfg(target_arch = "x86_64")] - let focal_image = FOCAL_IMAGE_NAME.to_string(); - let disk_config = UbuntuDiskConfig::new(focal_image); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - - let mut shared_dir = workload_path; - shared_dir.push("shared_dir"); - - #[cfg(target_arch = "x86_64")] - let kernel_path = direct_kernel_boot_path(); - #[cfg(target_arch = "aarch64")] - let kernel_path = if hotplug { - edk2_path() - } else { - direct_kernel_boot_path() - }; - - let (mut daemon_child, virtiofsd_socket_path) = - prepare_daemon(&guest.tmp_dir, shared_dir.to_str().unwrap()); - - let mut guest_command = GuestCommand::new(&guest); - guest_command - .default_cpus() - .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .args(["--api-socket", &api_socket]); - if pci_segment.is_some() { - guest_command.args([ - "--platform", - &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS}"), - ]); - } - - let fs_params = format!( - "socket={},id=myfs0,{}{}", - virtiofsd_socket_path, - if use_generic_vhost_user { - "queue_sizes=[1024,1024],virtio_id=26" - } else { - "tag=myfs,num_queues=1,queue_size=1024" - }, - if let Some(pci_segment) = pci_segment { - format!(",pci_segment={pci_segment}") - } else { - String::new() - } - ); - - if !hotplug { - guest_command.args([ - if use_generic_vhost_user { - "--generic-vhost-user" - } else { - "--fs" - }, - fs_params.as_str(), - ]); - } - - let mut child = guest_command.capture_output().spawn().unwrap(); - let add_arg = if use_generic_vhost_user { - "add-generic-vhost-user" - } else { - "add-fs" - }; - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - if hotplug { - // Add fs to the VM - let (cmd_success, cmd_output) = - remote_command_w_output(&api_socket, add_arg, Some(&fs_params)); - assert!(cmd_success); - - if let Some(pci_segment) = pci_segment { - assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( - "{{\"id\":\"myfs0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" - ))); - } else { - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}") - ); - } - - thread::sleep(std::time::Duration::new(10, 0)); - } - - // Mount shared directory through virtio_fs filesystem - guest - .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") - .unwrap(); - - // Check file1 exists and its content is "foo" - assert_eq!( - guest.ssh_command("cat mount_dir/file1").unwrap().trim(), - "foo" - ); - // Check file2 does not exist - guest - .ssh_command("[ ! -f 'mount_dir/file2' ] || true") - .unwrap(); - - // Check file3 exists and its content is "bar" - assert_eq!( - guest.ssh_command("cat mount_dir/file3").unwrap().trim(), - "bar" - ); - - // ACPI feature is needed. - #[cfg(target_arch = "x86_64")] - { - guest.enable_memory_hotplug(); - - // Add RAM to the VM - let desired_ram = 1024 << 20; - resize_command(&api_socket, None, Some(desired_ram), None, None); - - thread::sleep(std::time::Duration::new(30, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); - - // After the resize, check again that file1 exists and its - // content is "foo". - assert_eq!( - guest.ssh_command("cat mount_dir/file1").unwrap().trim(), - "foo" - ); - } - - if hotplug { - // Remove from VM - guest.ssh_command("sudo umount mount_dir").unwrap(); - assert!(remote_command(&api_socket, "remove-device", Some("myfs0"))); - } - }); - - let (r, hotplug_daemon_child) = if r.is_ok() && hotplug { - thread::sleep(std::time::Duration::new(10, 0)); - let (daemon_child, virtiofsd_socket_path) = - prepare_daemon(&guest.tmp_dir, shared_dir.to_str().unwrap()); - - let r = std::panic::catch_unwind(|| { - thread::sleep(std::time::Duration::new(10, 0)); - let fs_params = format!( - "id=myfs0,socket={},{}{}", - virtiofsd_socket_path, - if use_generic_vhost_user { - "queue_sizes=[1024,1024],virtio_id=26" - } else { - "tag=myfs,num_queues=1,queue_size=1024" - }, - if let Some(pci_segment) = pci_segment { - format!(",pci_segment={pci_segment}") - } else { - String::new() - } - ); - - // Add back and check it works - let (cmd_success, cmd_output) = - remote_command_w_output(&api_socket, add_arg, Some(&fs_params)); - assert!(cmd_success); - if let Some(pci_segment) = pci_segment { - assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( - "{{\"id\":\"myfs0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" - ))); - } else { - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}") - ); - } - - thread::sleep(std::time::Duration::new(10, 0)); - // Mount shared directory through virtio_fs filesystem - guest - .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") - .unwrap(); - - // Check file1 exists and its content is "foo" - assert_eq!( - guest.ssh_command("cat mount_dir/file1").unwrap().trim(), - "foo" - ); - }); - - (r, Some(daemon_child)) - } else { - (r, None) - }; - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - let _ = daemon_child.kill(); - let _ = daemon_child.wait(); - - if let Some(mut daemon_child) = hotplug_daemon_child { - let _ = daemon_child.kill(); - let _ = daemon_child.wait(); - } - - handle_child_output(r, &output); -} - -fn test_virtio_pmem(discard_writes: bool, specify_size: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let kernel_path = direct_kernel_boot_path(); - - let pmem_temp_file = TempFile::new().unwrap(); - pmem_temp_file.as_file().set_len(128 << 20).unwrap(); - - std::process::Command::new("mkfs.ext4") - .arg(pmem_temp_file.as_path()) - .output() - .expect("Expect creating disk image to succeed"); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .args([ - "--pmem", - format!( - "file={}{}{}", - pmem_temp_file.as_path().to_str().unwrap(), - if specify_size { ",size=128M" } else { "" }, - if discard_writes { - ",discard_writes=on" - } else { - "" - } - ) - .as_str(), - ]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Check for the presence of /dev/pmem0 - assert_eq!( - guest.ssh_command("ls /dev/pmem0").unwrap().trim(), - "/dev/pmem0" - ); - - // Check changes persist after reboot - assert_eq!(guest.ssh_command("sudo mount /dev/pmem0 /mnt").unwrap(), ""); - assert_eq!(guest.ssh_command("ls /mnt").unwrap(), "lost+found\n"); - guest - .ssh_command("echo test123 | sudo tee /mnt/test") - .unwrap(); - assert_eq!(guest.ssh_command("sudo umount /mnt").unwrap(), ""); - assert_eq!(guest.ssh_command("ls /mnt").unwrap(), ""); - - guest.reboot_linux(0); - assert_eq!(guest.ssh_command("sudo mount /dev/pmem0 /mnt").unwrap(), ""); - assert_eq!( - guest - .ssh_command("sudo cat /mnt/test || true") - .unwrap() - .trim(), - if discard_writes { "" } else { "test123" } - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn get_fd_count(pid: u32) -> usize { - fs::read_dir(format!("/proc/{pid}/fd")).unwrap().count() -} - -fn _test_virtio_vsock(guest: &Guest, hotplug: bool) { - let socket = temp_vsock_path(&guest.tmp_dir); - let api_socket = temp_api_path(&guest.tmp_dir); - - let mut cmd = GuestCommand::new(guest); - cmd.args(["--api-socket", &api_socket]); - cmd.default_cpus(); - cmd.default_memory(); - cmd.default_kernel_cmdline(); - cmd.default_disks(); - cmd.default_net(); - - if !hotplug { - cmd.args(["--vsock", format!("cid=3,socket={socket}").as_str()]); - } - - let mut child = cmd.capture_output().spawn().unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - if hotplug { - let (cmd_success, cmd_output) = remote_command_w_output( - &api_socket, - "add-vsock", - Some(format!("cid=3,socket={socket},id=test0").as_str()), - ); - assert!(cmd_success); - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") - ); - thread::sleep(std::time::Duration::new(10, 0)); - // Check adding a second one fails - assert!(!remote_command( - &api_socket, - "add-vsock", - Some("cid=1234,socket=/tmp/fail") - )); - } - - // Validate vsock works as expected. - guest.check_vsock(socket.as_str()); - guest.reboot_linux(0); - // Validate vsock still works after a reboot. - guest.check_vsock(socket.as_str()); - - if hotplug { - assert!(remote_command(&api_socket, "remove-device", Some("test0"))); - } - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn get_ksm_pages_shared() -> u32 { - fs::read_to_string("/sys/kernel/mm/ksm/pages_shared") - .unwrap() - .trim() - .parse::() - .unwrap() -} - -fn test_memory_mergeable(mergeable: bool) { - let memory_param = if mergeable { - "mergeable=on" - } else { - "mergeable=off" - }; - - // We assume the number of shared pages in the rest of the system to be constant - let ksm_ps_init = get_ksm_pages_shared(); - - let disk_config1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest1 = Guest::new(Box::new(disk_config1)); - let mut child1 = GuestCommand::new(&guest1) - .default_cpus() - .args(["--memory", format!("size=512M,{memory_param}").as_str()]) - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", guest1.default_net_string().as_str()]) - .args(["--serial", "tty", "--console", "off"]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest1.wait_vm_boot().unwrap(); - }); - if r.is_err() { - kill_child(&mut child1); - let output = child1.wait_with_output().unwrap(); - handle_child_output(r, &output); - panic!("Test should already be failed/panicked"); // To explicitly mark this block never return - } - - let ksm_ps_guest1 = get_ksm_pages_shared(); - - let disk_config2 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest2 = Guest::new(Box::new(disk_config2)); - let mut child2 = GuestCommand::new(&guest2) - .default_cpus() - .args(["--memory", format!("size=512M,{memory_param}").as_str()]) - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", guest2.default_net_string().as_str()]) - .args(["--serial", "tty", "--console", "off"]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest2.wait_vm_boot().unwrap(); - let ksm_ps_guest2 = get_ksm_pages_shared(); - - if mergeable { - println!( - "ksm pages_shared after vm1 booted '{ksm_ps_guest1}', ksm pages_shared after vm2 booted '{ksm_ps_guest2}'" - ); - // We are expecting the number of shared pages to increase as the number of VM increases - assert!(ksm_ps_guest1 < ksm_ps_guest2); - } else { - assert!(ksm_ps_guest1 == ksm_ps_init); - assert!(ksm_ps_guest2 == ksm_ps_init); - } - }); - - kill_child(&mut child1); - kill_child(&mut child2); - - let output = child1.wait_with_output().unwrap(); - child2.wait().unwrap(); - - handle_child_output(r, &output); -} - -fn _get_vmm_overhead(pid: u32, guest_memory_size: u32) -> HashMap { - let smaps = fs::File::open(format!("/proc/{pid}/smaps")).unwrap(); - let reader = io::BufReader::new(smaps); - - let mut skip_map: bool = false; - let mut region_name: String = String::new(); - let mut region_maps = HashMap::new(); - for line in reader.lines() { - let l = line.unwrap(); - - if l.contains('-') { - let values: Vec<&str> = l.split_whitespace().collect(); - region_name = values.last().unwrap().trim().to_string(); - if region_name == "0" { - region_name = "anonymous".to_string(); - } - } - - // Each section begins with something that looks like: - // Size: 2184 kB - if l.starts_with("Size:") { - let values: Vec<&str> = l.split_whitespace().collect(); - let map_size = values[1].parse::().unwrap(); - // We skip the assigned guest RAM map, its RSS is only - // dependent on the guest actual memory usage. - // Everything else can be added to the VMM overhead. - skip_map = map_size >= guest_memory_size; - continue; - } - - // If this is a map we're taking into account, then we only - // count the RSS. The sum of all counted RSS is the VMM overhead. - if !skip_map && l.starts_with("Rss:") { - let values: Vec<&str> = l.split_whitespace().collect(); - let value = values[1].trim().parse::().unwrap(); - *region_maps.entry(region_name.clone()).or_insert(0) += value; - } - } - - region_maps -} - -fn get_vmm_overhead(pid: u32, guest_memory_size: u32) -> u32 { - let mut total = 0; - - for (region_name, value) in &_get_vmm_overhead(pid, guest_memory_size) { - eprintln!("{region_name}: {value}"); - total += value; - } - - total -} - -fn process_rss_kib(pid: u32) -> usize { - let command = format!("ps -q {pid} -o rss="); - let rss = exec_host_command_output(&command); - String::from_utf8_lossy(&rss.stdout).trim().parse().unwrap() -} - -// 10MB is our maximum accepted overhead. -const MAXIMUM_VMM_OVERHEAD_KB: u32 = 10 * 1024; - -#[derive(PartialEq, Eq, PartialOrd)] -struct Counters { - rx_bytes: u64, - rx_frames: u64, - tx_bytes: u64, - tx_frames: u64, - read_bytes: u64, - write_bytes: u64, - read_ops: u64, - write_ops: u64, -} - -fn get_counters(api_socket: &str) -> Counters { - // Get counters - let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "counters", None); - assert!(cmd_success); - - let counters: HashMap<&str, HashMap<&str, u64>> = - serde_json::from_slice(&cmd_output).unwrap_or_default(); - - let rx_bytes = *counters.get("_net2").unwrap().get("rx_bytes").unwrap(); - let rx_frames = *counters.get("_net2").unwrap().get("rx_frames").unwrap(); - let tx_bytes = *counters.get("_net2").unwrap().get("tx_bytes").unwrap(); - let tx_frames = *counters.get("_net2").unwrap().get("tx_frames").unwrap(); - - let read_bytes = *counters.get("_disk0").unwrap().get("read_bytes").unwrap(); - let write_bytes = *counters.get("_disk0").unwrap().get("write_bytes").unwrap(); - let read_ops = *counters.get("_disk0").unwrap().get("read_ops").unwrap(); - let write_ops = *counters.get("_disk0").unwrap().get("write_ops").unwrap(); - - Counters { - rx_bytes, - rx_frames, - tx_bytes, - tx_frames, - read_bytes, - write_bytes, - read_ops, - write_ops, - } -} - -fn pty_read(mut pty: std::fs::File) -> Receiver { - let (tx, rx) = mpsc::channel::(); - thread::spawn(move || { - loop { - thread::sleep(std::time::Duration::new(1, 0)); - let mut buf = [0; 512]; - match pty.read(&mut buf) { - Ok(_bytes) => { - let output = std::str::from_utf8(&buf).unwrap().to_string(); - match tx.send(output) { - Ok(_) => (), - Err(_) => break, - } - } - Err(_) => break, - } - } - }); - rx -} - -fn get_pty_path(api_socket: &str, pty_type: &str) -> PathBuf { - let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None); - assert!(cmd_success); - let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default(); - assert_eq!("Pty", info["config"][pty_type]["mode"]); - PathBuf::from( - info["config"][pty_type]["file"] - .as_str() - .expect("Missing pty path"), - ) -} - -// VFIO test network setup. -// We reserve a different IP class for it: 172.18.0.0/24. -#[cfg(target_arch = "x86_64")] -fn setup_vfio_network_interfaces() { - // 'vfio-br0' - assert!(exec_host_command_status("sudo ip link add name vfio-br0 type bridge").success()); - assert!(exec_host_command_status("sudo ip link set vfio-br0 up").success()); - assert!(exec_host_command_status("sudo ip addr add 172.18.0.1/24 dev vfio-br0").success()); - // 'vfio-tap0' - assert!(exec_host_command_status("sudo ip tuntap add vfio-tap0 mode tap").success()); - assert!(exec_host_command_status("sudo ip link set vfio-tap0 master vfio-br0").success()); - assert!(exec_host_command_status("sudo ip link set vfio-tap0 up").success()); - // 'vfio-tap1' - assert!(exec_host_command_status("sudo ip tuntap add vfio-tap1 mode tap").success()); - assert!(exec_host_command_status("sudo ip link set vfio-tap1 master vfio-br0").success()); - assert!(exec_host_command_status("sudo ip link set vfio-tap1 up").success()); - // 'vfio-tap2' - assert!(exec_host_command_status("sudo ip tuntap add vfio-tap2 mode tap").success()); - assert!(exec_host_command_status("sudo ip link set vfio-tap2 master vfio-br0").success()); - assert!(exec_host_command_status("sudo ip link set vfio-tap2 up").success()); - // 'vfio-tap3' - assert!(exec_host_command_status("sudo ip tuntap add vfio-tap3 mode tap").success()); - assert!(exec_host_command_status("sudo ip link set vfio-tap3 master vfio-br0").success()); - assert!(exec_host_command_status("sudo ip link set vfio-tap3 up").success()); -} - -// Tear VFIO test network down -#[cfg(target_arch = "x86_64")] -fn cleanup_vfio_network_interfaces() { - assert!(exec_host_command_status("sudo ip link del vfio-br0").success()); - assert!(exec_host_command_status("sudo ip link del vfio-tap0").success()); - assert!(exec_host_command_status("sudo ip link del vfio-tap1").success()); - assert!(exec_host_command_status("sudo ip link del vfio-tap2").success()); - assert!(exec_host_command_status("sudo ip link del vfio-tap3").success()); -} - -fn balloon_size(api_socket: &str) -> u64 { - let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None); - assert!(cmd_success); - - let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default(); - let total_mem = &info["config"]["memory"]["size"] - .to_string() - .parse::() - .unwrap(); - let actual_mem = &info["memory_actual_size"] - .to_string() - .parse::() - .unwrap(); - total_mem - actual_mem -} - -fn vm_state(api_socket: &str) -> String { - let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None); - assert!(cmd_success); - - let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default(); - let state = &info["state"].as_str().unwrap(); - - state.to_string() -} - -// This test validates that it can find the virtio-iommu device at first. -// It also verifies that both disks and the network card are attached to -// the virtual IOMMU by looking at /sys/kernel/iommu_groups directory. -// The last interesting part of this test is that it exercises the network -// interface attached to the virtual IOMMU since this is the one used to -// send all commands through SSH. -fn _test_virtio_iommu(_acpi: bool /* not needed on x86_64 */) { - // Virtio-iommu support is ready in recent kernel (v5.14). But the kernel in - // Focal image is still old. - // So if ACPI is enabled on AArch64, we use a modified Focal image in which - // the kernel binary has been updated. - #[cfg(target_arch = "aarch64")] - let focal_image = FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string(); - #[cfg(target_arch = "x86_64")] - let focal_image = FOCAL_IMAGE_NAME.to_string(); - let disk_config = UbuntuDiskConfig::new(focal_image); - let guest = Guest::new(Box::new(disk_config)); - - #[cfg(target_arch = "x86_64")] - let kernel_path = direct_kernel_boot_path(); - #[cfg(target_arch = "aarch64")] - let kernel_path = if _acpi { - edk2_path() - } else { - direct_kernel_boot_path() - }; - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args([ - "--disk", - format!( - "path={},iommu=on", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - format!( - "path={},iommu=on", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - ]) - .args(["--net", guest.default_net_string_w_iommu().as_str()]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Verify the virtio-iommu device is present. - assert!( - guest - .does_device_vendor_pair_match("0x1057", "0x1af4") - .unwrap_or_default() - ); - - // On AArch64, if the guest system boots from FDT, the behavior of IOMMU is a bit - // different with ACPI. - // All devices on the PCI bus will be attached to the virtual IOMMU, except the - // virtio-iommu device itself. So these devices will all be added to IOMMU groups, - // and appear under folder '/sys/kernel/iommu_groups/'. - // - // Verify the first disk is in an iommu group. - assert!( - guest - .ssh_command("ls /sys/kernel/iommu_groups/*/devices") - .unwrap() - .contains("0000:00:02.0") - ); - - // Verify the second disk is in an iommu group. - assert!( - guest - .ssh_command("ls /sys/kernel/iommu_groups/*/devices") - .unwrap() - .contains("0000:00:03.0") - ); - - // Verify the network card is in an iommu group. - assert!( - guest - .ssh_command("ls /sys/kernel/iommu_groups/*/devices") - .unwrap() - .contains("0000:00:04.0") - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn get_reboot_count(guest: &Guest) -> u32 { - guest - .ssh_command("sudo last | grep -c reboot") - .unwrap() - .trim() - .parse::() - .unwrap_or_default() -} - -fn enable_guest_watchdog(guest: &Guest, watchdog_sec: u32) { - // Check for PCI device - assert!( - guest - .does_device_vendor_pair_match("0x1063", "0x1af4") - .unwrap_or_default() - ); - - // Enable systemd watchdog - guest - .ssh_command(&format!( - "echo RuntimeWatchdogSec={watchdog_sec}s | sudo tee -a /etc/systemd/system.conf" - )) - .unwrap(); - - guest.ssh_command("sudo systemctl daemon-reexec").unwrap(); -} - -fn make_guest_panic(guest: &Guest) { - // Check for pvpanic device - assert!( - guest - .does_device_vendor_pair_match("0x0011", "0x1b36") - .unwrap_or_default() - ); - - // Trigger guest a panic - guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); -} - -// ivshmem test -// This case validates that read data from host(host write data to ivshmem backend file, -// guest read data from ivshmem pci bar2 memory) -// and write data to host(guest write data to ivshmem pci bar2 memory, host read it from -// ivshmem backend file). -// It also checks the size of the shared memory region. -fn _test_ivshmem(guest: &Guest, ivshmem_file_path: impl AsRef, file_size: &str) { - let ivshmem_file_path = ivshmem_file_path.as_ref(); - let test_message_read = String::from("ivshmem device test data read"); - // Modify backend file data before function test - let mut file = OpenOptions::new() - .read(true) - .write(true) - .open(ivshmem_file_path) - .unwrap(); - file.seek(SeekFrom::Start(0)).unwrap(); - file.write_all(test_message_read.as_bytes()).unwrap(); - file.write_all(b"\0").unwrap(); - file.flush().unwrap(); - - let output = fs::read_to_string(ivshmem_file_path).unwrap(); - let nul_pos = output.as_bytes().iter().position(|&b| b == 0).unwrap(); - let c_str = CStr::from_bytes_until_nul(&output.as_bytes()[..=nul_pos]).unwrap(); - let file_message = c_str.to_string_lossy().to_string(); - // Check if the backend file data is correct - assert_eq!(test_message_read, file_message); - - let device_id_line = String::from( - guest - .ssh_command("lspci -D | grep \"Inter-VM shared memory\"") - .unwrap() - .trim(), - ); - // Check if ivshmem exists - assert!(!device_id_line.is_empty()); - let device_id = device_id_line.split(" ").next().unwrap(); - // Check shard memory size - assert_eq!( - guest - .ssh_command( - format!("lspci -vv -s {device_id} | grep -c \"Region 2.*size={file_size}\"") - .as_str(), - ) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // guest don't have gcc or g++, try to use python to test :( - // This python program try to mmap the ivshmem pci bar2 memory and read the data from it. - let ivshmem_test_read = format!( - r#" -import os -import mmap -from ctypes import create_string_buffer, c_char, memmove - -if __name__ == "__main__": - device_path = f"/sys/bus/pci/devices/{device_id}/resource2" - fd = os.open(device_path, os.O_RDWR | os.O_SYNC) - - PAGE_SIZE = os.sysconf('SC_PAGESIZE') - - with mmap.mmap(fd, PAGE_SIZE, flags=mmap.MAP_SHARED, - prot=mmap.PROT_READ | mmap.PROT_WRITE, offset=0) as shmem: - c_buf = (c_char * PAGE_SIZE).from_buffer(shmem) - null_pos = c_buf.raw.find(b'\x00') - valid_data = c_buf.raw[:null_pos] if null_pos != -1 else c_buf.raw - print(valid_data.decode('utf-8', errors='replace'), end="") - shmem.flush() - del c_buf - - os.close(fd) - "# - ); - guest - .ssh_command( - format!( - r#"cat << EOF > test_read.py -{ivshmem_test_read} -EOF -"# - ) - .as_str(), - ) - .unwrap(); - let guest_message = guest.ssh_command("sudo python3 test_read.py").unwrap(); - - // Check the probe message in host and guest - assert_eq!(test_message_read, guest_message); - - let test_message_write = "ivshmem device test data write"; - // Then the program writes a test message to the memory and flush it. - let ivshmem_test_write = format!( - r#" -import os -import mmap -from ctypes import create_string_buffer, c_char, memmove - -if __name__ == "__main__": - device_path = f"/sys/bus/pci/devices/{device_id}/resource2" - test_message = "{test_message_write}" - fd = os.open(device_path, os.O_RDWR | os.O_SYNC) - - PAGE_SIZE = os.sysconf('SC_PAGESIZE') - - with mmap.mmap(fd, PAGE_SIZE, flags=mmap.MAP_SHARED, - prot=mmap.PROT_READ | mmap.PROT_WRITE, offset=0) as shmem: - shmem.flush() - c_buf = (c_char * PAGE_SIZE).from_buffer(shmem) - encoded_msg = test_message.encode('utf-8').ljust(1000, b'\x00') - memmove(c_buf, encoded_msg, len(encoded_msg)) - shmem.flush() - del c_buf - - os.close(fd) - "# - ); - - guest - .ssh_command( - format!( - r#"cat << EOF > test_write.py -{ivshmem_test_write} -EOF -"# - ) - .as_str(), - ) - .unwrap(); - - let _ = guest.ssh_command("sudo python3 test_write.py").unwrap(); - - let output = fs::read_to_string(ivshmem_file_path).unwrap(); - let nul_pos = output.as_bytes().iter().position(|&b| b == 0).unwrap(); - let c_str = CStr::from_bytes_until_nul(&output.as_bytes()[..=nul_pos]).unwrap(); - let file_message = c_str.to_string_lossy().to_string(); - // Check to send data from guest to host - assert_eq!(test_message_write, file_message); -} - -fn _test_simple_launch(guest: &Guest) { - let event_path = temp_event_monitor_path(&guest.tmp_dir); - - let mut child = GuestCommand::new(guest) - .default_cpus() - .default_memory() - .default_kernel_cmdline() - .default_disks() - .default_net() - .args(["--serial", "tty", "--console", "off"]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - guest.validate_cpu_count(None); - guest.validate_memory(None); - assert_eq!(guest.get_pci_bridge_class().unwrap_or_default(), "0x060000"); - assert!(check_sequential_events( - &guest - .get_expected_seq_events_for_simple_launch() - .iter() - .collect::>(), - &event_path - )); - - // It's been observed on the Bionic image that udev and snapd - // services can cause some delay in the VM's shutdown. Disabling - // them improves the reliability of this test. - let _ = guest.ssh_command("sudo systemctl disable udev"); - let _ = guest.ssh_command("sudo systemctl stop udev"); - let _ = guest.ssh_command("sudo systemctl disable snapd"); - let _ = guest.ssh_command("sudo systemctl stop snapd"); - - guest.ssh_command("sudo poweroff").unwrap(); - thread::sleep(std::time::Duration::new(20, 0)); - let latest_events = [ - &MetaEvent { - event: "shutdown".to_string(), - device_id: None, - }, - &MetaEvent { - event: "deleted".to_string(), - device_id: None, - }, - &MetaEvent { - event: "shutdown".to_string(), - device_id: None, - }, - ]; - assert!(check_latest_events_exact(&latest_events, &event_path)); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn _test_multi_cpu(guest: &Guest) { - let mut cmd = GuestCommand::new(guest); - cmd.args(["--cpus", "boot=2,max=4"]) - .default_memory() - .default_kernel_cmdline() - .capture_output() - .default_disks() - .default_net(); - - let mut child = cmd.spawn().unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); - - assert_eq!( - guest - .ssh_command(r#"sudo dmesg | grep "smp: Brought up" | sed "s/\[\ *[0-9.]*\] //""#) - .unwrap() - .trim(), - "smp: Brought up 1 node, 2 CPUs" - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn _test_cpu_affinity(guest: &Guest) { - // We need the host to have at least 4 CPUs if we want to be able - // to run this test. - let host_cpus_count = exec_host_command_output("nproc"); - assert!( - String::from_utf8_lossy(&host_cpus_count.stdout) - .trim() - .parse::() - .unwrap_or(0) - >= 4 - ); - - let mut child = GuestCommand::new(guest) - .default_cpus_with_affinity() - .default_memory() - .default_kernel_cmdline() - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - let pid = child.id(); - let taskset_vcpu0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_vcpu0.stdout).trim(), "0,2"); - let taskset_vcpu1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_vcpu1.stdout).trim(), "1,3"); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); -} - -fn _test_virtio_queue_affinity(guest: &Guest) { - // We need the host to have at least 4 CPUs if we want to be able - // to run this test. - let host_cpus_count = exec_host_command_output("nproc"); - assert!( - String::from_utf8_lossy(&host_cpus_count.stdout) - .trim() - .parse::() - .unwrap_or(0) - >= 4 - ); - - let mut child = GuestCommand::new(guest) - .default_cpus() - .default_memory() - .default_kernel_cmdline() - .args([ - "--disk", - format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - format!( - "path={},num_queues=4,queue_affinity=[0@[0,2],1@[1,3],2@[1],3@[3]]", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - ]) - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - let pid = child.id(); - let taskset_q0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_q0.stdout).trim(), "0,2"); - let taskset_q1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_q1.stdout).trim(), "1,3"); - let taskset_q2 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q2 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_q2.stdout).trim(), "1"); - let taskset_q3 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q3 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str()); - assert_eq!(String::from_utf8_lossy(&taskset_q3.stdout).trim(), "3"); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); -} - -fn _test_pci_msi(guest: &Guest) { - let mut cmd = GuestCommand::new(guest); - cmd.default_cpus() - .default_memory() - .default_kernel_cmdline() - .capture_output() - .default_disks() - .default_net(); - - let mut child = cmd.spawn().unwrap(); - - guest.wait_vm_boot().unwrap(); - - let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); - - let r = std::panic::catch_unwind(|| { - assert_eq!( - guest - .ssh_command(&grep_cmd) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 12 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn _test_virtio_net_ctrl_queue(guest: &Guest) { - let mut cmd = GuestCommand::new(guest); - cmd.default_cpus() - .default_memory() - .default_kernel_cmdline() - .args(["--net", guest.default_net_string_w_mtu(3000).as_str()]) - .capture_output() - .default_disks(); - - let mut child = cmd.spawn().unwrap(); - - guest.wait_vm_boot().unwrap(); - - #[cfg(target_arch = "aarch64")] - let iface = "enp0s4"; - #[cfg(target_arch = "x86_64")] - let iface = "ens4"; - - let r = std::panic::catch_unwind(|| { - assert_eq!( - guest - .ssh_command( - format!("sudo ethtool -K {iface} rx-gro-hw off && echo success").as_str() - ) - .unwrap() - .trim(), - "success" - ); - assert_eq!( - guest - .ssh_command(format!("cat /sys/class/net/{iface}/mtu").as_str()) - .unwrap() - .trim(), - "3000" - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn _test_pci_multiple_segments( - guest: &Guest, - max_num_pci_segments: u16, - pci_segments_for_disk: u16, -) { - // Prepare another disk file for the virtio-disk device - let test_disk_path = String::from( - guest - .tmp_dir - .as_path() - .join("test-disk.raw") - .to_str() - .unwrap(), - ); - assert!( - exec_host_command_status(format!("truncate {test_disk_path} -s 4M").as_str()).success() - ); - assert!(exec_host_command_status(format!("mkfs.ext4 {test_disk_path}").as_str()).success()); - - let mut cmd = GuestCommand::new(guest); - cmd.default_cpus() - .default_memory() - .default_kernel_cmdline_with_platform(Some(&format!( - "num_pci_segments={max_num_pci_segments}" - ))) - .args([ - "--disk", - format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - format!( - "path={}", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - format!("path={test_disk_path},pci_segment={pci_segments_for_disk},image_type=raw") - .as_str(), - ]) - .capture_output() - .default_net(); - - let mut child = cmd.spawn().unwrap(); - - guest.wait_vm_boot().unwrap(); - - let grep_cmd = "lspci | grep \"Host bridge\" | wc -l"; - - let r = std::panic::catch_unwind(|| { - // There should be MAX_NUM_PCI_SEGMENTS PCI host bridges in the guest. - assert_eq!( - guest - .ssh_command(grep_cmd) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - max_num_pci_segments - ); - - // Check both if /dev/vdc exists and if the block size is 4M. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 4M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Mount the device. - guest.ssh_command("mkdir mount_image").unwrap(); - guest - .ssh_command("sudo mount -o rw -t ext4 /dev/vdc mount_image/") - .unwrap(); - // Grant all users with write permission. - guest.ssh_command("sudo chmod a+w mount_image/").unwrap(); - - // Write something to the device. - guest - .ssh_command("sudo echo \"bar\" >> mount_image/foo") - .unwrap(); - - // Check the content of the block device. The file "foo" should - // contain "bar". - assert_eq!( - guest - .ssh_command("sudo cat mount_image/foo") - .unwrap() - .trim(), - "bar" - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn _test_direct_kernel_boot(guest: &Guest) { - let mut child = GuestCommand::new(guest) - .default_cpus() - .default_memory() - .default_kernel_cmdline() - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - guest.validate_cpu_count(None); - guest.validate_memory(None); - - let grep_cmd = format!("grep -c {} /proc/interrupts", get_msi_interrupt_pattern()); - assert_eq!( - guest - .ssh_command(&grep_cmd) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 12 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); -} - -fn _test_virtio_block( - guest: &Guest, - disable_io_uring: bool, - disable_aio: bool, - verify_os_disk: bool, - backing_files: bool, - image_type: ImageType, -) { - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - - let mut blk_file_path = workload_path; - blk_file_path.push("blk.img"); - - let initial_backing_checksum = if verify_os_disk { - compute_backing_checksum(guest.disk_config.disk(DiskType::OperatingSystem).unwrap()) - } else { - None - }; - assert!( - guest.num_cpu >= 4, - "_test_virtio_block requires at least 4 CPUs to match num_queues=4" - ); - let mut cloud_child = GuestCommand::new(guest) - .default_cpus() - .args(["--memory", "size=512M,shared=on"]) - .default_kernel_cmdline() - .args([ - "--disk", - format!( - "path={},backing_files={},image_type={image_type}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), - if backing_files { "on" } else { "off" }, - ) - .as_str(), - format!( - "path={}", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - format!( - "path={},readonly=on,direct=on,num_queues=4,_disable_io_uring={},_disable_aio={}", - blk_file_path.to_str().unwrap(), - disable_io_uring, - disable_aio, - ) - .as_str(), - ]) - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Check both if /dev/vdc exists and if the block size is 16M. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 16M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Check both if /dev/vdc exists and if this block is RO. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | awk '{print $5}'") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Check if the number of queues is 4. - assert_eq!( - guest - .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 4 - ); - }); - - if verify_os_disk { - // Use clean shutdown to allow cloud-hypervisor to clear - // the dirty bit in the QCOW2 v3 image. - kill_child(&mut cloud_child); - } else { - let _ = cloud_child.kill(); - } - let output = cloud_child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - - if verify_os_disk { - disk_check_consistency( - guest.disk_config.disk(DiskType::OperatingSystem).unwrap(), - initial_backing_checksum, - ); - } -} - -fn compute_backing_checksum( - path_or_image_name: impl AsRef, -) -> Option<(std::path::PathBuf, String, u32)> { - let path = resolve_disk_path(path_or_image_name); - - let mut file = File::open(&path).ok()?; - if !matches!( - block::detect_image_type(&mut file).ok()?, - block::ImageType::Qcow2 - ) { - return None; - } - - let info = get_image_info(&path)?; - - let backing_file = info["backing-filename"].as_str()?; - let backing_path = if std::path::Path::new(backing_file).is_absolute() { - std::path::PathBuf::from(backing_file) - } else { - path.parent() - .unwrap_or_else(|| std::path::Path::new(".")) - .join(backing_file) - }; - - let backing_info = get_image_info(&backing_path)?; - let backing_format = backing_info["format"].as_str()?.to_string(); - let mut file = File::open(&backing_path).ok()?; - let file_size = file.metadata().ok()?.len(); - let checksum = compute_file_checksum(&mut file, file_size); - - Some((backing_path, backing_format, checksum)) -} - -/// Uses `qemu-img check` to verify disk image consistency. -/// -/// Supported formats are `qcow2` (compressed and uncompressed), -/// `vhdx`, `qed`, `parallels`, `vmdk`, and `vdi`. See man page -/// for more details. -/// -/// It takes either a full path to the image or just the name of -/// the image located in the `workloads` directory. -/// -/// For QCOW2 images with backing files, also verifies the backing file -/// integrity and checks that the backing file hasn't been modified -/// during the test. -/// -/// For QCOW2 v3 images, also verifies the dirty bit is cleared. -fn disk_check_consistency( - path_or_image_name: impl AsRef, - initial_backing_checksum: Option<(std::path::PathBuf, String, u32)>, -) { - let path = resolve_disk_path(path_or_image_name); - let output = run_qemu_img(&path, &["check"], None); - - assert!( - output.status.success(), - "qemu-img check failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - match check_dirty_flag(&path) { - Ok(Some(dirty)) => { - assert!(!dirty, "QCOW2 image shutdown unclean"); - } - Ok(None) => {} // Not a QCOW2 v3 image, skip dirty flag check - Err(e) => panic!("Failed to check dirty flag: {e}"), - } - - if let Some((backing_path, format, initial_checksum)) = initial_backing_checksum { - if format.parse::().ok() != Some(block::qcow::ImageType::Raw) { - let output = run_qemu_img(&backing_path, &["check"], None); - - assert!( - output.status.success(), - "qemu-img check of backing file failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - let mut file = File::open(&backing_path).unwrap(); - let file_size = file.metadata().unwrap().len(); - assert_eq!( - initial_checksum, - compute_file_checksum(&mut file, file_size) - ); - } -} - -fn run_qemu_img( - path: &std::path::Path, - args: &[&str], - trailing_args: Option<&[&str]>, -) -> std::process::Output { - let mut cmd = std::process::Command::new("qemu-img"); - cmd.arg(args[0]) - .args(&args[1..]) - .arg(path.to_str().unwrap()); - if let Some(extra) = trailing_args { - cmd.args(extra); - } - cmd.output().unwrap() -} - -fn get_image_info(path: &std::path::Path) -> Option { - let output = run_qemu_img(path, &["info", "-U", "--output=json"], None); - - output.status.success().then_some(())?; - serde_json::from_slice(&output.stdout).ok() -} - -fn get_qcow2_v3_info(path: &Path) -> Result, String> { - let info = get_image_info(path) - .ok_or_else(|| format!("qemu-img info failed for {}", path.display()))?; - if info["format"].as_str() != Some("qcow2") { - return Ok(None); - } - // QCOW2 v3 has compat "1.1", v2 has "0.10" - if info["format-specific"]["data"]["compat"].as_str() != Some("1.1") { - return Ok(None); - } - Ok(Some(info)) -} - -fn check_dirty_flag(path: &Path) -> Result, String> { - Ok(get_qcow2_v3_info(path)?.and_then(|info| info["dirty-flag"].as_bool())) -} - -fn check_corrupt_flag(path: &Path) -> Result, String> { - Ok(get_qcow2_v3_info(path)? - .and_then(|info| info["format-specific"]["data"]["corrupt"].as_bool())) -} - -const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72; - -fn set_corrupt_flag(path: &Path, corrupt: bool) -> io::Result<()> { - let mut file = OpenOptions::new().read(true).write(true).open(path)?; - - file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; - let mut buf = [0u8; 8]; - file.read_exact(&mut buf)?; - let mut features = u64::from_be_bytes(buf); - - if corrupt { - features |= 0x02; - } else { - features &= !0x02; - } - - file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?; - file.write_all(&features.to_be_bytes())?; - file.sync_all()?; - Ok(()) -} - -fn resolve_disk_path(path_or_image_name: impl AsRef) -> std::path::PathBuf { - if path_or_image_name.as_ref().exists() { - // A full path is provided - path_or_image_name.as_ref().to_path_buf() - } else { - // An image name is provided - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - workload_path.as_path().join(path_or_image_name.as_ref()) - } -} - -fn compute_file_checksum(reader: &mut dyn std::io::Read, size: u64) -> u32 { - // Read first 16MB or entire data if smaller - let read_size = cmp::min(size, 16 * 1024 * 1024) as usize; - - let mut buffer = vec![0u8; read_size]; - reader.read_exact(&mut buffer).unwrap(); - - // DJB2 hash - let mut hash: u32 = 5381; - for byte in buffer.iter() { - hash = hash.wrapping_mul(33).wrapping_add(*byte as u32); - } - hash -} - -fn make_virtio_block_guest(factory: &GuestFactory, image_name: &str) -> Guest { - let disk_config = UbuntuDiskConfig::new(image_name.to_string()); - factory.create_guest(Box::new(disk_config)).with_cpu(4) -} +mod common; +use common::tests_wrappers::*; +use common::utils::*; mod common_parallel { use std::io::{self, SeekFrom}; From d77e3e7ca25257443fc20d424359fcce592c92ed Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 09:45:38 +0100 Subject: [PATCH 0825/1893] block: qcow: Add From 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 --- block/src/error.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/block/src/error.rs b/block/src/error.rs index ebaa33ec5b..afd4b5533a 100644 --- a/block/src/error.rs +++ b/block/src/error.rs @@ -231,4 +231,56 @@ impl From for BlockError { } } +/// Temporary scaffolding: classify a `qcow::Error` into the appropriate +/// `BlockErrorKind`. +/// +/// This impl exists only to allow an incremental migration of the qcow +/// parse/construct chain from `qcow::Result` to `BlockResult`. Each +/// subsequent commit replaces bare `?` sites with explicit +/// `BlockError::new(kind, e)` calls. Once every site is migrated this +/// impl will be removed. +impl From for BlockError { + fn from(e: crate::qcow::Error) -> Self { + use crate::qcow::Error as E; + let kind = match &e { + // Structural / format violations + E::InvalidMagic + | E::BackingFileTooLong(_) + | E::InvalidBackingFileName(_) + | E::InvalidClusterSize + | E::InvalidL1TableSize(_) + | E::InvalidL1TableOffset + | E::InvalidOffset(_) + | E::InvalidRefcountTableOffset + | E::InvalidRefcountTableSize(_) + | E::FileTooBig(_) + | E::NoRefcountClusters + | E::RefcountTableOffEnd + | E::RefcountTableTooLarge + | E::TooManyL1Entries(_) + | E::TooManyRefcounts(_) + | E::SizeTooSmallForNumberOfClusters => BlockErrorKind::InvalidFormat, + + // Unsupported features / versions + E::UnsupportedVersion(_) + | E::UnsupportedFeature(_) + | E::UnsupportedCompressionType + | E::UnsupportedBackingFileFormat(_) + | E::UnsupportedRefcountOrder + | E::BackingFilesDisabled + | E::ShrinkNotSupported => BlockErrorKind::UnsupportedFeature, + + // Corrupt image + E::CorruptImage => BlockErrorKind::CorruptImage, + + // Nesting depth overflow + E::MaxNestingDepthExceeded => BlockErrorKind::Overflow, + + // Everything else is I/O + _ => BlockErrorKind::Io, + }; + Self::new(kind, e) + } +} + pub type BlockResult = Result; From c4a5c7f843b000720e666cc9348fb3fd85277259 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 09:46:03 +0100 Subject: [PATCH 0826/1893] 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 --- block/src/qcow/mod.rs | 81 +++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 0ed4cd858d..8248c3f846 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -569,7 +569,7 @@ impl QcowFile { /// Creates a QcowFile from `file`. File must be a valid qcow2 image. /// /// Additionally, max nesting depth of this qcow2 image will be set to default value 10. - pub fn from(file: RawFile) -> Result { + pub fn from(file: RawFile) -> BlockResult { Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH, true) } @@ -579,7 +579,7 @@ impl QcowFile { file: RawFile, max_nesting_depth: u32, sparse: bool, - ) -> Result { + ) -> BlockResult { let (inner, backing_file, sparse) = parse_qcow(file, max_nesting_depth, sparse)?; let metadata::QcowState { raw_file, @@ -607,8 +607,20 @@ impl QcowFile { } /// Creates a new QcowFile at the given path. - pub fn new(file: RawFile, version: u32, virtual_size: u64, sparse: bool) -> Result { - let header = QcowHeader::create_for_size_and_path(version, virtual_size, None)?; + pub fn new( + file: RawFile, + version: u32, + virtual_size: u64, + sparse: bool, + ) -> BlockResult { + let header = + QcowHeader::create_for_size_and_path(version, virtual_size, None).map_err(|e| { + let kind = match &e { + Error::BackingFileTooLong(_) => BlockErrorKind::InvalidFormat, + _ => BlockErrorKind::Io, + }; + BlockError::new(kind, e) + })?; QcowFile::new_from_header(file, &header, sparse) } @@ -619,12 +631,19 @@ impl QcowFile { backing_file_size: u64, backing_config: &BackingFileConfig, sparse: bool, - ) -> Result { + ) -> BlockResult { let mut header = QcowHeader::create_for_size_and_path( version, backing_file_size, Some(&backing_config.path), - )?; + ) + .map_err(|e| { + let kind = match &e { + Error::BackingFileTooLong(_) => BlockErrorKind::InvalidFormat, + _ => BlockErrorKind::Io, + }; + BlockError::new(kind, e) + })?; if let Some(backing_file) = &mut header.backing_file { backing_file.format = backing_config.format; } @@ -632,9 +651,16 @@ impl QcowFile { // backing_file is loaded by new_from_header -> Self::from() based on the header } - fn new_from_header(mut file: RawFile, header: &QcowHeader, sparse: bool) -> Result { - file.rewind().map_err(Error::SeekingFile)?; - header.write_to(&mut file)?; + fn new_from_header( + mut file: RawFile, + header: &QcowHeader, + sparse: bool, + ) -> BlockResult { + file.rewind() + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; + header + .write_to(&mut file) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; let mut qcow = Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH, sparse)?; @@ -646,9 +672,9 @@ impl QcowFile { let mut cluster_addr = 0; while cluster_addr < end_cluster_addr { - let mut unref_clusters = qcow - .set_cluster_refcount(cluster_addr, 1) - .map_err(Error::SettingRefcountRefcount)?; + let mut unref_clusters = qcow.set_cluster_refcount(cluster_addr, 1).map_err(|e| { + BlockError::new(BlockErrorKind::Io, Error::SettingRefcountRefcount(e)) + })?; qcow.unref_clusters.append(&mut unref_clusters); cluster_addr += cluster_size; } @@ -2180,6 +2206,7 @@ pub fn detect_image_type(file: &mut RawFile) -> Result { #[cfg(test)] mod unit_tests { + use std::error::Error as StdError; use std::fs::File; use std::path::Path; @@ -2531,7 +2558,11 @@ mod unit_tests { disk_file.rewind().unwrap(); // The maximum nesting depth is 0, which means backing file is not allowed. let res = QcowFile::from_with_nesting_depth(disk_file, 0, true); - assert!(matches!(res.unwrap_err(), Error::MaxNestingDepthExceeded)); + let err = res.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::Overflow)); + let source = StdError::source(&err).unwrap(); + let qcow_err = source.downcast_ref::().unwrap(); + assert!(matches!(qcow_err, Error::MaxNestingDepthExceeded)); } /// Create a qcow2 file with itself as its backing file. @@ -3839,7 +3870,7 @@ mod unit_tests { assert!(result.is_err()); let err = result.unwrap_err(); assert!( - matches!(err, Error::CorruptImage), + matches!(err.kind(), BlockErrorKind::CorruptImage), "Expected CorruptImage error, got: {err:?}" ); }); @@ -3853,8 +3884,11 @@ mod unit_tests { let result = QcowFile::from(disk_file); assert!(result.is_err()); let err = result.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature)); + let source = StdError::source(&err).unwrap(); + let qcow_err = source.downcast_ref::().unwrap(); assert!( - matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("external")), + matches!(qcow_err, Error::UnsupportedFeature(v) if v.to_string().contains("external")), "Expected UnsupportedFeature error mentioning external, got: {err:?}" ); }); @@ -3868,8 +3902,11 @@ mod unit_tests { let result = QcowFile::from(disk_file); assert!(result.is_err()); let err = result.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature)); + let source = StdError::source(&err).unwrap(); + let qcow_err = source.downcast_ref::().unwrap(); assert!( - matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("extended")), + matches!(qcow_err, Error::UnsupportedFeature(v) if v.to_string().contains("extended")), "Expected UnsupportedFeature error mentioning extended, got: {err:?}" ); }); @@ -3882,7 +3919,10 @@ mod unit_tests { with_basic_file(&header, |disk_file: RawFile| { let result = QcowFile::from(disk_file); assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), Error::UnsupportedFeature(_))); + assert!(matches!( + result.unwrap_err().kind(), + BlockErrorKind::UnsupportedFeature + )); }); } @@ -3894,8 +3934,11 @@ mod unit_tests { let result = QcowFile::from(disk_file); assert!(result.is_err()); let err = result.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature)); + let source = StdError::source(&err).unwrap(); + let qcow_err = source.downcast_ref::().unwrap(); assert!( - matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("unknown")), + matches!(qcow_err, Error::UnsupportedFeature(v) if v.to_string().contains("unknown")), "Expected UnsupportedFeature error mentioning unknown, got: {err:?}" ); }); @@ -4095,7 +4138,7 @@ mod unit_tests { assert!(result.is_err()); let err = result.unwrap_err(); assert!( - matches!(err, Error::CorruptImage), + matches!(err.kind(), BlockErrorKind::CorruptImage), "Expected CorruptImage error, got: {err:?}" ); }); From be9ef116aad907525ba5bb14fc7783aa6c392472 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 13 Mar 2026 23:39:45 +0100 Subject: [PATCH 0827/1893] 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 --- block/src/error.rs | 11 ++++++ block/src/qcow/mod.rs | 90 ++++++++++++++++++++++++++++++++++-------- block/src/qcow_sync.rs | 27 ++++--------- 3 files changed, 92 insertions(+), 36 deletions(-) diff --git a/block/src/error.rs b/block/src/error.rs index afd4b5533a..b235b17e48 100644 --- a/block/src/error.rs +++ b/block/src/error.rs @@ -167,6 +167,12 @@ impl BlockError { 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); @@ -204,6 +210,11 @@ impl BlockError { pub fn downcast_ref(&self) -> Option<&T> { self.source.as_ref()?.downcast_ref::() } + + /// Consume the error and return the boxed source, if any. + pub fn into_source(self) -> Option> { + self.source + } } impl Display for BlockError { diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 8248c3f846..27c7e3a8cd 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -189,14 +189,17 @@ impl BackingFile { direct_io: bool, max_nesting_depth: u32, sparse: bool, - ) -> Result> { + ) -> BlockResult> { let Some(config) = backing_file_config else { return Ok(None); }; // Check nesting depth - applies to any backing file if max_nesting_depth == 0 { - return Err(Error::MaxNestingDepthExceeded); + return Err(BlockError::new( + BlockErrorKind::Overflow, + Error::MaxNestingDepthExceeded, + )); } let backing_raw_file = OpenOptions::new() @@ -224,8 +227,17 @@ impl BackingFile { } ImageType::Qcow2 => { let (inner, nested_backing, _sparse) = - parse_qcow(raw_file, max_nesting_depth - 1, sparse) - .map_err(|e| Error::BackingFileOpen(config.path.clone(), Box::new(e)))?; + parse_qcow(raw_file, max_nesting_depth - 1, sparse).map_err(|e| { + let kind = e.kind(); + let source = e + .into_source() + .and_then(|s| s.downcast::().ok()) + .map(|qcow_err| Error::BackingFileOpen(config.path.clone(), qcow_err)); + match source { + Some(err) => BlockError::new(kind, err), + None => BlockError::from_kind(kind), + } + })?; let size = inner.header.size; ( BackingKind::Qcow { @@ -335,28 +347,51 @@ pub(crate) fn parse_qcow( mut file: RawFile, max_nesting_depth: u32, sparse: bool, -) -> Result<(metadata::QcowState, Option, bool)> { - let mut header = QcowHeader::new(&mut file)?; +) -> BlockResult<(metadata::QcowState, Option, bool)> { + let mut header = QcowHeader::new(&mut file).map_err(|e| { + let kind = match &e { + Error::InvalidMagic + | Error::BackingFileTooLong(_) + | Error::InvalidBackingFileName(_) => BlockErrorKind::InvalidFormat, + Error::UnsupportedFeature(_) | Error::UnsupportedCompressionType => { + BlockErrorKind::UnsupportedFeature + } + _ => BlockErrorKind::Io, + }; + BlockError::new(kind, e) + })?; // Only v2 and v3 files are supported. if header.version != 2 && header.version != 3 { - return Err(Error::UnsupportedVersion(header.version)); + return Err(BlockError::new( + BlockErrorKind::UnsupportedFeature, + Error::UnsupportedVersion(header.version), + )); } // Make sure that the L1 table fits in RAM. if u64::from(header.l1_size) > MAX_RAM_POINTER_TABLE_SIZE { - return Err(Error::InvalidL1TableSize(header.l1_size)); + return Err(BlockError::new( + BlockErrorKind::InvalidFormat, + Error::InvalidL1TableSize(header.l1_size), + )); } let cluster_bits: u32 = header.cluster_bits; if !(MIN_CLUSTER_BITS..=MAX_CLUSTER_BITS).contains(&cluster_bits) { - return Err(Error::InvalidClusterSize); + return Err(BlockError::new( + BlockErrorKind::InvalidFormat, + Error::InvalidClusterSize, + )); } let cluster_size = 0x01u64 << cluster_bits; // Limit the total size of the disk. if header.size > MAX_QCOW_FILE_SIZE { - return Err(Error::FileTooBig(header.size)); + return Err(BlockError::new( + BlockErrorKind::InvalidFormat, + Error::FileTooBig(header.size), + )); } let direct_io = file.is_direct(); @@ -373,12 +408,18 @@ pub(crate) fn parse_qcow( .checked_shl(header.refcount_order) .ok_or(Error::UnsupportedRefcountOrder)?; if refcount_bits > 64 { - return Err(Error::UnsupportedRefcountOrder); + return Err(BlockError::new( + BlockErrorKind::UnsupportedFeature, + Error::UnsupportedRefcountOrder, + )); } // Need at least one refcount cluster if header.refcount_table_clusters == 0 { - return Err(Error::NoRefcountClusters); + return Err(BlockError::new( + BlockErrorKind::InvalidFormat, + Error::NoRefcountClusters, + )); } offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?; offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?; @@ -386,7 +427,10 @@ pub(crate) fn parse_qcow( offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?; let file_size = file.metadata().map_err(Error::GettingFileSize)?.len(); if header.refcount_table_offset > max(file_size, header.size) { - return Err(Error::RefcountTableOffEnd); + return Err(BlockError::new( + BlockErrorKind::CorruptImage, + Error::RefcountTableOffEnd, + )); } // The first cluster should always have a non-zero refcount, so if it is 0, @@ -414,7 +458,10 @@ pub(crate) fn parse_qcow( if header.is_corrupt() { if is_writable { - return Err(Error::CorruptImage); + return Err(BlockError::new( + BlockErrorKind::CorruptImage, + Error::CorruptImage, + )); } let path = read_link(format!("/proc/self/fd/{}", raw_file.file().as_raw_fd())) .map_or_else(|_| "".to_string(), |p| p.display().to_string()); @@ -440,7 +487,10 @@ pub(crate) fn parse_qcow( let l1_clusters = div_round_up_u64(num_l2_clusters, entries_per_cluster); let header_clusters = div_round_up_u64(size_of::() as u64, cluster_size); if num_l2_clusters > MAX_RAM_POINTER_TABLE_SIZE { - return Err(Error::TooManyL1Entries(num_l2_clusters)); + return Err(BlockError::new( + BlockErrorKind::CorruptImage, + Error::TooManyL1Entries(num_l2_clusters), + )); } let l1_table = VecCache::from_vec( raw_file @@ -460,10 +510,16 @@ pub(crate) fn parse_qcow( ); // Check that the given header doesn't have a suspiciously sized refcount table. if u64::from(header.refcount_table_clusters) > 2 * refcount_clusters { - return Err(Error::RefcountTableTooLarge); + return Err(BlockError::new( + BlockErrorKind::CorruptImage, + Error::RefcountTableTooLarge, + )); } if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE { - return Err(Error::TooManyRefcounts(refcount_clusters)); + return Err(BlockError::new( + BlockErrorKind::InvalidFormat, + Error::TooManyRefcounts(refcount_clusters), + )); } let refcount_block_entries = cluster_size * 8 / refcount_bits; let mut refcounts = RefCount::new( diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index cce4c192d7..7340a7aa40 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -197,25 +197,14 @@ impl QcowDiskSync { ) -> BlockResult { let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 }; let (inner, backing_file, sparse) = - parse_qcow(RawFile::new(file, direct_io), max_nesting_depth, sparse) - .map_err(|e| match e { - QcowError::MaxNestingDepthExceeded if !backing_files => { - QcowError::BackingFilesDisabled - } - other => other, - }) - .map_err(|e| { - let kind = match &e { - QcowError::InvalidMagic | QcowError::UnsupportedVersion(_) => { - BlockErrorKind::InvalidFormat - } - QcowError::UnsupportedFeature(_) | QcowError::BackingFilesDisabled => { - BlockErrorKind::UnsupportedFeature - } - _ => BlockErrorKind::Io, - }; - BlockError::new(kind, e).with_op(ErrorOp::Open) - })?; + parse_qcow(RawFile::new(file, direct_io), 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(QcowDiskSync { metadata: Arc::new(QcowMetadata::new(inner)), From 524e620240d82bcbf31c8327073cf51b65adb0e6 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 13 Mar 2026 23:53:42 +0100 Subject: [PATCH 0828/1893] 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 --- block/src/qcow/mod.rs | 92 ++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 27c7e3a8cd..c926642e64 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -205,7 +205,12 @@ impl BackingFile { let backing_raw_file = OpenOptions::new() .read(true) .open(&config.path) - .map_err(|e| Error::BackingFileIo(config.path.clone(), e))?; + .map_err(|e| { + BlockError::new( + BlockErrorKind::Io, + Error::BackingFileIo(config.path.clone(), e), + ) + })?; let mut raw_file = RawFile::new(backing_raw_file, direct_io); @@ -217,12 +222,18 @@ impl BackingFile { let (kind, virtual_size) = match backing_format { ImageType::Raw => { - let size = raw_file - .seek(SeekFrom::End(0)) - .map_err(|e| Error::BackingFileIo(config.path.clone(), e))?; - raw_file - .rewind() - .map_err(|e| Error::BackingFileIo(config.path.clone(), e))?; + let size = raw_file.seek(SeekFrom::End(0)).map_err(|e| { + BlockError::new( + BlockErrorKind::Io, + Error::BackingFileIo(config.path.clone(), e), + ) + })?; + raw_file.rewind().map_err(|e| { + BlockError::new( + BlockErrorKind::Io, + Error::BackingFileIo(config.path.clone(), e), + ) + })?; (BackingKind::Raw(raw_file), size) } ImageType::Qcow2 => { @@ -404,9 +415,12 @@ pub(crate) fn parse_qcow( )?; // Validate refcount order to be 0..6 - let refcount_bits: u64 = 0x01u64 - .checked_shl(header.refcount_order) - .ok_or(Error::UnsupportedRefcountOrder)?; + let refcount_bits: u64 = 0x01u64.checked_shl(header.refcount_order).ok_or_else(|| { + BlockError::new( + BlockErrorKind::UnsupportedFeature, + Error::UnsupportedRefcountOrder, + ) + })?; if refcount_bits > 64 { return Err(BlockError::new( BlockErrorKind::UnsupportedFeature, @@ -421,11 +435,17 @@ pub(crate) fn parse_qcow( Error::NoRefcountClusters, )); } - offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?; - offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?; + offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits) + .map_err(|e| BlockError::new(BlockErrorKind::CorruptImage, e))?; + offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits) + .map_err(|e| BlockError::new(BlockErrorKind::CorruptImage, e))?; // refcount table must be a cluster boundary, and within the file's virtual or actual size. - offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?; - let file_size = file.metadata().map_err(Error::GettingFileSize)?.len(); + offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits) + .map_err(|e| BlockError::new(BlockErrorKind::CorruptImage, e))?; + let file_size = file + .metadata() + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))? + .len(); if header.refcount_table_offset > max(file_size, header.size) { return Err(BlockError::new( BlockErrorKind::CorruptImage, @@ -437,12 +457,14 @@ pub(crate) fn parse_qcow( // this is an old file with broken refcounts, which requires a rebuild. let mut refcount_rebuild_required = true; file.seek(SeekFrom::Start(header.refcount_table_offset)) - .map_err(Error::SeekingFile)?; - let first_refblock_addr = u64::read_be(&mut file).map_err(Error::ReadingHeader)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; + let first_refblock_addr = u64::read_be(&mut file) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?; if first_refblock_addr != 0 { file.seek(SeekFrom::Start(first_refblock_addr)) - .map_err(Error::SeekingFile)?; - let first_cluster_refcount = u16::read_be(&mut file).map_err(Error::ReadingHeader)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; + let first_cluster_refcount = u16::read_be(&mut file) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?; if first_cluster_refcount != 0 { refcount_rebuild_required = false; } @@ -452,8 +474,8 @@ pub(crate) fn parse_qcow( refcount_rebuild_required = true; } - let mut raw_file = - QcowRawFile::from(file, cluster_size, refcount_bits).ok_or(Error::InvalidClusterSize)?; + let mut raw_file = QcowRawFile::from(file, cluster_size, refcount_bits) + .ok_or_else(|| BlockError::new(BlockErrorKind::InvalidFormat, Error::InvalidClusterSize))?; let is_writable = raw_file.file().is_writable(); if header.is_corrupt() { @@ -499,7 +521,7 @@ pub(crate) fn parse_qcow( num_l2_clusters, Some(L1_TABLE_OFFSET_MASK), ) - .map_err(Error::ReadingHeader)?, + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?, ); let num_clusters = div_round_up_u64(header.size, cluster_size); @@ -530,7 +552,7 @@ pub(crate) fn parse_qcow( cluster_size, refcount_bits, ) - .map_err(Error::ReadingRefCounts)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingRefCounts(e)))?; let l2_entries = cluster_size / size_of::() as u64; @@ -539,23 +561,30 @@ pub(crate) fn parse_qcow( header .l1_table_offset .checked_add(l1_index * size_of::() as u64) - .ok_or(Error::InvalidL1TableOffset)?; + .ok_or_else(|| { + BlockError::new(BlockErrorKind::CorruptImage, Error::InvalidL1TableOffset) + })?; header .refcount_table_offset .checked_add(u64::from(header.refcount_table_clusters) * cluster_size) - .ok_or(Error::InvalidRefcountTableOffset)?; + .ok_or_else(|| { + BlockError::new( + BlockErrorKind::CorruptImage, + Error::InvalidRefcountTableOffset, + ) + })?; // Find available (refcount == 0) clusters for the free list. let file_size = raw_file .file_mut() .metadata() - .map_err(Error::GettingFileSize)? + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))? .len(); let mut avail_clusters = Vec::new(); for i in (0..file_size).step_by(cluster_size as usize) { let refcount = refcounts .get_cluster_refcount(&mut raw_file, i) - .map_err(Error::GettingRefcount)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingRefcount(e)))?; if refcount == 0 { avail_clusters.push(i); } @@ -567,10 +596,17 @@ pub(crate) fn parse_qcow( { header .set_dirty_bit(raw_file.file_mut(), true) - .map_err(|e| Error::WritingHeader(io::Error::other(e)))?; + .map_err(|e| { + BlockError::new( + BlockErrorKind::Io, + Error::WritingHeader(io::Error::other(e)), + ) + })?; } - header.clear_autoclear_features(raw_file.file_mut())?; + header + .clear_autoclear_features(raw_file.file_mut()) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; } let inner = metadata::QcowState { From f6ec817b9b24f8724b1d15b7905d6afc1d86ec91 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 00:00:14 +0100 Subject: [PATCH 0829/1893] 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 --- block/src/qcow/mod.rs | 58 +++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index c926642e64..75afd268dc 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -868,7 +868,7 @@ impl QcowFile { /// if needed. Shrinking is not supported, as it could lead to data /// loss. Not supported when a backing file is present in that case /// an error is returned. - pub fn resize(&mut self, new_size: u64) -> Result<()> { + pub fn resize(&mut self, new_size: u64) -> BlockResult<()> { let current_size = self.virtual_size(); if new_size == current_size { @@ -876,11 +876,17 @@ impl QcowFile { } if new_size < current_size { - return Err(Error::ShrinkNotSupported); + return Err(BlockError::new( + BlockErrorKind::UnsupportedFeature, + Error::ShrinkNotSupported, + )); } if self.backing_file.is_some() { - return Err(Error::ResizeWithBackingFile); + return Err(BlockError::new( + BlockErrorKind::UnsupportedFeature, + Error::ResizeWithBackingFile, + )); } // Grow the L1 table if needed @@ -898,18 +904,20 @@ impl QcowFile { self.raw_file .file_mut() .rewind() - .map_err(Error::SeekingFile)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; self.header .write_to(self.raw_file.file_mut()) .map_err(|e| match e { - Error::WritingHeader(io_err) => Error::ResizeIo(io_err), - other => other, + Error::WritingHeader(io_err) => { + BlockError::new(BlockErrorKind::Io, Error::ResizeIo(io_err)) + } + other => BlockError::new(BlockErrorKind::Io, other), })?; self.raw_file .file_mut() .sync_all() - .map_err(Error::SyncingHeader)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?; Ok(()) } @@ -919,7 +927,7 @@ impl QcowFile { /// This allocates a new L1 table at file end (guaranteeing contiguity), /// copies existing entries, updates refcounts, and atomically switches /// to the new table. - fn grow_l1_table(&mut self, new_l1_size: u32) -> Result<()> { + fn grow_l1_table(&mut self, new_l1_size: u32) -> BlockResult<()> { let old_l1_size = self.header.l1_size; let old_l1_offset = self.header.l1_table_offset; let cluster_size = self.raw_file.cluster_size(); @@ -932,7 +940,7 @@ impl QcowFile { .raw_file .file_mut() .seek(SeekFrom::End(0)) - .map_err(Error::ResizeIo)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ResizeIo(e)))?; let new_l1_offset = self.raw_file.cluster_address(file_size + cluster_size - 1); // Extend file to fit all L1 clusters @@ -940,12 +948,12 @@ impl QcowFile { self.raw_file .file_mut() .set_len(new_file_end) - .map_err(Error::SettingFileSize)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SettingFileSize(e)))?; // Set refcounts for the contiguous range for i in 0..new_l1_clusters { self.set_cluster_refcount(new_l1_offset + i * cluster_size, 1) - .map_err(Error::ResizeIo)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ResizeIo(e)))?; } let mut new_l1_data = vec![0u64; new_l1_size as usize]; @@ -957,7 +965,7 @@ impl QcowFile { let refcount = self .refcounts .get_cluster_refcount(&mut self.raw_file, *l2_addr) - .map_err(Error::GettingRefcount)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingRefcount(e)))?; *l2_addr = l1_entry_make(*l2_addr, refcount == 1); } } @@ -965,12 +973,12 @@ impl QcowFile { // Write the new L1 table to the file. self.raw_file .write_pointer_table_direct(new_l1_offset, new_l1_data.iter()) - .map_err(Error::ResizeIo)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ResizeIo(e)))?; self.raw_file .file_mut() .sync_all() - .map_err(Error::SyncingHeader)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?; self.header.l1_size = new_l1_size; self.header.l1_table_offset = new_l1_offset; @@ -978,13 +986,15 @@ impl QcowFile { self.raw_file .file_mut() .rewind() - .map_err(Error::SeekingFile)?; - self.header.write_to(self.raw_file.file_mut())?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; + self.header + .write_to(self.raw_file.file_mut()) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; self.raw_file .file_mut() .sync_all() - .map_err(Error::SyncingHeader)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?; // Free old L1 table clusters let old_l1_bytes = old_l1_size as u64 * size_of::() as u64; @@ -3141,7 +3151,12 @@ mod unit_tests { let result = q.resize(smaller_size); assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), Error::ShrinkNotSupported)); + let err = result.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature)); + assert!(matches!( + err.downcast_ref::(), + Some(Error::ShrinkNotSupported) + )); assert_eq!(q.virtual_size(), original_size); }); @@ -3172,7 +3187,12 @@ mod unit_tests { let result = overlay.resize(backing_size * 2); assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), Error::ResizeWithBackingFile)); + let err = result.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature)); + assert!(matches!( + err.downcast_ref::(), + Some(Error::ResizeWithBackingFile) + )); assert_eq!(overlay.virtual_size(), backing_size); } From 9daf1782a87ba983a5c8cafe143e575c7a02be98 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 00:31:37 +0100 Subject: [PATCH 0830/1893] 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 --- block/src/qcow/mod.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 75afd268dc..95df69d36f 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -1011,7 +1011,7 @@ impl QcowFile { } /// Rebuild the reference count tables. - fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> Result<()> { + fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockResult<()> { fn add_ref( refcounts: &mut [u64], cluster_size: u64, @@ -1234,7 +1234,7 @@ impl QcowFile { let file_size = raw_file .file_mut() .metadata() - .map_err(Error::GettingFileSize)? + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))? .len(); let refcount_bits = 1u64 << header.refcount_order; @@ -1264,25 +1264,33 @@ impl QcowFile { max_valid_cluster_index += refblocks_for_refs + reftable_clusters_for_refs; if max_valid_cluster_index > MAX_RAM_POINTER_TABLE_SIZE { - return Err(Error::InvalidRefcountTableSize(max_valid_cluster_index)); + return Err(BlockError::new( + BlockErrorKind::CorruptImage, + Error::InvalidRefcountTableSize(max_valid_cluster_index), + )); } let max_valid_cluster_offset = max_valid_cluster_index * cluster_size; if max_valid_cluster_offset < file_size - cluster_size { - return Err(Error::InvalidRefcountTableSize(max_valid_cluster_offset)); + return Err(BlockError::new( + BlockErrorKind::CorruptImage, + Error::InvalidRefcountTableSize(max_valid_cluster_offset), + )); } let mut refcounts = vec![0; max_valid_cluster_index as usize]; // Find all references clusters and rebuild refcounts. - set_header_refcount(&mut refcounts, cluster_size, max_refcount, refcount_bits)?; + set_header_refcount(&mut refcounts, cluster_size, max_refcount, refcount_bits) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; set_l1_refcounts( &mut refcounts, &header, cluster_size, max_refcount, refcount_bits, - )?; + ) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; set_data_refcounts( &mut refcounts, &header, @@ -1290,14 +1298,16 @@ impl QcowFile { raw_file, max_refcount, refcount_bits, - )?; + ) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; set_refcount_table_refcounts( &mut refcounts, &header, cluster_size, max_refcount, refcount_bits, - )?; + ) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; // Allocate clusters to store the new reference count blocks. let ref_table = alloc_refblocks( @@ -1306,7 +1316,8 @@ impl QcowFile { refblock_clusters, max_refcount, refcount_bits, - )?; + ) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; // Write updated reference counts and point the reftable at them. write_refblocks( @@ -1316,6 +1327,7 @@ impl QcowFile { raw_file, refcount_block_entries, ) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e)) } // Limits the range so that it doesn't exceed the virtual size of the file. From 5410d4b2d568154069338bab25f88398c22a4e7b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 00:46:20 +0100 Subject: [PATCH 0831/1893] 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 --- block/src/qcow/mod.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 95df69d36f..0c77b865cc 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -2286,8 +2286,7 @@ pub fn convert( dst_type: ImageType, src_max_nesting_depth: u32, ) -> BlockResult<()> { - let src_type = - detect_image_type(&mut src_file).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; + let src_type = detect_image_type(&mut src_file)?; match src_type { ImageType::Qcow2 => { let mut src_reader = @@ -2304,17 +2303,21 @@ pub fn convert( } /// Detect the type of an image file by checking for a valid qcow2 header. -pub fn detect_image_type(file: &mut RawFile) -> Result { - let orig_seek = file.stream_position().map_err(Error::SeekingFile)?; - file.rewind().map_err(Error::SeekingFile)?; - let magic = u32::read_be(file).map_err(Error::ReadingHeader)?; +pub fn detect_image_type(file: &mut RawFile) -> BlockResult { + let orig_seek = file + .stream_position() + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; + file.rewind() + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; + let magic = u32::read_be(file) + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?; let image_type = if magic == QCOW_MAGIC { ImageType::Qcow2 } else { ImageType::Raw }; file.seek(SeekFrom::Start(orig_seek)) - .map_err(Error::SeekingFile)?; + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; Ok(image_type) } From fc79d08d7ddffa2a5ca743327e89e0e689f02b53 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 01:07:31 +0100 Subject: [PATCH 0832/1893] block: qcow: Remove From 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 --- block/src/error.rs | 52 ---------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/block/src/error.rs b/block/src/error.rs index b235b17e48..645057005e 100644 --- a/block/src/error.rs +++ b/block/src/error.rs @@ -242,56 +242,4 @@ impl From for BlockError { } } -/// Temporary scaffolding: classify a `qcow::Error` into the appropriate -/// `BlockErrorKind`. -/// -/// This impl exists only to allow an incremental migration of the qcow -/// parse/construct chain from `qcow::Result` to `BlockResult`. Each -/// subsequent commit replaces bare `?` sites with explicit -/// `BlockError::new(kind, e)` calls. Once every site is migrated this -/// impl will be removed. -impl From for BlockError { - fn from(e: crate::qcow::Error) -> Self { - use crate::qcow::Error as E; - let kind = match &e { - // Structural / format violations - E::InvalidMagic - | E::BackingFileTooLong(_) - | E::InvalidBackingFileName(_) - | E::InvalidClusterSize - | E::InvalidL1TableSize(_) - | E::InvalidL1TableOffset - | E::InvalidOffset(_) - | E::InvalidRefcountTableOffset - | E::InvalidRefcountTableSize(_) - | E::FileTooBig(_) - | E::NoRefcountClusters - | E::RefcountTableOffEnd - | E::RefcountTableTooLarge - | E::TooManyL1Entries(_) - | E::TooManyRefcounts(_) - | E::SizeTooSmallForNumberOfClusters => BlockErrorKind::InvalidFormat, - - // Unsupported features / versions - E::UnsupportedVersion(_) - | E::UnsupportedFeature(_) - | E::UnsupportedCompressionType - | E::UnsupportedBackingFileFormat(_) - | E::UnsupportedRefcountOrder - | E::BackingFilesDisabled - | E::ShrinkNotSupported => BlockErrorKind::UnsupportedFeature, - - // Corrupt image - E::CorruptImage => BlockErrorKind::CorruptImage, - - // Nesting depth overflow - E::MaxNestingDepthExceeded => BlockErrorKind::Overflow, - - // Everything else is I/O - _ => BlockErrorKind::Io, - }; - Self::new(kind, e) - } -} - pub type BlockResult = Result; From d3cad420a5c6714db13d0c9b222fe435548fbc10 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 21 Mar 2026 09:37:35 +0100 Subject: [PATCH 0833/1893] 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 --- block/src/lib.rs | 18 +++++++++++++++--- virtio-devices/src/block.rs | 7 ++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 288db3fbfd..b6c083cefb 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -70,7 +70,11 @@ use crate::vhdx::VhdxError; const SECTOR_SHIFT: u8 = 9; pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT; -/// Field offsets within `struct virtio_blk_discard_write_zeroes`. +/// Maximum number of segments per DISCARD or WRITE_ZEROES request. +pub const MAX_DISCARD_WRITE_ZEROES_SEG: u32 = 1; + +/// Size and field offsets within `struct virtio_blk_discard_write_zeroes`. +const DISCARD_WZ_SEG_SIZE: u32 = mem::size_of::() as u32; const DISCARD_WZ_SECTOR_OFFSET: u64 = mem::offset_of!(virtio_blk_discard_write_zeroes, sector) as u64; const DISCARD_WZ_NUM_SECTORS_OFFSET: u64 = @@ -105,6 +109,8 @@ pub enum Error { RawFileError(#[source] std::io::Error), #[error("The requested operation does not support multiple descriptors")] TooManyDescriptors, + #[error("Request contains too many segments")] + TooManySegments, #[error("Failure in vhdx")] VhdxError(#[source] VhdxError), } @@ -591,9 +597,12 @@ impl Request { return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); }; - if data_len < 16 { + if data_len < DISCARD_WZ_SEG_SIZE { return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); } + if data_len > DISCARD_WZ_SEG_SIZE * MAX_DISCARD_WRITE_ZEROES_SEG { + return Err(ExecuteError::BadRequest(Error::TooManySegments)); + } let mut discard_sector = [0u8; 8]; let mut discard_num_sectors = [0u8; 4]; @@ -630,9 +639,12 @@ impl Request { return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); }; - if data_len < 16 { + if data_len < DISCARD_WZ_SEG_SIZE { return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); } + if data_len > DISCARD_WZ_SEG_SIZE * MAX_DISCARD_WRITE_ZEROES_SEG { + return Err(ExecuteError::BadRequest(Error::TooManySegments)); + } let mut wz_sector = [0u8; 8]; let mut wz_num_sectors = [0u8; 4]; diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index edd1327f45..97d7c58c15 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -23,7 +23,8 @@ use block::disk_file::DiskBackend; use block::error::BlockError; use block::fcntl::{LockError, LockGranularity, LockGranularityChoice, LockType, get_lock_state}; use block::{ - ExecuteAsync, ExecuteError, Request, RequestType, VirtioBlockConfig, build_serial, fcntl, + ExecuteAsync, ExecuteError, MAX_DISCARD_WRITE_ZEROES_SEG, Request, RequestType, + VirtioBlockConfig, build_serial, fcntl, }; use event_monitor::event; use log::{debug, error, info, warn}; @@ -829,12 +830,12 @@ impl Block { if avail_features & (1u64 << VIRTIO_BLK_F_WRITE_ZEROES) != 0 { config.max_write_zeroes_sectors = u32::MAX; - config.max_write_zeroes_seg = 1; + config.max_write_zeroes_seg = MAX_DISCARD_WRITE_ZEROES_SEG; config.write_zeroes_may_unmap = if discard_supported { 1 } else { 0 }; } if avail_features & (1u64 << VIRTIO_BLK_F_DISCARD) != 0 { config.max_discard_sectors = u32::MAX; - config.max_discard_seg = 1; + config.max_discard_seg = MAX_DISCARD_WRITE_ZEROES_SEG; config.discard_sector_alignment = (logical_block_size / SECTOR_SIZE) as u32; } From c79f3acfabf352af75f8b2081768503812a5127a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 23:25:29 +0100 Subject: [PATCH 0834/1893] 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 --- block/src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/block/src/lib.rs b/block/src/lib.rs index b6c083cefb..81b3f27c00 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -625,6 +625,13 @@ impl Request { let discard_num_sectors = u32::from_le_bytes(discard_num_sectors); + let top = discard_sector + .checked_add(discard_num_sectors as u64) + .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; + if top > disk_nsectors { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + let discard_offset = discard_sector * SECTOR_SIZE; let discard_length = (discard_num_sectors as u64) * SECTOR_SIZE; @@ -672,6 +679,14 @@ impl Request { if wz_offset == 0 && disable_sector0_writes { return Err(ExecuteError::BadRequest(Error::InvalidOffset)); } + + let top = wz_sector + .checked_add(wz_num_sectors as u64) + .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; + if top > disk_nsectors { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + let wz_length = (wz_num_sectors as u64) * SECTOR_SIZE; if wz_flags & VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP != 0 { From 4f52e9355e922413047a35190041670800f767f9 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 22 Mar 2026 11:27:55 +0000 Subject: [PATCH 0835/1893] 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 --- virtio-devices/src/seccomp_filters.rs | 12 ++++-------- vmm/src/seccomp_filters.rs | 27 +++++++++++++-------------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs index 63d01a5d8d..f44fdc1b92 100644 --- a/virtio-devices/src/seccomp_filters.rs +++ b/virtio-devices/src/seccomp_filters.rs @@ -146,11 +146,11 @@ fn virtio_mem_thread_rules() -> Vec<(i64, Vec)> { fn virtio_net_thread_rules() -> Vec<(i64, Vec)> { vec![ + #[cfg(feature = "sev_snp")] + (libc::SYS_ioctl, create_mshv_sev_snp_ioctl_seccomp_rule()), (libc::SYS_readv, vec![]), (libc::SYS_timerfd_settime, vec![]), (libc::SYS_writev, vec![]), - #[cfg(feature = "sev_snp")] - (libc::SYS_ioctl, create_mshv_sev_snp_ioctl_seccomp_rule()), ] } @@ -254,14 +254,11 @@ fn virtio_vsock_thread_rules() -> Vec<(i64, Vec)> { vec![ (libc::SYS_accept4, vec![]), (libc::SYS_connect, vec![]), + (libc::SYS_fcntl, vec![]), (libc::SYS_ioctl, create_vsock_ioctl_seccomp_rule()), (libc::SYS_recvfrom, vec![]), (libc::SYS_sendto, vec![]), (libc::SYS_socket, vec![]), - // If debug_assertions is enabled, closing a file first checks - // whether the FD is valid with fcntl. - #[cfg(debug_assertions)] - (libc::SYS_fcntl, vec![]), ] } @@ -308,6 +305,7 @@ fn virtio_thread_common() -> Vec<(i64, Vec)> { #[cfg(target_arch = "x86_64")] (libc::SYS_epoll_wait, vec![]), (libc::SYS_exit, vec![]), + (libc::SYS_fcntl, vec![]), (libc::SYS_futex, vec![]), (libc::SYS_gettid, vec![]), (libc::SYS_madvise, vec![]), @@ -321,8 +319,6 @@ fn virtio_thread_common() -> Vec<(i64, Vec)> { (libc::SYS_rt_sigreturn, vec![]), (libc::SYS_sigaltstack, vec![]), (libc::SYS_write, vec![]), - #[cfg(debug_assertions)] - (libc::SYS_fcntl, vec![]), ] } diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 938e7832f3..18b8ba097d 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -501,6 +501,7 @@ fn signal_handler_thread_rules() -> Result)>, Backend (libc::SYS_close, vec![]), (libc::SYS_exit, vec![]), (libc::SYS_exit_group, vec![]), + (libc::SYS_fcntl, vec![]), (libc::SYS_futex, vec![]), (libc::SYS_ioctl, create_signal_handler_ioctl_seccomp_rule()?), (libc::SYS_landlock_create_ruleset, vec![]), @@ -517,8 +518,6 @@ fn signal_handler_thread_rules() -> Result)>, Backend (libc::SYS_sendto, vec![]), (libc::SYS_sigaltstack, vec![]), (libc::SYS_write, vec![]), - #[cfg(debug_assertions)] - (libc::SYS_fcntl, vec![]), ]) } @@ -534,7 +533,10 @@ fn pty_foreground_thread_rules() -> Result)>, Backend Ok(vec![ (libc::SYS_close, vec![]), (libc::SYS_exit_group, vec![]), + (libc::SYS_fcntl, vec![]), + (libc::SYS_getcwd, vec![]), (libc::SYS_getpgid, vec![]), + (libc::SYS_gettid, vec![]), #[cfg(target_arch = "x86_64")] (libc::SYS_getpgrp, vec![]), (libc::SYS_ioctl, create_pty_foreground_ioctl_seccomp_rule()?), @@ -549,12 +551,8 @@ fn pty_foreground_thread_rules() -> Result)>, Backend (libc::SYS_rt_sigreturn, vec![]), (libc::SYS_sched_yield, vec![]), (libc::SYS_setsid, vec![]), - (libc::SYS_gettid, vec![]), (libc::SYS_sigaltstack, vec![]), (libc::SYS_write, vec![]), - #[cfg(debug_assertions)] - (libc::SYS_fcntl, vec![]), - (libc::SYS_getcwd, vec![]), ]) } @@ -598,6 +596,7 @@ fn vmm_thread_rules( #[cfg(target_arch = "aarch64")] (libc::SYS_newfstatat, vec![]), (libc::SYS_futex, vec![]), + (libc::SYS_getcwd, vec![]), (libc::SYS_getdents64, vec![]), (libc::SYS_getpgid, vec![]), #[cfg(target_arch = "x86_64")] @@ -701,7 +700,6 @@ fn vmm_thread_rules( (libc::SYS_wait4, vec![]), (libc::SYS_write, vec![]), (libc::SYS_writev, vec![]), - (libc::SYS_getcwd, vec![]), ]) } @@ -801,11 +799,13 @@ fn vcpu_thread_rules( (libc::SYS_dup, vec![]), (libc::SYS_exit, vec![]), (libc::SYS_epoll_ctl, vec![]), + (libc::SYS_fcntl, vec![]), (libc::SYS_fstat, vec![]), - (libc::SYS_gettid, vec![]), (libc::SYS_futex, vec![]), + (libc::SYS_getcwd, vec![]), (libc::SYS_getrandom, vec![]), (libc::SYS_getpid, vec![]), + (libc::SYS_gettid, vec![]), ( libc::SYS_ioctl, create_vcpu_ioctl_seccomp_rule(hypervisor_type)?, @@ -842,8 +842,6 @@ fn vcpu_thread_rules( (libc::SYS_unlinkat, vec![]), (libc::SYS_write, vec![]), (libc::SYS_writev, vec![]), - (libc::SYS_fcntl, vec![]), - (libc::SYS_getcwd, vec![]), ]) } @@ -863,6 +861,7 @@ fn http_api_thread_rules() -> Result)>, BackendError> (libc::SYS_epoll_wait, vec![]), (libc::SYS_exit, vec![]), (libc::SYS_fcntl, vec![]), + (libc::SYS_getcwd, vec![]), (libc::SYS_gettid, vec![]), (libc::SYS_futex, vec![]), (libc::SYS_getrandom, vec![]), @@ -876,12 +875,11 @@ fn http_api_thread_rules() -> Result)>, BackendError> (libc::SYS_prctl, vec![]), (libc::SYS_recvfrom, vec![]), (libc::SYS_recvmsg, vec![]), + (libc::SYS_rt_sigprocmask, vec![]), (libc::SYS_sched_yield, vec![]), (libc::SYS_sendto, vec![]), (libc::SYS_sigaltstack, vec![]), (libc::SYS_write, vec![]), - (libc::SYS_rt_sigprocmask, vec![]), - (libc::SYS_getcwd, vec![]), ]) } @@ -899,7 +897,9 @@ fn dbus_api_thread_rules() -> Result)>, BackendError> (libc::SYS_epoll_ctl, vec![]), (libc::SYS_exit, vec![]), (libc::SYS_gettid, vec![]), + (libc::SYS_fcntl, vec![]), (libc::SYS_futex, vec![]), + (libc::SYS_getcwd, vec![]), (libc::SYS_getrandom, vec![]), (libc::SYS_madvise, vec![]), (libc::SYS_mmap, vec![]), @@ -915,7 +915,6 @@ fn dbus_api_thread_rules() -> Result)>, BackendError> (libc::SYS_set_robust_list, vec![]), (libc::SYS_sigaltstack, vec![]), (libc::SYS_write, vec![]), - (libc::SYS_getcwd, vec![]), ]) } @@ -923,6 +922,7 @@ fn event_monitor_thread_rules() -> Result)>, BackendE Ok(vec![ (libc::SYS_brk, vec![]), (libc::SYS_close, vec![]), + (libc::SYS_getcwd, vec![]), (libc::SYS_gettid, vec![]), (libc::SYS_futex, vec![]), (libc::SYS_landlock_create_ruleset, vec![]), @@ -932,7 +932,6 @@ fn event_monitor_thread_rules() -> Result)>, BackendE (libc::SYS_prctl, vec![]), (libc::SYS_sched_yield, vec![]), (libc::SYS_write, vec![]), - (libc::SYS_getcwd, vec![]), ]) } From e05065f509dedf4c9477ca0a47c12c1c3440efd5 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 19 Mar 2026 17:20:59 -0700 Subject: [PATCH 0836/1893] 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 --- Cargo.lock | 244 ++++++++---------- Cargo.toml | 22 +- arch/src/aarch64/uefi.rs | 2 +- fuzz/Cargo.lock | 170 +++++------- fuzz/Cargo.toml | 12 +- hypervisor/src/kvm/mod.rs | 2 +- pci/src/vfio.rs | 35 +-- vhost_user_block/src/lib.rs | 24 +- vhost_user_net/src/lib.rs | 23 +- virtio-devices/src/console.rs | 2 +- virtio-devices/src/lib.rs | 4 +- virtio-devices/src/rng.rs | 2 +- virtio-devices/src/vhost_user/mod.rs | 3 +- .../src/vhost_user/vu_common_ctrl.rs | 11 +- vmm/src/device_manager.rs | 17 +- vmm/src/memory_manager.rs | 24 +- vmm/src/vm.rs | 6 +- 17 files changed, 272 insertions(+), 331 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c02d2604f..2fb1cfe887 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,7 +95,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -106,7 +106,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -119,7 +119,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" name = "api_client" version = "0.1.0" dependencies = [ - "thiserror 2.0.18", + "thiserror", "vmm-sys-util", ] @@ -144,7 +144,7 @@ dependencies = [ "linux-loader", "log", "serde", - "thiserror 2.0.18", + "thiserror", "uuid", "vm-fdt", "vm-memory", @@ -204,7 +204,7 @@ dependencies = [ "polling", "rustix", "slab", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -262,7 +262,7 @@ dependencies = [ "rustix", "signal-hook-registry", "slab", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -357,7 +357,7 @@ dependencies = [ "remain", "serde", "smallvec", - "thiserror 2.0.18", + "thiserror", "uuid", "virtio-bindings", "virtio-queue", @@ -471,7 +471,7 @@ dependencies = [ "serde_json", "signal-hook", "test_infra", - "thiserror 2.0.18", + "thiserror", "tpm", "tracer", "vm-memory", @@ -597,7 +597,7 @@ dependencies = [ "num_enum", "pci", "serde", - "thiserror 2.0.18", + "thiserror", "tpm", "vm-allocator", "vm-device", @@ -641,7 +641,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -723,7 +723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -764,7 +764,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", ] [[package]] @@ -934,9 +934,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -1040,7 +1040,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.18", + "thiserror", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -1081,7 +1081,7 @@ dependencies = [ "open-enum", "range_map_vec", "static_assertions", - "thiserror 2.0.18", + "thiserror", "tracing", "zerocopy", ] @@ -1147,9 +1147,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" @@ -1187,9 +1187,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -1197,9 +1197,9 @@ dependencies = [ [[package]] name = "kvm-bindings" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a537873e15e8daabb416667e606d9b0abc2a8fb9a45bd5853b888ae0ead82f9" +checksum = "4b3c06ff73c7ce03e780887ec2389d62d2a2a9ddf471ab05c2ff69207cd3f3b4" dependencies = [ "serde", "vmm-sys-util", @@ -1208,9 +1208,9 @@ dependencies = [ [[package]] name = "kvm-ioctls" -version = "0.22.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d" +checksum = "333f77a20344a448f3f70664918135fddeb804e938f28a99d685bd92926e0b19" dependencies = [ "bitflags 2.11.0", "kvm-bindings", @@ -1226,7 +1226,7 @@ checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088" dependencies = [ "enumflags2", "libc", - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -1284,9 +1284,9 @@ dependencies = [ [[package]] name = "linux-loader" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53802c0b111faf302a16fa20a2e3a33bd0eab408f60fc34cbfe052f6b153791e" +checksum = "de72cb02c55ecffcf75fe78295926f872eb6eb0a58d629c58a8c324dc26380f6" dependencies = [ "vm-memory", ] @@ -1336,7 +1336,7 @@ dependencies = [ [[package]] name = "micro_http" version = "0.1.0" -source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#3248ceeae41461d034624b582d5d358cd6e6f89f" +source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#5c2254d6cf4f32a668d0d8e57ba20bebad9d4fba" dependencies = [ "libc", "vmm-sys-util", @@ -1380,7 +1380,7 @@ checksum = "f035616abe1e4cbc026a1a8094ff8d3900f5063fe6608309098bc745926fdfd8" dependencies = [ "libc", "mshv-bindings", - "thiserror 2.0.18", + "thiserror", "vmm-sys-util", ] @@ -1405,7 +1405,7 @@ dependencies = [ "rate_limiter", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", "virtio-bindings", "virtio-queue", "vm-memory", @@ -1523,7 +1523,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" name = "option_parser" version = "0.1.0" dependencies = [ - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -1581,7 +1581,7 @@ dependencies = [ "libc", "log", "serde", - "thiserror 2.0.18", + "thiserror", "vfio-bindings", "vfio-ioctls", "vfio_user", @@ -1603,7 +1603,7 @@ dependencies = [ "serde", "serde_json", "test_infra", - "thiserror 2.0.18", + "thiserror", "vmm-sys-util", ] @@ -1732,7 +1732,7 @@ dependencies = [ "hermit-abi", "pin-project-lite", "rustix", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1829,7 +1829,7 @@ dependencies = [ "epoll", "libc", "log", - "thiserror 2.0.18", + "thiserror", "vmm-sys-util", ] @@ -1848,9 +1848,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -1915,7 +1915,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2123,7 +2123,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2146,38 +2146,18 @@ dependencies = [ "rand", "serde_json", "ssh2", - "thiserror 2.0.18", + "thiserror", "vmm-sys-util", "wait-timeout", ] -[[package]] -name = "thiserror" -version = "1.0.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" -dependencies = [ - "thiserror-impl 1.0.62", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -2199,32 +2179,32 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ "indexmap", "toml_datetime", "toml_parser", - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ - "winnow", + "winnow 1.0.0", ] [[package]] @@ -2235,7 +2215,7 @@ dependencies = [ "libc", "log", "net_gen", - "thiserror 2.0.18", + "thiserror", "vmm-sys-util", ] @@ -2288,7 +2268,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2330,18 +2310,16 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vfio-bindings" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "698c66a4522a31ab407a410a59c9660da036178e4fe3f371825cd6aad7d46837" +version = "0.6.1" +source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" dependencies = [ "vmm-sys-util", ] [[package]] name = "vfio-ioctls" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af7e8d49719333e5eb52209417f26695c9ab2b117a82596a63a44947f97c5d6" +version = "0.5.2" +source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" dependencies = [ "byteorder", "kvm-bindings", @@ -2350,7 +2328,7 @@ dependencies = [ "log", "mshv-bindings", "mshv-ioctls", - "thiserror 2.0.18", + "thiserror", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2358,9 +2336,8 @@ dependencies = [ [[package]] name = "vfio_user" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a" +version = "0.1.2" +source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" dependencies = [ "bitflags 2.11.0", "libc", @@ -2368,7 +2345,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.18", + "thiserror", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -2376,9 +2353,8 @@ dependencies = [ [[package]] name = "vhost" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" +version = "0.15.0" +source = "git+https://github.com/rust-vmm/vhost?rev=c9b80a1c93bac7820e4aee4269aa904568937035#c9b80a1c93bac7820e4aee4269aa904568937035" dependencies = [ "bitflags 2.11.0", "libc", @@ -2389,9 +2365,8 @@ dependencies = [ [[package]] name = "vhost-user-backend" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e183205a9ba7cb9c47fcb0fc0a07fc295a110efbb11ab78ad0d793b0a38a7bde" +version = "0.21.0" +source = "git+https://github.com/rust-vmm/vhost?rev=c9b80a1c93bac7820e4aee4269aa904568937035#c9b80a1c93bac7820e4aee4269aa904568937035" dependencies = [ "libc", "log", @@ -2412,7 +2387,7 @@ dependencies = [ "libc", "log", "option_parser", - "thiserror 2.0.18", + "thiserror", "vhost", "vhost-user-backend", "virtio-bindings", @@ -2432,7 +2407,7 @@ dependencies = [ "log", "net_util", "option_parser", - "thiserror 2.0.18", + "thiserror", "vhost", "vhost-user-backend", "virtio-bindings", @@ -2442,9 +2417,9 @@ dependencies = [ [[package]] name = "virtio-bindings" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f498a26d5a63be7bbb8bdcd3869c3f286c4c4a17108905276454da0caf8cb" +checksum = "091f1f09cfbf2a78563b562e7a949465cce1aef63b6065645188d995162f8868" [[package]] name = "virtio-devices" @@ -2466,7 +2441,7 @@ dependencies = [ "serde", "serde_with", "serial_buffer", - "thiserror 2.0.18", + "thiserror", "vhost", "virtio-bindings", "virtio-queue", @@ -2480,10 +2455,11 @@ dependencies = [ [[package]] name = "virtio-queue" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0479158f863e59323771a1f684d843962f76960b86fecfec2bfa9c8f0f9180" +checksum = "e358084f32ed165fddb41d98ff1b7ff3c08b9611d8d6114a1b422e2e85688baf" dependencies = [ + "libc", "log", "virtio-bindings", "vm-memory", @@ -2505,7 +2481,7 @@ version = "0.1.0" dependencies = [ "hypervisor", "serde", - "thiserror 2.0.18", + "thiserror", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -2519,13 +2495,13 @@ checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23" [[package]] name = "vm-memory" -version = "0.16.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd5e56d48353c5f54ef50bd158a0452fc82f5383da840f7b8efc31695dd3b9d" +checksum = "f39348a049689cabd3377cdd9182bf526ec76a6f823b79903896452e9d7a7380" dependencies = [ "arc-swap", "libc", - "thiserror 1.0.62", + "thiserror", "winapi", ] @@ -2537,7 +2513,7 @@ dependencies = [ "itertools", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", "vm-memory", ] @@ -2589,7 +2565,7 @@ dependencies = [ "serde_json", "serial_buffer", "signal-hook", - "thiserror 2.0.18", + "thiserror", "tracer", "uuid", "vfio-ioctls", @@ -2609,9 +2585,9 @@ dependencies = [ [[package]] name = "vmm-sys-util" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21f366bf22bfba3e868349978766a965cbe628c323d58e026be80b8357ab789" +checksum = "506c62fdf617a5176827c2f9afbcf1be155b03a9b4bf9617a60dbc07e3a1642f" dependencies = [ "bitflags 1.3.2", "libc", @@ -2654,35 +2630,22 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2690,22 +2653,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -2783,9 +2746,9 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] @@ -2864,6 +2827,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -2980,8 +2952,8 @@ dependencies = [ "tracing", "uds_windows", "uuid", - "windows-sys 0.61.0", - "winnow", + "windows-sys 0.61.2", + "winnow 0.7.15", "zbus_macros", "zbus_names", "zvariant", @@ -3009,24 +2981,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "winnow", + "winnow 0.7.15", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", @@ -3076,7 +3048,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow", + "winnow 0.7.15", "zvariant_derive", "zvariant_utils", ] @@ -3104,5 +3076,5 @@ dependencies = [ "quote", "serde", "syn", - "winnow", + "winnow 0.7.15", ] diff --git a/Cargo.toml b/Cargo.toml index b8fc54d9e1..bd2a53cad2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,22 +54,22 @@ resolver = "3" [workspace.dependencies] # rust-vmm crates acpi_tables = "0.2.0" -kvm-bindings = "0.12.1" -kvm-ioctls = "0.22.1" -linux-loader = "0.13.1" +kvm-bindings = "0.14.0" +kvm-ioctls = "0.24.0" +linux-loader = "0.13.2" mshv-bindings = "0.6.7" mshv-ioctls = "0.6.7" 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 = { git = "https://github.com/rust-vmm/vfio", rev = "df861a878168ad71602d8a1945bd3b7acbd22693", default-features = false } +vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", rev = "df861a878168ad71602d8a1945bd3b7acbd22693", default-features = false } +vfio_user = { git = "https://github.com/rust-vmm/vfio", rev = "df861a878168ad71602d8a1945bd3b7acbd22693", default-features = false } +vhost = { git = "https://github.com/rust-vmm/vhost", rev = "c9b80a1c93bac7820e4aee4269aa904568937035", default-features = false } +vhost-user-backend = { git = "https://github.com/rust-vmm/vhost", rev = "c9b80a1c93bac7820e4aee4269aa904568937035", 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 igvm = "0.4.0" diff --git a/arch/src/aarch64/uefi.rs b/arch/src/aarch64/uefi.rs index bd40e36ff0..2ff3a8638f 100644 --- a/arch/src/aarch64/uefi.rs +++ b/arch/src/aarch64/uefi.rs @@ -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)] diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index e03789eaf4..4dfe9600e5 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -100,7 +100,7 @@ dependencies = [ "linux-loader", "log", "serde", - "thiserror 2.0.18", + "thiserror", "uuid", "vm-fdt", "vm-memory", @@ -149,7 +149,7 @@ dependencies = [ "remain", "serde", "smallvec", - "thiserror 2.0.18", + "thiserror", "uuid", "virtio-bindings", "virtio-queue", @@ -352,7 +352,7 @@ dependencies = [ "num_enum", "pci", "serde", - "thiserror 2.0.18", + "thiserror", "tpm", "vm-allocator", "vm-device", @@ -589,7 +589,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.18", + "thiserror", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -646,9 +646,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" @@ -662,9 +662,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -672,9 +672,9 @@ dependencies = [ [[package]] name = "kvm-bindings" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a537873e15e8daabb416667e606d9b0abc2a8fb9a45bd5853b888ae0ead82f9" +checksum = "4b3c06ff73c7ce03e780887ec2389d62d2a2a9ddf471ab05c2ff69207cd3f3b4" dependencies = [ "serde", "vmm-sys-util", @@ -683,9 +683,9 @@ dependencies = [ [[package]] name = "kvm-ioctls" -version = "0.22.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d" +checksum = "333f77a20344a448f3f70664918135fddeb804e938f28a99d685bd92926e0b19" dependencies = [ "bitflags 2.11.0", "kvm-bindings", @@ -701,7 +701,7 @@ checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088" dependencies = [ "enumflags2", "libc", - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -734,9 +734,9 @@ dependencies = [ [[package]] name = "linux-loader" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53802c0b111faf302a16fa20a2e3a33bd0eab408f60fc34cbfe052f6b153791e" +checksum = "de72cb02c55ecffcf75fe78295926f872eb6eb0a58d629c58a8c324dc26380f6" dependencies = [ "vm-memory", ] @@ -771,7 +771,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "micro_http" version = "0.1.0" -source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#3248ceeae41461d034624b582d5d358cd6e6f89f" +source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#5c2254d6cf4f32a668d0d8e57ba20bebad9d4fba" dependencies = [ "libc", "vmm-sys-util", @@ -819,7 +819,7 @@ dependencies = [ "net_gen", "rate_limiter", "serde", - "thiserror 2.0.18", + "thiserror", "virtio-bindings", "virtio-queue", "vm-memory", @@ -894,7 +894,7 @@ dependencies = [ name = "option_parser" version = "0.1.0" dependencies = [ - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -913,7 +913,7 @@ dependencies = [ "libc", "log", "serde", - "thiserror 2.0.18", + "thiserror", "vfio-bindings", "vfio-ioctls", "vfio_user", @@ -1003,7 +1003,7 @@ dependencies = [ "epoll", "libc", "log", - "thiserror 2.0.18", + "thiserror", "vmm-sys-util", ] @@ -1178,33 +1178,13 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" -dependencies = [ - "thiserror-impl 1.0.64", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -1220,18 +1200,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ "indexmap", "toml_datetime", @@ -1241,9 +1221,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ "winnow", ] @@ -1256,7 +1236,7 @@ dependencies = [ "libc", "log", "net_gen", - "thiserror 2.0.18", + "thiserror", "vmm-sys-util", ] @@ -1302,25 +1282,23 @@ dependencies = [ [[package]] name = "vfio-bindings" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "698c66a4522a31ab407a410a59c9660da036178e4fe3f371825cd6aad7d46837" +version = "0.6.1" +source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" dependencies = [ "vmm-sys-util", ] [[package]] name = "vfio-ioctls" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af7e8d49719333e5eb52209417f26695c9ab2b117a82596a63a44947f97c5d6" +version = "0.5.2" +source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" dependencies = [ "byteorder", "kvm-bindings", "kvm-ioctls", "libc", "log", - "thiserror 2.0.18", + "thiserror", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1328,9 +1306,8 @@ dependencies = [ [[package]] name = "vfio_user" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a" +version = "0.1.2" +source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" dependencies = [ "bitflags 2.11.0", "libc", @@ -1338,7 +1315,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.18", + "thiserror", "vfio-bindings", "vm-memory", "vmm-sys-util", @@ -1346,9 +1323,8 @@ dependencies = [ [[package]] name = "vhost" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05" +version = "0.15.0" +source = "git+https://github.com/rust-vmm/vhost?rev=c9b80a1c93bac7820e4aee4269aa904568937035#c9b80a1c93bac7820e4aee4269aa904568937035" dependencies = [ "bitflags 2.11.0", "libc", @@ -1359,9 +1335,9 @@ dependencies = [ [[package]] name = "virtio-bindings" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f498a26d5a63be7bbb8bdcd3869c3f286c4c4a17108905276454da0caf8cb" +checksum = "091f1f09cfbf2a78563b562e7a949465cce1aef63b6065645188d995162f8868" [[package]] name = "virtio-devices" @@ -1382,7 +1358,7 @@ dependencies = [ "serde", "serde_with", "serial_buffer", - "thiserror 2.0.18", + "thiserror", "vhost", "virtio-bindings", "virtio-queue", @@ -1396,10 +1372,11 @@ dependencies = [ [[package]] name = "virtio-queue" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0479158f863e59323771a1f684d843962f76960b86fecfec2bfa9c8f0f9180" +checksum = "e358084f32ed165fddb41d98ff1b7ff3c08b9611d8d6114a1b422e2e85688baf" dependencies = [ + "libc", "log", "virtio-bindings", "vm-memory", @@ -1421,7 +1398,7 @@ version = "0.1.0" dependencies = [ "hypervisor", "serde", - "thiserror 2.0.18", + "thiserror", "vfio-ioctls", "vm-memory", "vmm-sys-util", @@ -1435,13 +1412,13 @@ checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23" [[package]] name = "vm-memory" -version = "0.16.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd5e56d48353c5f54ef50bd158a0452fc82f5383da840f7b8efc31695dd3b9d" +checksum = "f39348a049689cabd3377cdd9182bf526ec76a6f823b79903896452e9d7a7380" dependencies = [ "arc-swap", "libc", - "thiserror 1.0.64", + "thiserror", "winapi", ] @@ -1453,7 +1430,7 @@ dependencies = [ "itertools", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", "vm-memory", ] @@ -1497,7 +1474,7 @@ dependencies = [ "serde_json", "serial_buffer", "signal-hook", - "thiserror 2.0.18", + "thiserror", "tracer", "uuid", "vfio-ioctls", @@ -1516,9 +1493,9 @@ dependencies = [ [[package]] name = "vmm-sys-util" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21f366bf22bfba3e868349978766a965cbe628c323d58e026be80b8357ab789" +checksum = "506c62fdf617a5176827c2f9afbcf1be155b03a9b4bf9617a60dbc07e3a1642f" dependencies = [ "bitflags 1.3.2", "libc", @@ -1552,35 +1529,22 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1588,22 +1552,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -1681,9 +1645,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.15" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" dependencies = [ "memchr", ] @@ -1778,18 +1742,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 88d31a152a..80c59be630 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -19,23 +19,23 @@ pvmemcontrol = [] arbitrary = "1.4.2" block = { path = "../block" } devices = { path = "../devices" } -epoll = "4.3.3" +epoll = "4.4.0" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } libc = "0.2.183" libfuzzer-sys = "0.4.12" -linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] } +linux-loader = { version = "0.13.2", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } -mshv-bindings = "0.6.6" +mshv-bindings = "0.6.7" net_util = { path = "../net_util" } seccompiler = "0.5.0" virtio-devices = { path = "../virtio-devices" } -virtio-queue = "0.16.0" +virtio-queue = "0.17.0" vm-device = { path = "../vm-device" } -vm-memory = "0.16.0" +vm-memory = "0.17.1" vm-migration = { path = "../vm-migration" } vm-virtio = { path = "../vm-virtio" } vmm = { path = "../vmm", features = ["guest_debug"] } -vmm-sys-util = "0.14.0" +vmm-sys-util = "0.15.0" # Prevent this from interfering with workspaces [workspace] diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 1c1abd4b68..d18785fd90 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -2991,7 +2991,7 @@ impl KvmVcpu { let maybe_size = self .fd - .get_nested_state(&mut buffer) + .nested_state(&mut buffer) .map_err(|e| cpu::HypervisorCpuError::GetNestedState(e.into()))?; if let Some(_size) = maybe_size { diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index e46e276aa6..ee4e43e31e 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -1709,10 +1709,8 @@ impl VfioPciDevice { unsafe { self.container.vfio_dma_map( user_memory_region.start, - user_memory_region.mapping.len().try_into().unwrap(), - (user_memory_region.mapping.addr() as usize) - .try_into() - .unwrap(), + user_memory_region.mapping.len(), + user_memory_region.mapping.addr(), ) } .map_err(|e| VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf))?; @@ -1734,7 +1732,7 @@ impl VfioPciDevice { if !self.iommu_attached && let Err(e) = self .container - .vfio_dma_unmap(user_memory_region.start, len.try_into().unwrap()) + .vfio_dma_unmap(user_memory_region.start, len) .map_err(|e| VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf)) { error!( @@ -1892,7 +1890,7 @@ impl PciDevice for VfioPciDevice { if !self.iommu_attached && let Err(e) = self .container - .vfio_dma_unmap(user_memory_region.start, len.try_into().unwrap()) + .vfio_dma_unmap(user_memory_region.start, len) .map_err(|e| { VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf) }) @@ -1950,11 +1948,8 @@ iova 0x{:x}, size 0x{:x}: {}, ", // host_addr points to len bytes of // valid memory that will only be unmapped with munmap(). unsafe { - self.container.vfio_dma_map( - user_memory_region.start, - len.try_into().unwrap(), - (host_addr as usize).try_into().unwrap(), - ) + self.container + .vfio_dma_map(user_memory_region.start, len, host_addr) } .map_err(|e| VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf)) .map_err(|e| { @@ -2069,11 +2064,7 @@ impl ExternalDmaMapping for VfioDmaMapping ExternalDmaMapping for VfioDmaMapping std::result::Result<(), io::Error> { - self.container.vfio_dma_unmap(iova, size).map_err(|e| { - io::Error::other(format!( - "failed to unmap memory for VFIO container, \ + self.container + .vfio_dma_unmap(iova, size as usize) + .map_err(|e| { + io::Error::other(format!( + "failed to unmap memory for VFIO container, \ iova 0x{iova:x}, size 0x{size:x}: {e:?}" - )) - }) + )) + }) } } diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 13668e1cdb..9b0e429ee4 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -12,6 +12,7 @@ use std::fs::{File, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; use std::ops::{Deref, DerefMut}; use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::io::{FromRawFd, IntoRawFd}; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock, RwLockWriteGuard}; @@ -34,6 +35,7 @@ use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use virtio_queue::QueueT; use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic}; use vmm_sys_util::epoll::EventSet; +use vmm_sys_util::event::{EventConsumer, EventNotifier}; use vmm_sys_util::eventfd::EventFd; type GuestMemoryMmap = vm_memory::GuestMemoryMmap; @@ -423,15 +425,15 @@ impl VhostUserBackendMut for VhostUserBlkBackend { Ok(()) } - fn exit_event(&self, thread_index: usize) -> Option { - Some( - self.threads[thread_index] - .lock() - .unwrap() - .kill_evt - .try_clone() - .unwrap(), - ) + fn exit_event(&self, thread_index: usize) -> Option<(EventConsumer, EventNotifier)> { + let kill_evt = &self.threads[thread_index].lock().unwrap().kill_evt; + // SAFETY: kill_evt is a valid eventfd + unsafe { + Some(( + EventConsumer::from_raw_fd(kill_evt.try_clone().unwrap().into_raw_fd()), + EventNotifier::from_raw_fd(kill_evt.try_clone().unwrap().into_raw_fd()), + )) + } } fn queues_per_thread(&self) -> Vec { @@ -533,14 +535,14 @@ pub fn start_block_backend(backend_command: &str) { debug!("blk_backend is created!\n"); - let listener = Listener::new(&backend_config.socket, true).unwrap(); + let mut listener = Listener::new(&backend_config.socket, true).unwrap(); let name = "vhost-user-blk-backend"; let mut blk_daemon = VhostUserDaemon::new(name.to_string(), blk_backend.clone(), mem).unwrap(); debug!("blk_daemon is created!\n"); - if let Err(e) = blk_daemon.start(listener) { + if let Err(e) = blk_daemon.start(&mut listener) { error!("Failed to start daemon for vhost-user-block with error: {e:?}\n"); process::exit(1); } diff --git a/vhost_user_net/src/lib.rs b/vhost_user_net/src/lib.rs index 0e89a763a8..254058abd3 100644 --- a/vhost_user_net/src/lib.rs +++ b/vhost_user_net/src/lib.rs @@ -8,7 +8,7 @@ use std::net::{IpAddr, Ipv4Addr}; use std::ops::Deref; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; use std::sync::{Arc, Mutex, RwLock}; use std::{io, process}; @@ -27,6 +27,7 @@ use virtio_bindings::virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_ use virtio_bindings::virtio_net::*; use vm_memory::{GuestAddressSpace, GuestMemoryAtomic}; use vmm_sys_util::epoll::EventSet; +use vmm_sys_util::event::{EventConsumer, EventNotifier}; use vmm_sys_util::eventfd::EventFd; type GuestMemoryMmap = vm_memory::GuestMemoryMmap; @@ -249,15 +250,15 @@ impl VhostUserBackendMut for VhostUserNetBackend { Ok(()) } - fn exit_event(&self, thread_index: usize) -> Option { - Some( - self.threads[thread_index] - .lock() - .unwrap() - .kill_evt - .try_clone() - .unwrap(), - ) + fn exit_event(&self, thread_index: usize) -> Option<(EventConsumer, EventNotifier)> { + let kill_evt = &self.threads[thread_index].lock().unwrap().kill_evt; + // SAFETY: kill_evt is a valid eventfd + unsafe { + Some(( + EventConsumer::from_raw_fd(kill_evt.try_clone().unwrap().into_raw_fd()), + EventNotifier::from_raw_fd(kill_evt.try_clone().unwrap().into_raw_fd()), + )) + } } fn queues_per_thread(&self) -> Vec { @@ -394,7 +395,7 @@ pub fn start_net_backend(backend_command: &str) { if let Err(e) = if backend_config.client { net_daemon.start_client(&backend_config.socket) } else { - net_daemon.start(Listener::new(&backend_config.socket, true).unwrap()) + net_daemon.start(&mut Listener::new(&backend_config.socket, true).unwrap()) } { error!("failed to start daemon for vhost-user-net with error: {e:?}"); process::exit(1); diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index 74b42c46b5..96282b5228 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize}; use serial_buffer::SerialBuffer; use thiserror::Error; use virtio_queue::{Queue, QueueT}; -use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; +use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic}; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index f69ff5579e..d57673ad34 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -168,9 +168,7 @@ pub fn get_host_address_range( if mem.check_range(addr, size) { let slice = mem.get_slice(addr, size).unwrap(); assert!(slice.len() >= size); - // TODO: return a VolatileSlice and fix all callers. - #[allow(deprecated)] - Some(slice.as_ptr()) + Some(slice.ptr_guard_mut().as_ptr()) } else { None } diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 16a539f923..2133cb79ee 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -17,7 +17,7 @@ use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; use virtio_queue::{Queue, QueueT}; -use vm_memory::{GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; +use vm_memory::{Bytes, GuestAddressSpace, GuestMemoryAtomic}; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 0dad19acea..aca9aba113 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -17,8 +17,9 @@ use vhost::vhost_user::message::{ }; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontendReqHandler}; use virtio_queue::{Error as QueueError, Queue}; +use vm_memory::guest_memory::Error as MmapError; use vm_memory::mmap::MmapRegionError; -use vm_memory::{Address, Error as MmapError, GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; +use vm_memory::{Address, GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{MigratableError, Snapshot}; use vmm_sys_util::eventfd::EventFd; diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 264635149c..b603463fb4 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -21,9 +21,8 @@ use vhost::vhost_user::{ use vhost::{VhostBackend, VhostUserDirtyLogRegion, VhostUserMemoryRegionInfo, VringConfigData}; use virtio_queue::desc::RawDescriptor; use virtio_queue::{Queue, QueueT}; -use vm_memory::{ - Address, Error as MmapError, FileOffset, GuestAddress, GuestMemory, GuestMemoryRegion, -}; +use vm_memory::guest_memory::Error as MmapError; +use vm_memory::{Address, FileOffset, GuestAddress, GuestMemory, GuestMemoryRegion}; use vm_migration::protocol::MemoryRangeTable; use vmm_sys_util::eventfd::EventFd; @@ -67,7 +66,11 @@ impl VhostUserHandle { for region in mem.iter() { let (mmap_handle, mmap_offset) = match region.file_offset() { Some(_file_offset) => (_file_offset.file().as_raw_fd(), _file_offset.start()), - None => return Err(Error::VhostUserMemoryRegion(MmapError::NoMemoryRegion)), + None => { + return Err(Error::VhostUserMemoryRegion( + MmapError::InvalidGuestAddress(region.start_addr()), + )); + } }; let vhost_user_net_reg = VhostUserMemoryRegionInfo { diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 51a5e476a7..50b82772d0 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -91,7 +91,7 @@ use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracer::trace_scoped; -use vfio_ioctls::{VfioContainer, VfioDevice, VfioDeviceFd}; +use vfio_ioctls::{VfioContainer, VfioDevice, VfioDeviceFd, VfioOps}; use virtio_devices::transport::{VirtioPciDevice, VirtioPciDeviceActivator, VirtioTransport}; use virtio_devices::vhost_user::VhostUserConfig; use virtio_devices::{ @@ -3867,8 +3867,11 @@ impl DeviceManager { vfio_container }; - let vfio_device = VfioDevice::new(&device_cfg.path, Arc::clone(&vfio_container)) - .map_err(DeviceManagerError::VfioCreate)?; + let vfio_device = VfioDevice::new( + &device_cfg.path, + Arc::clone(&vfio_container) as Arc, + ) + .map_err(DeviceManagerError::VfioCreate)?; if needs_dma_mapping { // Register DMA mapping in IOMMU. @@ -3884,8 +3887,8 @@ impl DeviceManager { unsafe { vfio_container.vfio_dma_map( region.start_addr().raw_value(), - region.len(), - region.as_ptr() as u64, + region.len() as usize, + region.as_ptr(), ) } .map_err(DeviceManagerError::VfioDmaMap)?; @@ -4519,8 +4522,8 @@ impl DeviceManager { unsafe { vfio_container.vfio_dma_map( new_region.start_addr().raw_value(), - new_region.len(), - new_region.as_ptr() as u64, + new_region.len() as usize, + new_region.as_ptr(), ) } .map_err(DeviceManagerError::UpdateMemoryForVfioPciDevice)?; diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index ba0313b29a..c8f64b15ad 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -35,10 +35,10 @@ use vm_allocator::GsiApic; use vm_allocator::{AddressAllocator, MemorySlotAllocator, SystemAllocator}; use vm_device::BusDevice; use vm_memory::bitmap::AtomicBitmap; -use vm_memory::guest_memory::FileOffset; +use vm_memory::guest_memory::{Error as MmapError, FileOffset}; use vm_memory::mmap::MmapRegionError; use vm_memory::{ - Address, Error as MmapError, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, + Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryError, GuestMemoryRegion, GuestUsize, MmapRegion, ReadVolatile, }; use vm_migration::protocol::{MemoryRange, MemoryRangeTable}; @@ -251,6 +251,10 @@ pub enum Error { #[error("Mmap backed guest memory error")] GuestMemory(#[source] MmapError), + /// Guest region collection error + #[error("Guest region collection error")] + GuestRegionCollection(#[source] vm_memory::GuestRegionCollectionError), + /// Failed to allocate a memory range. #[error("Failed to allocate a memory range")] MemoryRangeAllocation, @@ -1448,7 +1452,7 @@ impl MemoryManager { config.thp, )?; let guest_memory = - GuestMemoryMmap::from_arc_regions(regions).map_err(Error::GuestMemory)?; + GuestMemoryMmap::from_arc_regions(regions).map_err(Error::GuestRegionCollection)?; let boot_guest_memory = guest_memory.clone(); ( GuestAddress(data.start_of_device_area), @@ -1485,8 +1489,8 @@ impl MemoryManager { let (mem_regions, mut memory_zones) = Self::create_memory_regions_from_zones(&ram_regions, &zones, prefault, config.thp)?; - let mut guest_memory = - GuestMemoryMmap::from_arc_regions(mem_regions).map_err(Error::GuestMemory)?; + let mut guest_memory = GuestMemoryMmap::from_arc_regions(mem_regions) + .map_err(Error::GuestRegionCollection)?; let boot_guest_memory = guest_memory.clone(); @@ -1534,7 +1538,7 @@ impl MemoryManager { guest_memory = guest_memory .insert_region(Arc::clone(®ion)) - .map_err(Error::GuestMemory)?; + .map_err(Error::GuestRegionCollection)?; let hotplugged_size = zone.hotplugged_size.unwrap_or(0); let region_size = region.len(); @@ -1961,9 +1965,9 @@ impl MemoryManager { thp, )?; - Ok(Arc::new( - GuestRegionMmap::new(r, start_addr).map_err(Error::GuestMemory)?, - )) + Ok(Arc::new(GuestRegionMmap::new(r, start_addr).ok_or( + Error::GuestMemory(MmapError::InvalidGuestAddress(start_addr)), + )?)) } // Duplicate of `memory_zone_get_align_size` that does not require a `zone` @@ -2024,7 +2028,7 @@ impl MemoryManager { .guest_memory .memory() .insert_region(region) - .map_err(Error::GuestMemory)?; + .map_err(Error::GuestRegionCollection)?; self.guest_memory.lock().unwrap().replace(guest_memory); Ok(()) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 710847fab3..430c9bdc74 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -65,10 +65,10 @@ use thiserror::Error; use tracer::trace_scoped; use vm_device::Bus; #[cfg(feature = "tdx")] +use vm_memory::GuestMemory; +#[cfg(feature = "tdx")] use vm_memory::{Address, ByteValued, GuestMemoryRegion, ReadVolatile}; -use vm_memory::{ - Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, WriteVolatile, -}; +use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, WriteVolatile}; use vm_migration::protocol::{MemoryRangeTable, Request, Response}; use vm_migration::{ Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, snapshot_from_id, From 3a56f20ee156a1c550dae98f5f57e3b3f8deb965 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 22 Mar 2026 18:52:26 +0000 Subject: [PATCH 0837/1893] vmm: seccomp: Add readlink/readlinkat to vcpu filter Now needed for IOMMU group resolution with VFIO. Fixes: #7876 Signed-off-by: Rob Bradford --- vmm/src/seccomp_filters.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 18b8ba097d..d295761518 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -824,6 +824,10 @@ fn vcpu_thread_rules( (libc::SYS_pread64, vec![]), (libc::SYS_pwrite64, vec![]), (libc::SYS_read, vec![]), + #[cfg(target_arch = "x86_64")] + (libc::SYS_readlink, vec![]), + #[cfg(target_arch = "aarch64")] + (libc::SYS_readlinkat, vec![]), (libc::SYS_recvfrom, vec![]), (libc::SYS_recvmsg, vec![]), (libc::SYS_rt_sigaction, vec![]), From 2b28c5b15ac0f9733dcf556695fb1ab98fc0b220 Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Fri, 13 Mar 2026 15:11:14 +0100 Subject: [PATCH 0838/1893] 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` to a `CString` for the `net_util::Tap.if_name` field. On-behalf-of: SAP julian.schindel@sap.com Signed-off-by: Julian Schindel --- Cargo.lock | 9 - Cargo.toml | 1 - fuzz/Cargo.lock | 9 - fuzz/fuzz_targets/net.rs | 2 +- net_gen/Cargo.toml | 13 - net_gen/src/if_tun.rs | 327 ---------- net_gen/src/iff.rs | 1228 -------------------------------------- net_gen/src/inn.rs | 294 --------- net_gen/src/ipv6.rs | 41 -- net_gen/src/lib.rs | 66 -- net_gen/src/sockios.rs | 95 --- net_util/Cargo.toml | 1 - net_util/src/lib.rs | 28 +- net_util/src/open_tap.rs | 6 +- net_util/src/tap.rs | 228 +++---- tpm/Cargo.toml | 1 - tpm/src/emulator.rs | 10 +- 17 files changed, 142 insertions(+), 2217 deletions(-) delete mode 100644 net_gen/Cargo.toml delete mode 100644 net_gen/src/if_tun.rs delete mode 100644 net_gen/src/iff.rs delete mode 100644 net_gen/src/inn.rs delete mode 100644 net_gen/src/ipv6.rs delete mode 100644 net_gen/src/lib.rs delete mode 100644 net_gen/src/sockios.rs diff --git a/Cargo.lock b/Cargo.lock index 2fb1cfe887..048c911371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1384,13 +1384,6 @@ dependencies = [ "vmm-sys-util", ] -[[package]] -name = "net_gen" -version = "0.1.0" -dependencies = [ - "vmm-sys-util", -] - [[package]] name = "net_util" version = "0.1.0" @@ -1399,7 +1392,6 @@ dependencies = [ "getrandom 0.4.2", "libc", "log", - "net_gen", "pnet", "pnet_datalink", "rate_limiter", @@ -2214,7 +2206,6 @@ dependencies = [ "anyhow", "libc", "log", - "net_gen", "thiserror", "vmm-sys-util", ] diff --git a/Cargo.toml b/Cargo.toml index bd2a53cad2..39a79ba815 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ members = [ "devices", "event_monitor", "hypervisor", - "net_gen", "net_util", "option_parser", "pci", diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 4dfe9600e5..475f0b8344 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -801,13 +801,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "net_gen" -version = "0.1.0" -dependencies = [ - "vmm-sys-util", -] - [[package]] name = "net_util" version = "0.1.0" @@ -816,7 +809,6 @@ dependencies = [ "getrandom 0.4.2", "libc", "log", - "net_gen", "rate_limiter", "serde", "thiserror", @@ -1235,7 +1227,6 @@ dependencies = [ "anyhow", "libc", "log", - "net_gen", "thiserror", "vmm-sys-util", ] diff --git a/fuzz/fuzz_targets/net.rs b/fuzz/fuzz_targets/net.rs index efc9605806..55c98bdcfc 100644 --- a/fuzz/fuzz_targets/net.rs +++ b/fuzz/fuzz_targets/net.rs @@ -64,7 +64,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { } let (dummy_tap_frontend, dummy_tap_backend) = create_socketpair().unwrap(); - let if_name = "fuzzer_tap_name".as_bytes().to_vec(); + let if_name = "fuzzer_tap_name"; let tap = net_util::Tap::new_for_fuzzing(dummy_tap_frontend, if_name); let mut net = virtio_devices::Net::new_with_tap( diff --git a/net_gen/Cargo.toml b/net_gen/Cargo.toml deleted file mode 100644 index dfbcee2af9..0000000000 --- a/net_gen/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -authors = ["The Chromium OS Authors"] -edition = "2021" -#edition.workspace = true -name = "net_gen" -rust-version.workspace = true -version = "0.1.0" - -[dependencies] -vmm-sys-util = { workspace = true } - -[lints] -workspace = true diff --git a/net_gen/src/if_tun.rs b/net_gen/src/if_tun.rs deleted file mode 100644 index ab9f327b94..0000000000 --- a/net_gen/src/if_tun.rs +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright © 2021 Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 -// - -// bindgen /usr/include/linux/if_tun.h --no-layout-tests - -/* automatically generated by rust-bindgen 0.58.1 */ - -#[repr(C)] -#[derive(Default)] -pub struct __IncompleteArrayField(::std::marker::PhantomData, [T; 0]); -#[allow(clippy::missing_safety_doc)] -impl __IncompleteArrayField { - #[inline] - pub const fn new() -> Self { - __IncompleteArrayField(::std::marker::PhantomData, []) - } - #[inline] - pub fn as_ptr(&self) -> *const T { - self as *const _ as *const T - } - #[inline] - pub fn as_mut_ptr(&mut self) -> *mut T { - self as *mut _ as *mut T - } - #[inline] - pub unsafe fn as_slice(&self, len: usize) -> &[T] { - ::std::slice::from_raw_parts(self.as_ptr(), len) - } - #[inline] - pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { - ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len) - } -} -impl ::std::fmt::Debug for __IncompleteArrayField { - fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - fmt.write_str("__IncompleteArrayField") - } -} -pub const __BITS_PER_LONG: u32 = 64; -pub const __FD_SETSIZE: u32 = 1024; -pub const ETH_ALEN: u32 = 6; -pub const ETH_TLEN: u32 = 2; -pub const ETH_HLEN: u32 = 14; -pub const ETH_ZLEN: u32 = 60; -pub const ETH_DATA_LEN: u32 = 1500; -pub const ETH_FRAME_LEN: u32 = 1514; -pub const ETH_FCS_LEN: u32 = 4; -pub const ETH_MIN_MTU: u32 = 68; -pub const ETH_MAX_MTU: u32 = 65535; -pub const ETH_P_LOOP: u32 = 96; -pub const ETH_P_PUP: u32 = 512; -pub const ETH_P_PUPAT: u32 = 513; -pub const ETH_P_TSN: u32 = 8944; -pub const ETH_P_ERSPAN2: u32 = 8939; -pub const ETH_P_IP: u32 = 2048; -pub const ETH_P_X25: u32 = 2053; -pub const ETH_P_ARP: u32 = 2054; -pub const ETH_P_BPQ: u32 = 2303; -pub const ETH_P_IEEEPUP: u32 = 2560; -pub const ETH_P_IEEEPUPAT: u32 = 2561; -pub const ETH_P_BATMAN: u32 = 17157; -pub const ETH_P_DEC: u32 = 24576; -pub const ETH_P_DNA_DL: u32 = 24577; -pub const ETH_P_DNA_RC: u32 = 24578; -pub const ETH_P_DNA_RT: u32 = 24579; -pub const ETH_P_LAT: u32 = 24580; -pub const ETH_P_DIAG: u32 = 24581; -pub const ETH_P_CUST: u32 = 24582; -pub const ETH_P_SCA: u32 = 24583; -pub const ETH_P_TEB: u32 = 25944; -pub const ETH_P_RARP: u32 = 32821; -pub const ETH_P_ATALK: u32 = 32923; -pub const ETH_P_AARP: u32 = 33011; -pub const ETH_P_8021Q: u32 = 33024; -pub const ETH_P_ERSPAN: u32 = 35006; -pub const ETH_P_IPX: u32 = 33079; -pub const ETH_P_IPV6: u32 = 34525; -pub const ETH_P_PAUSE: u32 = 34824; -pub const ETH_P_SLOW: u32 = 34825; -pub const ETH_P_WCCP: u32 = 34878; -pub const ETH_P_MPLS_UC: u32 = 34887; -pub const ETH_P_MPLS_MC: u32 = 34888; -pub const ETH_P_ATMMPOA: u32 = 34892; -pub const ETH_P_PPP_DISC: u32 = 34915; -pub const ETH_P_PPP_SES: u32 = 34916; -pub const ETH_P_LINK_CTL: u32 = 34924; -pub const ETH_P_ATMFATE: u32 = 34948; -pub const ETH_P_PAE: u32 = 34958; -pub const ETH_P_AOE: u32 = 34978; -pub const ETH_P_8021AD: u32 = 34984; -pub const ETH_P_802_EX1: u32 = 34997; -pub const ETH_P_PREAUTH: u32 = 35015; -pub const ETH_P_TIPC: u32 = 35018; -pub const ETH_P_LLDP: u32 = 35020; -pub const ETH_P_MRP: u32 = 35043; -pub const ETH_P_MACSEC: u32 = 35045; -pub const ETH_P_8021AH: u32 = 35047; -pub const ETH_P_MVRP: u32 = 35061; -pub const ETH_P_1588: u32 = 35063; -pub const ETH_P_NCSI: u32 = 35064; -pub const ETH_P_PRP: u32 = 35067; -pub const ETH_P_FCOE: u32 = 35078; -pub const ETH_P_IBOE: u32 = 35093; -pub const ETH_P_TDLS: u32 = 35085; -pub const ETH_P_FIP: u32 = 35092; -pub const ETH_P_80221: u32 = 35095; -pub const ETH_P_HSR: u32 = 35119; -pub const ETH_P_NSH: u32 = 35151; -pub const ETH_P_LOOPBACK: u32 = 36864; -pub const ETH_P_QINQ1: u32 = 37120; -pub const ETH_P_QINQ2: u32 = 37376; -pub const ETH_P_QINQ3: u32 = 37632; -pub const ETH_P_EDSA: u32 = 56026; -pub const ETH_P_DSA_8021Q: u32 = 56027; -pub const ETH_P_IFE: u32 = 60734; -pub const ETH_P_AF_IUCV: u32 = 64507; -pub const ETH_P_802_3_MIN: u32 = 1536; -pub const ETH_P_802_3: u32 = 1; -pub const ETH_P_AX25: u32 = 2; -pub const ETH_P_ALL: u32 = 3; -pub const ETH_P_802_2: u32 = 4; -pub const ETH_P_SNAP: u32 = 5; -pub const ETH_P_DDCMP: u32 = 6; -pub const ETH_P_WAN_PPP: u32 = 7; -pub const ETH_P_PPP_MP: u32 = 8; -pub const ETH_P_LOCALTALK: u32 = 9; -pub const ETH_P_CAN: u32 = 12; -pub const ETH_P_CANFD: u32 = 13; -pub const ETH_P_PPPTALK: u32 = 16; -pub const ETH_P_TR_802_2: u32 = 17; -pub const ETH_P_MOBITEX: u32 = 21; -pub const ETH_P_CONTROL: u32 = 22; -pub const ETH_P_IRDA: u32 = 23; -pub const ETH_P_ECONET: u32 = 24; -pub const ETH_P_HDLC: u32 = 25; -pub const ETH_P_ARCNET: u32 = 26; -pub const ETH_P_DSA: u32 = 27; -pub const ETH_P_TRAILER: u32 = 28; -pub const ETH_P_PHONET: u32 = 245; -pub const ETH_P_IEEE802154: u32 = 246; -pub const ETH_P_CAIF: u32 = 247; -pub const ETH_P_XDSA: u32 = 248; -pub const ETH_P_MAP: u32 = 249; -pub const __UAPI_DEF_ETHHDR: u32 = 1; -pub const BPF_LD: u32 = 0; -pub const BPF_LDX: u32 = 1; -pub const BPF_ST: u32 = 2; -pub const BPF_STX: u32 = 3; -pub const BPF_ALU: u32 = 4; -pub const BPF_JMP: u32 = 5; -pub const BPF_RET: u32 = 6; -pub const BPF_MISC: u32 = 7; -pub const BPF_W: u32 = 0; -pub const BPF_H: u32 = 8; -pub const BPF_B: u32 = 16; -pub const BPF_IMM: u32 = 0; -pub const BPF_ABS: u32 = 32; -pub const BPF_IND: u32 = 64; -pub const BPF_MEM: u32 = 96; -pub const BPF_LEN: u32 = 128; -pub const BPF_MSH: u32 = 160; -pub const BPF_ADD: u32 = 0; -pub const BPF_SUB: u32 = 16; -pub const BPF_MUL: u32 = 32; -pub const BPF_DIV: u32 = 48; -pub const BPF_OR: u32 = 64; -pub const BPF_AND: u32 = 80; -pub const BPF_LSH: u32 = 96; -pub const BPF_RSH: u32 = 112; -pub const BPF_NEG: u32 = 128; -pub const BPF_MOD: u32 = 144; -pub const BPF_XOR: u32 = 160; -pub const BPF_JA: u32 = 0; -pub const BPF_JEQ: u32 = 16; -pub const BPF_JGT: u32 = 32; -pub const BPF_JGE: u32 = 48; -pub const BPF_JSET: u32 = 64; -pub const BPF_K: u32 = 0; -pub const BPF_X: u32 = 8; -pub const BPF_MAXINSNS: u32 = 4096; -pub const BPF_MAJOR_VERSION: u32 = 1; -pub const BPF_MINOR_VERSION: u32 = 1; -pub const BPF_A: u32 = 16; -pub const BPF_TAX: u32 = 0; -pub const BPF_TXA: u32 = 128; -pub const BPF_MEMWORDS: u32 = 16; -pub const SKF_AD_OFF: i32 = -4096; -pub const SKF_AD_PROTOCOL: u32 = 0; -pub const SKF_AD_PKTTYPE: u32 = 4; -pub const SKF_AD_IFINDEX: u32 = 8; -pub const SKF_AD_NLATTR: u32 = 12; -pub const SKF_AD_NLATTR_NEST: u32 = 16; -pub const SKF_AD_MARK: u32 = 20; -pub const SKF_AD_QUEUE: u32 = 24; -pub const SKF_AD_HATYPE: u32 = 28; -pub const SKF_AD_RXHASH: u32 = 32; -pub const SKF_AD_CPU: u32 = 36; -pub const SKF_AD_ALU_XOR_X: u32 = 40; -pub const SKF_AD_VLAN_TAG: u32 = 44; -pub const SKF_AD_VLAN_TAG_PRESENT: u32 = 48; -pub const SKF_AD_PAY_OFFSET: u32 = 52; -pub const SKF_AD_RANDOM: u32 = 56; -pub const SKF_AD_VLAN_TPID: u32 = 60; -pub const SKF_AD_MAX: u32 = 64; -pub const SKF_NET_OFF: i32 = -1048576; -pub const SKF_LL_OFF: i32 = -2097152; -pub const BPF_NET_OFF: i32 = -1048576; -pub const BPF_LL_OFF: i32 = -2097152; -pub const TUN_READQ_SIZE: u32 = 500; -pub const TUN_TYPE_MASK: u32 = 15; -pub const IFF_TUN: u32 = 1; -pub const IFF_TAP: u32 = 2; -pub const IFF_NAPI: u32 = 16; -pub const IFF_NAPI_FRAGS: u32 = 32; -pub const IFF_NO_PI: u32 = 4096; -pub const IFF_ONE_QUEUE: u32 = 8192; -pub const IFF_VNET_HDR: u32 = 16384; -pub const IFF_TUN_EXCL: u32 = 32768; -pub const IFF_MULTI_QUEUE: u32 = 256; -pub const IFF_ATTACH_QUEUE: u32 = 512; -pub const IFF_DETACH_QUEUE: u32 = 1024; -pub const IFF_PERSIST: u32 = 2048; -pub const IFF_NOFILTER: u32 = 4096; -pub const TUN_TX_TIMESTAMP: u32 = 1; -pub const TUN_F_CSUM: u32 = 1; -pub const TUN_F_TSO4: u32 = 2; -pub const TUN_F_TSO6: u32 = 4; -pub const TUN_F_TSO_ECN: u32 = 8; -pub const TUN_F_UFO: u32 = 16; -pub const TUN_PKT_STRIP: u32 = 1; -pub const TUN_FLT_ALLMULTI: u32 = 1; -pub type __s8 = ::std::os::raw::c_schar; -pub type __u8 = ::std::os::raw::c_uchar; -pub type __s16 = ::std::os::raw::c_short; -pub type __u16 = ::std::os::raw::c_ushort; -pub type __s32 = ::std::os::raw::c_int; -pub type __u32 = ::std::os::raw::c_uint; -pub type __s64 = ::std::os::raw::c_longlong; -pub type __u64 = ::std::os::raw::c_ulonglong; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __kernel_fd_set { - pub fds_bits: [::std::os::raw::c_ulong; 16usize], -} -pub type __kernel_sighandler_t = - ::std::option::Option; -pub type __kernel_key_t = ::std::os::raw::c_int; -pub type __kernel_mqd_t = ::std::os::raw::c_int; -pub type __kernel_old_uid_t = ::std::os::raw::c_ushort; -pub type __kernel_old_gid_t = ::std::os::raw::c_ushort; -pub type __kernel_old_dev_t = ::std::os::raw::c_ulong; -pub type __kernel_long_t = ::std::os::raw::c_long; -pub type __kernel_ulong_t = ::std::os::raw::c_ulong; -pub type __kernel_ino_t = __kernel_ulong_t; -pub type __kernel_mode_t = ::std::os::raw::c_uint; -pub type __kernel_pid_t = ::std::os::raw::c_int; -pub type __kernel_ipc_pid_t = ::std::os::raw::c_int; -pub type __kernel_uid_t = ::std::os::raw::c_uint; -pub type __kernel_gid_t = ::std::os::raw::c_uint; -pub type __kernel_suseconds_t = __kernel_long_t; -pub type __kernel_daddr_t = ::std::os::raw::c_int; -pub type __kernel_uid32_t = ::std::os::raw::c_uint; -pub type __kernel_gid32_t = ::std::os::raw::c_uint; -pub type __kernel_size_t = __kernel_ulong_t; -pub type __kernel_ssize_t = __kernel_long_t; -pub type __kernel_ptrdiff_t = __kernel_long_t; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __kernel_fsid_t { - pub val: [::std::os::raw::c_int; 2usize], -} -pub type __kernel_off_t = __kernel_long_t; -pub type __kernel_loff_t = ::std::os::raw::c_longlong; -pub type __kernel_old_time_t = __kernel_long_t; -pub type __kernel_time_t = __kernel_long_t; -pub type __kernel_time64_t = ::std::os::raw::c_longlong; -pub type __kernel_clock_t = __kernel_long_t; -pub type __kernel_timer_t = ::std::os::raw::c_int; -pub type __kernel_clockid_t = ::std::os::raw::c_int; -pub type __kernel_caddr_t = *mut ::std::os::raw::c_char; -pub type __kernel_uid16_t = ::std::os::raw::c_ushort; -pub type __kernel_gid16_t = ::std::os::raw::c_ushort; -pub type __le16 = __u16; -pub type __be16 = __u16; -pub type __le32 = __u32; -pub type __be32 = __u32; -pub type __le64 = __u64; -pub type __be64 = __u64; -pub type __sum16 = __u16; -pub type __wsum = __u32; -pub type __poll_t = ::std::os::raw::c_uint; -#[repr(C, packed)] -#[derive(Debug, Copy, Clone)] -pub struct ethhdr { - pub h_dest: [::std::os::raw::c_uchar; 6usize], - pub h_source: [::std::os::raw::c_uchar; 6usize], - pub h_proto: __be16, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct sock_filter { - pub code: __u16, - pub jt: __u8, - pub jf: __u8, - pub k: __u32, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct sock_fprog { - pub len: ::std::os::raw::c_ushort, - pub filter: *mut sock_filter, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct tun_pi { - pub flags: __u16, - pub proto: __be16, -} -#[repr(C)] -#[derive(Debug)] -pub struct tun_filter { - pub flags: __u16, - pub count: __u16, - pub addr: __IncompleteArrayField<[__u8; 6usize]>, -} diff --git a/net_gen/src/iff.rs b/net_gen/src/iff.rs deleted file mode 100644 index 974e01b42e..0000000000 --- a/net_gen/src/iff.rs +++ /dev/null @@ -1,1228 +0,0 @@ -// Copyright © 2021 Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 -// - -// bindgen /usr/include/linux/if.h --no-layout-tests - -/* automatically generated by rust-bindgen 0.58.1 */ - -#[repr(C)] -#[derive(Default)] -pub struct __IncompleteArrayField(::std::marker::PhantomData, [T; 0]); -#[allow(clippy::missing_safety_doc)] -impl __IncompleteArrayField { - #[inline] - pub const fn new() -> Self { - __IncompleteArrayField(::std::marker::PhantomData, []) - } - #[inline] - pub fn as_ptr(&self) -> *const T { - self as *const _ as *const T - } - #[inline] - pub fn as_mut_ptr(&mut self) -> *mut T { - self as *mut _ as *mut T - } - #[inline] - pub unsafe fn as_slice(&self, len: usize) -> &[T] { - ::std::slice::from_raw_parts(self.as_ptr(), len) - } - #[inline] - pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { - ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len) - } -} -impl ::std::fmt::Debug for __IncompleteArrayField { - fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - fmt.write_str("__IncompleteArrayField") - } -} -pub const __UAPI_DEF_IF_IFCONF: u32 = 1; -pub const __UAPI_DEF_IF_IFMAP: u32 = 1; -pub const __UAPI_DEF_IF_IFNAMSIZ: u32 = 1; -pub const __UAPI_DEF_IF_IFREQ: u32 = 1; -pub const __UAPI_DEF_IF_NET_DEVICE_FLAGS: u32 = 1; -pub const __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO: u32 = 1; -pub const __UAPI_DEF_IN_ADDR: u32 = 1; -pub const __UAPI_DEF_IN_IPPROTO: u32 = 1; -pub const __UAPI_DEF_IN_PKTINFO: u32 = 1; -pub const __UAPI_DEF_IP_MREQ: u32 = 1; -pub const __UAPI_DEF_SOCKADDR_IN: u32 = 1; -pub const __UAPI_DEF_IN_CLASS: u32 = 1; -pub const __UAPI_DEF_IN6_ADDR: u32 = 1; -pub const __UAPI_DEF_IN6_ADDR_ALT: u32 = 1; -pub const __UAPI_DEF_SOCKADDR_IN6: u32 = 1; -pub const __UAPI_DEF_IPV6_MREQ: u32 = 1; -pub const __UAPI_DEF_IPPROTO_V6: u32 = 1; -pub const __UAPI_DEF_IPV6_OPTIONS: u32 = 1; -pub const __UAPI_DEF_IN6_PKTINFO: u32 = 1; -pub const __UAPI_DEF_IP6_MTUINFO: u32 = 1; -pub const __UAPI_DEF_SOCKADDR_IPX: u32 = 1; -pub const __UAPI_DEF_IPX_ROUTE_DEFINITION: u32 = 1; -pub const __UAPI_DEF_IPX_INTERFACE_DEFINITION: u32 = 1; -pub const __UAPI_DEF_IPX_CONFIG_DATA: u32 = 1; -pub const __UAPI_DEF_IPX_ROUTE_DEF: u32 = 1; -pub const __UAPI_DEF_XATTR: u32 = 1; -pub const __BITS_PER_LONG: u32 = 64; -pub const __FD_SETSIZE: u32 = 1024; -pub const _K_SS_MAXSIZE: u32 = 128; -pub const _SYS_SOCKET_H: u32 = 1; -pub const _FEATURES_H: u32 = 1; -pub const _DEFAULT_SOURCE: u32 = 1; -pub const __GLIBC_USE_ISOC2X: u32 = 0; -pub const __USE_ISOC11: u32 = 1; -pub const __USE_ISOC99: u32 = 1; -pub const __USE_ISOC95: u32 = 1; -pub const __USE_POSIX_IMPLICITLY: u32 = 1; -pub const _POSIX_SOURCE: u32 = 1; -pub const _POSIX_C_SOURCE: u32 = 200809; -pub const __USE_POSIX: u32 = 1; -pub const __USE_POSIX2: u32 = 1; -pub const __USE_POSIX199309: u32 = 1; -pub const __USE_POSIX199506: u32 = 1; -pub const __USE_XOPEN2K: u32 = 1; -pub const __USE_XOPEN2K8: u32 = 1; -pub const _ATFILE_SOURCE: u32 = 1; -pub const __USE_MISC: u32 = 1; -pub const __USE_ATFILE: u32 = 1; -pub const __USE_FORTIFY_LEVEL: u32 = 0; -pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0; -pub const __GLIBC_USE_DEPRECATED_SCANF: u32 = 0; -pub const _STDC_PREDEF_H: u32 = 1; -pub const __STDC_IEC_559__: u32 = 1; -pub const __STDC_IEC_559_COMPLEX__: u32 = 1; -pub const __STDC_ISO_10646__: u32 = 201706; -pub const __GNU_LIBRARY__: u32 = 6; -pub const __GLIBC__: u32 = 2; -pub const __GLIBC_MINOR__: u32 = 32; -pub const _SYS_CDEFS_H: u32 = 1; -pub const __glibc_c99_flexarr_available: u32 = 1; -pub const __WORDSIZE: u32 = 64; -pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1; -pub const __SYSCALL_WORDSIZE: u32 = 64; -pub const __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI: u32 = 0; -pub const __HAVE_GENERIC_SELECTION: u32 = 1; -pub const __iovec_defined: u32 = 1; -pub const _SYS_TYPES_H: u32 = 1; -pub const _BITS_TYPES_H: u32 = 1; -pub const __TIMESIZE: u32 = 64; -pub const _BITS_TYPESIZES_H: u32 = 1; -pub const __OFF_T_MATCHES_OFF64_T: u32 = 1; -pub const __INO_T_MATCHES_INO64_T: u32 = 1; -pub const __RLIM_T_MATCHES_RLIM64_T: u32 = 1; -pub const __STATFS_MATCHES_STATFS64: u32 = 1; -pub const __KERNEL_OLD_TIMEVAL_MATCHES_TIMEVAL64: u32 = 1; -pub const _BITS_TIME64_H: u32 = 1; -pub const __clock_t_defined: u32 = 1; -pub const __clockid_t_defined: u32 = 1; -pub const __time_t_defined: u32 = 1; -pub const __timer_t_defined: u32 = 1; -pub const _BITS_STDINT_INTN_H: u32 = 1; -pub const __BIT_TYPES_DEFINED__: u32 = 1; -pub const _ENDIAN_H: u32 = 1; -pub const _BITS_ENDIAN_H: u32 = 1; -pub const __LITTLE_ENDIAN: u32 = 1234; -pub const __BIG_ENDIAN: u32 = 4321; -pub const __PDP_ENDIAN: u32 = 3412; -pub const _BITS_ENDIANNESS_H: u32 = 1; -pub const __BYTE_ORDER: u32 = 1234; -pub const __FLOAT_WORD_ORDER: u32 = 1234; -pub const LITTLE_ENDIAN: u32 = 1234; -pub const BIG_ENDIAN: u32 = 4321; -pub const PDP_ENDIAN: u32 = 3412; -pub const BYTE_ORDER: u32 = 1234; -pub const _BITS_BYTESWAP_H: u32 = 1; -pub const _BITS_UINTN_IDENTITY_H: u32 = 1; -pub const _SYS_SELECT_H: u32 = 1; -pub const __sigset_t_defined: u32 = 1; -pub const __timeval_defined: u32 = 1; -pub const _STRUCT_TIMESPEC: u32 = 1; -pub const FD_SETSIZE: u32 = 1024; -pub const _BITS_PTHREADTYPES_COMMON_H: u32 = 1; -pub const _THREAD_SHARED_TYPES_H: u32 = 1; -pub const _BITS_PTHREADTYPES_ARCH_H: u32 = 1; -pub const __SIZEOF_PTHREAD_MUTEX_T: u32 = 40; -pub const __SIZEOF_PTHREAD_ATTR_T: u32 = 56; -pub const __SIZEOF_PTHREAD_RWLOCK_T: u32 = 56; -pub const __SIZEOF_PTHREAD_BARRIER_T: u32 = 32; -pub const __SIZEOF_PTHREAD_MUTEXATTR_T: u32 = 4; -pub const __SIZEOF_PTHREAD_COND_T: u32 = 48; -pub const __SIZEOF_PTHREAD_CONDATTR_T: u32 = 4; -pub const __SIZEOF_PTHREAD_RWLOCKATTR_T: u32 = 8; -pub const __SIZEOF_PTHREAD_BARRIERATTR_T: u32 = 4; -pub const _THREAD_MUTEX_INTERNAL_H: u32 = 1; -pub const __PTHREAD_MUTEX_HAVE_PREV: u32 = 1; -pub const __have_pthread_attr_t: u32 = 1; -pub const PF_UNSPEC: u32 = 0; -pub const PF_LOCAL: u32 = 1; -pub const PF_UNIX: u32 = 1; -pub const PF_FILE: u32 = 1; -pub const PF_INET: u32 = 2; -pub const PF_AX25: u32 = 3; -pub const PF_IPX: u32 = 4; -pub const PF_APPLETALK: u32 = 5; -pub const PF_NETROM: u32 = 6; -pub const PF_BRIDGE: u32 = 7; -pub const PF_ATMPVC: u32 = 8; -pub const PF_X25: u32 = 9; -pub const PF_INET6: u32 = 10; -pub const PF_ROSE: u32 = 11; -pub const PF_DECnet: u32 = 12; -pub const PF_NETBEUI: u32 = 13; -pub const PF_SECURITY: u32 = 14; -pub const PF_KEY: u32 = 15; -pub const PF_NETLINK: u32 = 16; -pub const PF_ROUTE: u32 = 16; -pub const PF_PACKET: u32 = 17; -pub const PF_ASH: u32 = 18; -pub const PF_ECONET: u32 = 19; -pub const PF_ATMSVC: u32 = 20; -pub const PF_RDS: u32 = 21; -pub const PF_SNA: u32 = 22; -pub const PF_IRDA: u32 = 23; -pub const PF_PPPOX: u32 = 24; -pub const PF_WANPIPE: u32 = 25; -pub const PF_LLC: u32 = 26; -pub const PF_IB: u32 = 27; -pub const PF_MPLS: u32 = 28; -pub const PF_CAN: u32 = 29; -pub const PF_TIPC: u32 = 30; -pub const PF_BLUETOOTH: u32 = 31; -pub const PF_IUCV: u32 = 32; -pub const PF_RXRPC: u32 = 33; -pub const PF_ISDN: u32 = 34; -pub const PF_PHONET: u32 = 35; -pub const PF_IEEE802154: u32 = 36; -pub const PF_CAIF: u32 = 37; -pub const PF_ALG: u32 = 38; -pub const PF_NFC: u32 = 39; -pub const PF_VSOCK: u32 = 40; -pub const PF_KCM: u32 = 41; -pub const PF_QIPCRTR: u32 = 42; -pub const PF_SMC: u32 = 43; -pub const PF_XDP: u32 = 44; -pub const PF_MAX: u32 = 45; -pub const AF_UNSPEC: u32 = 0; -pub const AF_LOCAL: u32 = 1; -pub const AF_UNIX: u32 = 1; -pub const AF_FILE: u32 = 1; -pub const AF_INET: u32 = 2; -pub const AF_AX25: u32 = 3; -pub const AF_IPX: u32 = 4; -pub const AF_APPLETALK: u32 = 5; -pub const AF_NETROM: u32 = 6; -pub const AF_BRIDGE: u32 = 7; -pub const AF_ATMPVC: u32 = 8; -pub const AF_X25: u32 = 9; -pub const AF_INET6: u32 = 10; -pub const AF_ROSE: u32 = 11; -pub const AF_DECnet: u32 = 12; -pub const AF_NETBEUI: u32 = 13; -pub const AF_SECURITY: u32 = 14; -pub const AF_KEY: u32 = 15; -pub const AF_NETLINK: u32 = 16; -pub const AF_ROUTE: u32 = 16; -pub const AF_PACKET: u32 = 17; -pub const AF_ASH: u32 = 18; -pub const AF_ECONET: u32 = 19; -pub const AF_ATMSVC: u32 = 20; -pub const AF_RDS: u32 = 21; -pub const AF_SNA: u32 = 22; -pub const AF_IRDA: u32 = 23; -pub const AF_PPPOX: u32 = 24; -pub const AF_WANPIPE: u32 = 25; -pub const AF_LLC: u32 = 26; -pub const AF_IB: u32 = 27; -pub const AF_MPLS: u32 = 28; -pub const AF_CAN: u32 = 29; -pub const AF_TIPC: u32 = 30; -pub const AF_BLUETOOTH: u32 = 31; -pub const AF_IUCV: u32 = 32; -pub const AF_RXRPC: u32 = 33; -pub const AF_ISDN: u32 = 34; -pub const AF_PHONET: u32 = 35; -pub const AF_IEEE802154: u32 = 36; -pub const AF_CAIF: u32 = 37; -pub const AF_ALG: u32 = 38; -pub const AF_NFC: u32 = 39; -pub const AF_VSOCK: u32 = 40; -pub const AF_KCM: u32 = 41; -pub const AF_QIPCRTR: u32 = 42; -pub const AF_SMC: u32 = 43; -pub const AF_XDP: u32 = 44; -pub const AF_MAX: u32 = 45; -pub const SOL_RAW: u32 = 255; -pub const SOL_DECNET: u32 = 261; -pub const SOL_X25: u32 = 262; -pub const SOL_PACKET: u32 = 263; -pub const SOL_ATM: u32 = 264; -pub const SOL_AAL: u32 = 265; -pub const SOL_IRDA: u32 = 266; -pub const SOL_NETBEUI: u32 = 267; -pub const SOL_LLC: u32 = 268; -pub const SOL_DCCP: u32 = 269; -pub const SOL_NETLINK: u32 = 270; -pub const SOL_TIPC: u32 = 271; -pub const SOL_RXRPC: u32 = 272; -pub const SOL_PPPOL2TP: u32 = 273; -pub const SOL_BLUETOOTH: u32 = 274; -pub const SOL_PNPIPE: u32 = 275; -pub const SOL_RDS: u32 = 276; -pub const SOL_IUCV: u32 = 277; -pub const SOL_CAIF: u32 = 278; -pub const SOL_ALG: u32 = 279; -pub const SOL_NFC: u32 = 280; -pub const SOL_KCM: u32 = 281; -pub const SOL_TLS: u32 = 282; -pub const SOL_XDP: u32 = 283; -pub const SOMAXCONN: u32 = 4096; -pub const _BITS_SOCKADDR_H: u32 = 1; -pub const _SS_SIZE: u32 = 128; -pub const FIOSETOWN: u32 = 35073; -pub const SIOCSPGRP: u32 = 35074; -pub const FIOGETOWN: u32 = 35075; -pub const SIOCGPGRP: u32 = 35076; -pub const SIOCATMARK: u32 = 35077; -pub const SIOCGSTAMP_OLD: u32 = 35078; -pub const SIOCGSTAMPNS_OLD: u32 = 35079; -pub const SOL_SOCKET: u32 = 1; -pub const SO_DEBUG: u32 = 1; -pub const SO_REUSEADDR: u32 = 2; -pub const SO_TYPE: u32 = 3; -pub const SO_ERROR: u32 = 4; -pub const SO_DONTROUTE: u32 = 5; -pub const SO_BROADCAST: u32 = 6; -pub const SO_SNDBUF: u32 = 7; -pub const SO_RCVBUF: u32 = 8; -pub const SO_SNDBUFFORCE: u32 = 32; -pub const SO_RCVBUFFORCE: u32 = 33; -pub const SO_KEEPALIVE: u32 = 9; -pub const SO_OOBINLINE: u32 = 10; -pub const SO_NO_CHECK: u32 = 11; -pub const SO_PRIORITY: u32 = 12; -pub const SO_LINGER: u32 = 13; -pub const SO_BSDCOMPAT: u32 = 14; -pub const SO_REUSEPORT: u32 = 15; -pub const SO_PASSCRED: u32 = 16; -pub const SO_PEERCRED: u32 = 17; -pub const SO_RCVLOWAT: u32 = 18; -pub const SO_SNDLOWAT: u32 = 19; -pub const SO_RCVTIMEO_OLD: u32 = 20; -pub const SO_SNDTIMEO_OLD: u32 = 21; -pub const SO_SECURITY_AUTHENTICATION: u32 = 22; -pub const SO_SECURITY_ENCRYPTION_TRANSPORT: u32 = 23; -pub const SO_SECURITY_ENCRYPTION_NETWORK: u32 = 24; -pub const SO_BINDTODEVICE: u32 = 25; -pub const SO_ATTACH_FILTER: u32 = 26; -pub const SO_DETACH_FILTER: u32 = 27; -pub const SO_GET_FILTER: u32 = 26; -pub const SO_PEERNAME: u32 = 28; -pub const SO_ACCEPTCONN: u32 = 30; -pub const SO_PEERSEC: u32 = 31; -pub const SO_PASSSEC: u32 = 34; -pub const SO_MARK: u32 = 36; -pub const SO_PROTOCOL: u32 = 38; -pub const SO_DOMAIN: u32 = 39; -pub const SO_RXQ_OVFL: u32 = 40; -pub const SO_WIFI_STATUS: u32 = 41; -pub const SCM_WIFI_STATUS: u32 = 41; -pub const SO_PEEK_OFF: u32 = 42; -pub const SO_NOFCS: u32 = 43; -pub const SO_LOCK_FILTER: u32 = 44; -pub const SO_SELECT_ERR_QUEUE: u32 = 45; -pub const SO_BUSY_POLL: u32 = 46; -pub const SO_MAX_PACING_RATE: u32 = 47; -pub const SO_BPF_EXTENSIONS: u32 = 48; -pub const SO_INCOMING_CPU: u32 = 49; -pub const SO_ATTACH_BPF: u32 = 50; -pub const SO_DETACH_BPF: u32 = 27; -pub const SO_ATTACH_REUSEPORT_CBPF: u32 = 51; -pub const SO_ATTACH_REUSEPORT_EBPF: u32 = 52; -pub const SO_CNX_ADVICE: u32 = 53; -pub const SCM_TIMESTAMPING_OPT_STATS: u32 = 54; -pub const SO_MEMINFO: u32 = 55; -pub const SO_INCOMING_NAPI_ID: u32 = 56; -pub const SO_COOKIE: u32 = 57; -pub const SCM_TIMESTAMPING_PKTINFO: u32 = 58; -pub const SO_PEERGROUPS: u32 = 59; -pub const SO_ZEROCOPY: u32 = 60; -pub const SO_TXTIME: u32 = 61; -pub const SCM_TXTIME: u32 = 61; -pub const SO_BINDTOIFINDEX: u32 = 62; -pub const SO_TIMESTAMP_OLD: u32 = 29; -pub const SO_TIMESTAMPNS_OLD: u32 = 35; -pub const SO_TIMESTAMPING_OLD: u32 = 37; -pub const SO_TIMESTAMP_NEW: u32 = 63; -pub const SO_TIMESTAMPNS_NEW: u32 = 64; -pub const SO_TIMESTAMPING_NEW: u32 = 65; -pub const SO_RCVTIMEO_NEW: u32 = 66; -pub const SO_SNDTIMEO_NEW: u32 = 67; -pub const SO_DETACH_REUSEPORT_BPF: u32 = 68; -pub const SO_TIMESTAMP: u32 = 29; -pub const SO_TIMESTAMPNS: u32 = 35; -pub const SO_TIMESTAMPING: u32 = 37; -pub const SO_RCVTIMEO: u32 = 20; -pub const SO_SNDTIMEO: u32 = 21; -pub const SCM_TIMESTAMP: u32 = 29; -pub const SCM_TIMESTAMPNS: u32 = 35; -pub const SCM_TIMESTAMPING: u32 = 37; -pub const __osockaddr_defined: u32 = 1; -pub const IFNAMSIZ: u32 = 16; -pub const IFALIASZ: u32 = 256; -pub const ALTIFNAMSIZ: u32 = 128; -pub const GENERIC_HDLC_VERSION: u32 = 4; -pub const CLOCK_DEFAULT: u32 = 0; -pub const CLOCK_EXT: u32 = 1; -pub const CLOCK_INT: u32 = 2; -pub const CLOCK_TXINT: u32 = 3; -pub const CLOCK_TXFROMRX: u32 = 4; -pub const ENCODING_DEFAULT: u32 = 0; -pub const ENCODING_NRZ: u32 = 1; -pub const ENCODING_NRZI: u32 = 2; -pub const ENCODING_FM_MARK: u32 = 3; -pub const ENCODING_FM_SPACE: u32 = 4; -pub const ENCODING_MANCHESTER: u32 = 5; -pub const PARITY_DEFAULT: u32 = 0; -pub const PARITY_NONE: u32 = 1; -pub const PARITY_CRC16_PR0: u32 = 2; -pub const PARITY_CRC16_PR1: u32 = 3; -pub const PARITY_CRC16_PR0_CCITT: u32 = 4; -pub const PARITY_CRC16_PR1_CCITT: u32 = 5; -pub const PARITY_CRC32_PR0_CCITT: u32 = 6; -pub const PARITY_CRC32_PR1_CCITT: u32 = 7; -pub const LMI_DEFAULT: u32 = 0; -pub const LMI_NONE: u32 = 1; -pub const LMI_ANSI: u32 = 2; -pub const LMI_CCITT: u32 = 3; -pub const LMI_CISCO: u32 = 4; -pub const IF_GET_IFACE: u32 = 1; -pub const IF_GET_PROTO: u32 = 2; -pub const IF_IFACE_V35: u32 = 4096; -pub const IF_IFACE_V24: u32 = 4097; -pub const IF_IFACE_X21: u32 = 4098; -pub const IF_IFACE_T1: u32 = 4099; -pub const IF_IFACE_E1: u32 = 4100; -pub const IF_IFACE_SYNC_SERIAL: u32 = 4101; -pub const IF_IFACE_X21D: u32 = 4102; -pub const IF_PROTO_HDLC: u32 = 8192; -pub const IF_PROTO_PPP: u32 = 8193; -pub const IF_PROTO_CISCO: u32 = 8194; -pub const IF_PROTO_FR: u32 = 8195; -pub const IF_PROTO_FR_ADD_PVC: u32 = 8196; -pub const IF_PROTO_FR_DEL_PVC: u32 = 8197; -pub const IF_PROTO_X25: u32 = 8198; -pub const IF_PROTO_HDLC_ETH: u32 = 8199; -pub const IF_PROTO_FR_ADD_ETH_PVC: u32 = 8200; -pub const IF_PROTO_FR_DEL_ETH_PVC: u32 = 8201; -pub const IF_PROTO_FR_PVC: u32 = 8202; -pub const IF_PROTO_FR_ETH_PVC: u32 = 8203; -pub const IF_PROTO_RAW: u32 = 8204; -pub const IFHWADDRLEN: u32 = 6; -pub type __s8 = ::std::os::raw::c_schar; -pub type __u8 = ::std::os::raw::c_uchar; -pub type __s16 = ::std::os::raw::c_short; -pub type __u16 = ::std::os::raw::c_ushort; -pub type __s32 = ::std::os::raw::c_int; -pub type __u32 = ::std::os::raw::c_uint; -pub type __s64 = ::std::os::raw::c_longlong; -pub type __u64 = ::std::os::raw::c_ulonglong; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __kernel_fd_set { - pub fds_bits: [::std::os::raw::c_ulong; 16usize], -} -pub type __kernel_sighandler_t = - ::std::option::Option; -pub type __kernel_key_t = ::std::os::raw::c_int; -pub type __kernel_mqd_t = ::std::os::raw::c_int; -pub type __kernel_old_uid_t = ::std::os::raw::c_ushort; -pub type __kernel_old_gid_t = ::std::os::raw::c_ushort; -pub type __kernel_old_dev_t = ::std::os::raw::c_ulong; -pub type __kernel_long_t = ::std::os::raw::c_long; -pub type __kernel_ulong_t = ::std::os::raw::c_ulong; -pub type __kernel_ino_t = __kernel_ulong_t; -pub type __kernel_mode_t = ::std::os::raw::c_uint; -pub type __kernel_pid_t = ::std::os::raw::c_int; -pub type __kernel_ipc_pid_t = ::std::os::raw::c_int; -pub type __kernel_uid_t = ::std::os::raw::c_uint; -pub type __kernel_gid_t = ::std::os::raw::c_uint; -pub type __kernel_suseconds_t = __kernel_long_t; -pub type __kernel_daddr_t = ::std::os::raw::c_int; -pub type __kernel_uid32_t = ::std::os::raw::c_uint; -pub type __kernel_gid32_t = ::std::os::raw::c_uint; -pub type __kernel_size_t = __kernel_ulong_t; -pub type __kernel_ssize_t = __kernel_long_t; -pub type __kernel_ptrdiff_t = __kernel_long_t; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __kernel_fsid_t { - pub val: [::std::os::raw::c_int; 2usize], -} -pub type __kernel_off_t = __kernel_long_t; -pub type __kernel_loff_t = ::std::os::raw::c_longlong; -pub type __kernel_old_time_t = __kernel_long_t; -pub type __kernel_time_t = __kernel_long_t; -pub type __kernel_time64_t = ::std::os::raw::c_longlong; -pub type __kernel_clock_t = __kernel_long_t; -pub type __kernel_timer_t = ::std::os::raw::c_int; -pub type __kernel_clockid_t = ::std::os::raw::c_int; -pub type __kernel_caddr_t = *mut ::std::os::raw::c_char; -pub type __kernel_uid16_t = ::std::os::raw::c_ushort; -pub type __kernel_gid16_t = ::std::os::raw::c_ushort; -pub type __le16 = __u16; -pub type __be16 = __u16; -pub type __le32 = __u32; -pub type __be32 = __u32; -pub type __le64 = __u64; -pub type __be64 = __u64; -pub type __sum16 = __u16; -pub type __wsum = __u32; -pub type __poll_t = ::std::os::raw::c_uint; -pub type __kernel_sa_family_t = ::std::os::raw::c_ushort; -#[repr(C)] -#[derive(Copy, Clone)] -pub struct __kernel_sockaddr_storage { - pub __bindgen_anon_1: __kernel_sockaddr_storage__bindgen_ty_1, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union __kernel_sockaddr_storage__bindgen_ty_1 { - pub __bindgen_anon_1: __kernel_sockaddr_storage__bindgen_ty_1__bindgen_ty_1, - pub __align: *mut ::std::os::raw::c_void, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct __kernel_sockaddr_storage__bindgen_ty_1__bindgen_ty_1 { - pub ss_family: __kernel_sa_family_t, - pub __data: [::std::os::raw::c_char; 126usize], -} -pub type size_t = ::std::os::raw::c_ulong; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct iovec { - pub iov_base: *mut ::std::os::raw::c_void, - pub iov_len: size_t, -} -pub type __u_char = ::std::os::raw::c_uchar; -pub type __u_short = ::std::os::raw::c_ushort; -pub type __u_int = ::std::os::raw::c_uint; -pub type __u_long = ::std::os::raw::c_ulong; -pub type __int8_t = ::std::os::raw::c_schar; -pub type __uint8_t = ::std::os::raw::c_uchar; -pub type __int16_t = ::std::os::raw::c_short; -pub type __uint16_t = ::std::os::raw::c_ushort; -pub type __int32_t = ::std::os::raw::c_int; -pub type __uint32_t = ::std::os::raw::c_uint; -pub type __int64_t = ::std::os::raw::c_long; -pub type __uint64_t = ::std::os::raw::c_ulong; -pub type __int_least8_t = __int8_t; -pub type __uint_least8_t = __uint8_t; -pub type __int_least16_t = __int16_t; -pub type __uint_least16_t = __uint16_t; -pub type __int_least32_t = __int32_t; -pub type __uint_least32_t = __uint32_t; -pub type __int_least64_t = __int64_t; -pub type __uint_least64_t = __uint64_t; -pub type __quad_t = ::std::os::raw::c_long; -pub type __u_quad_t = ::std::os::raw::c_ulong; -pub type __intmax_t = ::std::os::raw::c_long; -pub type __uintmax_t = ::std::os::raw::c_ulong; -pub type __dev_t = ::std::os::raw::c_ulong; -pub type __uid_t = ::std::os::raw::c_uint; -pub type __gid_t = ::std::os::raw::c_uint; -pub type __ino_t = ::std::os::raw::c_ulong; -pub type __ino64_t = ::std::os::raw::c_ulong; -pub type __mode_t = ::std::os::raw::c_uint; -pub type __nlink_t = ::std::os::raw::c_ulong; -pub type __off_t = ::std::os::raw::c_long; -pub type __off64_t = ::std::os::raw::c_long; -pub type __pid_t = ::std::os::raw::c_int; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __fsid_t { - pub __val: [::std::os::raw::c_int; 2usize], -} -pub type __clock_t = ::std::os::raw::c_long; -pub type __rlim_t = ::std::os::raw::c_ulong; -pub type __rlim64_t = ::std::os::raw::c_ulong; -pub type __id_t = ::std::os::raw::c_uint; -pub type __time_t = ::std::os::raw::c_long; -pub type __useconds_t = ::std::os::raw::c_uint; -pub type __suseconds_t = ::std::os::raw::c_long; -pub type __suseconds64_t = ::std::os::raw::c_long; -pub type __daddr_t = ::std::os::raw::c_int; -pub type __key_t = ::std::os::raw::c_int; -pub type __clockid_t = ::std::os::raw::c_int; -pub type __timer_t = *mut ::std::os::raw::c_void; -pub type __blksize_t = ::std::os::raw::c_long; -pub type __blkcnt_t = ::std::os::raw::c_long; -pub type __blkcnt64_t = ::std::os::raw::c_long; -pub type __fsblkcnt_t = ::std::os::raw::c_ulong; -pub type __fsblkcnt64_t = ::std::os::raw::c_ulong; -pub type __fsfilcnt_t = ::std::os::raw::c_ulong; -pub type __fsfilcnt64_t = ::std::os::raw::c_ulong; -pub type __fsword_t = ::std::os::raw::c_long; -pub type __ssize_t = ::std::os::raw::c_long; -pub type __syscall_slong_t = ::std::os::raw::c_long; -pub type __syscall_ulong_t = ::std::os::raw::c_ulong; -pub type __loff_t = __off64_t; -pub type __caddr_t = *mut ::std::os::raw::c_char; -pub type __intptr_t = ::std::os::raw::c_long; -pub type __socklen_t = ::std::os::raw::c_uint; -pub type __sig_atomic_t = ::std::os::raw::c_int; -pub type u_char = __u_char; -pub type u_short = __u_short; -pub type u_int = __u_int; -pub type u_long = __u_long; -pub type quad_t = __quad_t; -pub type u_quad_t = __u_quad_t; -pub type fsid_t = __fsid_t; -pub type loff_t = __loff_t; -pub type ino_t = __ino_t; -pub type dev_t = __dev_t; -pub type gid_t = __gid_t; -pub type mode_t = __mode_t; -pub type nlink_t = __nlink_t; -pub type uid_t = __uid_t; -pub type off_t = __off_t; -pub type pid_t = __pid_t; -pub type id_t = __id_t; -pub type ssize_t = __ssize_t; -pub type daddr_t = __daddr_t; -pub type caddr_t = __caddr_t; -pub type key_t = __key_t; -pub type clock_t = __clock_t; -pub type clockid_t = __clockid_t; -pub type time_t = __time_t; -pub type timer_t = __timer_t; -pub type ulong = ::std::os::raw::c_ulong; -pub type ushort = ::std::os::raw::c_ushort; -pub type uint = ::std::os::raw::c_uint; -pub type u_int8_t = __uint8_t; -pub type u_int16_t = __uint16_t; -pub type u_int32_t = __uint32_t; -pub type u_int64_t = __uint64_t; -pub type register_t = ::std::os::raw::c_long; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __sigset_t { - pub __val: [::std::os::raw::c_ulong; 16usize], -} -pub type sigset_t = __sigset_t; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct timeval { - pub tv_sec: __time_t, - pub tv_usec: __suseconds_t, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct timespec { - pub tv_sec: __time_t, - pub tv_nsec: __syscall_slong_t, -} -pub type suseconds_t = __suseconds_t; -pub type __fd_mask = ::std::os::raw::c_long; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct fd_set { - pub __fds_bits: [__fd_mask; 16usize], -} -pub type fd_mask = __fd_mask; -extern "C" { - pub fn select( - __nfds: ::std::os::raw::c_int, - __readfds: *mut fd_set, - __writefds: *mut fd_set, - __exceptfds: *mut fd_set, - __timeout: *mut timeval, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn pselect( - __nfds: ::std::os::raw::c_int, - __readfds: *mut fd_set, - __writefds: *mut fd_set, - __exceptfds: *mut fd_set, - __timeout: *const timespec, - __sigmask: *const __sigset_t, - ) -> ::std::os::raw::c_int; -} -pub type blksize_t = __blksize_t; -pub type blkcnt_t = __blkcnt_t; -pub type fsblkcnt_t = __fsblkcnt_t; -pub type fsfilcnt_t = __fsfilcnt_t; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __pthread_internal_list { - pub __prev: *mut __pthread_internal_list, - pub __next: *mut __pthread_internal_list, -} -pub type __pthread_list_t = __pthread_internal_list; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __pthread_internal_slist { - pub __next: *mut __pthread_internal_slist, -} -pub type __pthread_slist_t = __pthread_internal_slist; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __pthread_mutex_s { - pub __lock: ::std::os::raw::c_int, - pub __count: ::std::os::raw::c_uint, - pub __owner: ::std::os::raw::c_int, - pub __nusers: ::std::os::raw::c_uint, - pub __kind: ::std::os::raw::c_int, - pub __spins: ::std::os::raw::c_short, - pub __elision: ::std::os::raw::c_short, - pub __list: __pthread_list_t, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __pthread_rwlock_arch_t { - pub __readers: ::std::os::raw::c_uint, - pub __writers: ::std::os::raw::c_uint, - pub __wrphase_futex: ::std::os::raw::c_uint, - pub __writers_futex: ::std::os::raw::c_uint, - pub __pad3: ::std::os::raw::c_uint, - pub __pad4: ::std::os::raw::c_uint, - pub __cur_writer: ::std::os::raw::c_int, - pub __shared: ::std::os::raw::c_int, - pub __rwelision: ::std::os::raw::c_schar, - pub __pad1: [::std::os::raw::c_uchar; 7usize], - pub __pad2: ::std::os::raw::c_ulong, - pub __flags: ::std::os::raw::c_uint, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct __pthread_cond_s { - pub __bindgen_anon_1: __pthread_cond_s__bindgen_ty_1, - pub __bindgen_anon_2: __pthread_cond_s__bindgen_ty_2, - pub __g_refs: [::std::os::raw::c_uint; 2usize], - pub __g_size: [::std::os::raw::c_uint; 2usize], - pub __g1_orig_size: ::std::os::raw::c_uint, - pub __wrefs: ::std::os::raw::c_uint, - pub __g_signals: [::std::os::raw::c_uint; 2usize], -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union __pthread_cond_s__bindgen_ty_1 { - pub __wseq: ::std::os::raw::c_ulonglong, - pub __wseq32: __pthread_cond_s__bindgen_ty_1__bindgen_ty_1, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __pthread_cond_s__bindgen_ty_1__bindgen_ty_1 { - pub __low: ::std::os::raw::c_uint, - pub __high: ::std::os::raw::c_uint, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union __pthread_cond_s__bindgen_ty_2 { - pub __g1_start: ::std::os::raw::c_ulonglong, - pub __g1_start32: __pthread_cond_s__bindgen_ty_2__bindgen_ty_1, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __pthread_cond_s__bindgen_ty_2__bindgen_ty_1 { - pub __low: ::std::os::raw::c_uint, - pub __high: ::std::os::raw::c_uint, -} -pub type __tss_t = ::std::os::raw::c_uint; -pub type __thrd_t = ::std::os::raw::c_ulong; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __once_flag { - pub __data: ::std::os::raw::c_int, -} -pub type pthread_t = ::std::os::raw::c_ulong; -#[repr(C)] -#[derive(Copy, Clone)] -pub union pthread_mutexattr_t { - pub __size: [::std::os::raw::c_char; 4usize], - pub __align: ::std::os::raw::c_int, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union pthread_condattr_t { - pub __size: [::std::os::raw::c_char; 4usize], - pub __align: ::std::os::raw::c_int, -} -pub type pthread_key_t = ::std::os::raw::c_uint; -pub type pthread_once_t = ::std::os::raw::c_int; -#[repr(C)] -#[derive(Copy, Clone)] -pub union pthread_attr_t { - pub __size: [::std::os::raw::c_char; 56usize], - pub __align: ::std::os::raw::c_long, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union pthread_mutex_t { - pub __data: __pthread_mutex_s, - pub __size: [::std::os::raw::c_char; 40usize], - pub __align: ::std::os::raw::c_long, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union pthread_cond_t { - pub __data: __pthread_cond_s, - pub __size: [::std::os::raw::c_char; 48usize], - pub __align: ::std::os::raw::c_longlong, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union pthread_rwlock_t { - pub __data: __pthread_rwlock_arch_t, - pub __size: [::std::os::raw::c_char; 56usize], - pub __align: ::std::os::raw::c_long, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union pthread_rwlockattr_t { - pub __size: [::std::os::raw::c_char; 8usize], - pub __align: ::std::os::raw::c_long, -} -pub type pthread_spinlock_t = ::std::os::raw::c_int; -#[repr(C)] -#[derive(Copy, Clone)] -pub union pthread_barrier_t { - pub __size: [::std::os::raw::c_char; 32usize], - pub __align: ::std::os::raw::c_long, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union pthread_barrierattr_t { - pub __size: [::std::os::raw::c_char; 4usize], - pub __align: ::std::os::raw::c_int, -} -pub type socklen_t = __socklen_t; -pub const __socket_type_SOCK_STREAM: __socket_type = 1; -pub const __socket_type_SOCK_DGRAM: __socket_type = 2; -pub const __socket_type_SOCK_RAW: __socket_type = 3; -pub const __socket_type_SOCK_RDM: __socket_type = 4; -pub const __socket_type_SOCK_SEQPACKET: __socket_type = 5; -pub const __socket_type_SOCK_DCCP: __socket_type = 6; -pub const __socket_type_SOCK_PACKET: __socket_type = 10; -pub const __socket_type_SOCK_CLOEXEC: __socket_type = 524288; -pub const __socket_type_SOCK_NONBLOCK: __socket_type = 2048; -pub type __socket_type = ::std::os::raw::c_uint; -pub type sa_family_t = ::std::os::raw::c_ushort; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct sockaddr { - pub sa_family: sa_family_t, - pub sa_data: [::std::os::raw::c_uchar; 14usize], -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct sockaddr_storage { - pub ss_family: sa_family_t, - pub __ss_padding: [::std::os::raw::c_char; 118usize], - pub __ss_align: ::std::os::raw::c_ulong, -} -pub const MSG_OOB: ::std::os::raw::c_uint = 1; -pub const MSG_PEEK: ::std::os::raw::c_uint = 2; -pub const MSG_DONTROUTE: ::std::os::raw::c_uint = 4; -pub const MSG_CTRUNC: ::std::os::raw::c_uint = 8; -pub const MSG_PROXY: ::std::os::raw::c_uint = 16; -pub const MSG_TRUNC: ::std::os::raw::c_uint = 32; -pub const MSG_DONTWAIT: ::std::os::raw::c_uint = 64; -pub const MSG_EOR: ::std::os::raw::c_uint = 128; -pub const MSG_WAITALL: ::std::os::raw::c_uint = 256; -pub const MSG_FIN: ::std::os::raw::c_uint = 512; -pub const MSG_SYN: ::std::os::raw::c_uint = 1024; -pub const MSG_CONFIRM: ::std::os::raw::c_uint = 2048; -pub const MSG_RST: ::std::os::raw::c_uint = 4096; -pub const MSG_ERRQUEUE: ::std::os::raw::c_uint = 8192; -pub const MSG_NOSIGNAL: ::std::os::raw::c_uint = 16384; -pub const MSG_MORE: ::std::os::raw::c_uint = 32768; -pub const MSG_WAITFORONE: ::std::os::raw::c_uint = 65536; -pub const MSG_BATCH: ::std::os::raw::c_uint = 262144; -pub const MSG_ZEROCOPY: ::std::os::raw::c_uint = 67108864; -pub const MSG_FASTOPEN: ::std::os::raw::c_uint = 536870912; -pub const MSG_CMSG_CLOEXEC: ::std::os::raw::c_uint = 1073741824; -pub type _bindgen_ty_1 = ::std::os::raw::c_uint; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct msghdr { - pub msg_name: *mut ::std::os::raw::c_void, - pub msg_namelen: socklen_t, - pub msg_iov: *mut iovec, - pub msg_iovlen: size_t, - pub msg_control: *mut ::std::os::raw::c_void, - pub msg_controllen: size_t, - pub msg_flags: ::std::os::raw::c_int, -} -#[repr(C)] -#[derive(Debug)] -pub struct cmsghdr { - pub cmsg_len: size_t, - pub cmsg_level: ::std::os::raw::c_int, - pub cmsg_type: ::std::os::raw::c_int, - pub __cmsg_data: __IncompleteArrayField<::std::os::raw::c_uchar>, -} -extern "C" { - pub fn __cmsg_nxthdr(__mhdr: *mut msghdr, __cmsg: *mut cmsghdr) -> *mut cmsghdr; -} -pub const SCM_RIGHTS: ::std::os::raw::c_uint = 1; -pub type _bindgen_ty_2 = ::std::os::raw::c_uint; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct linger { - pub l_onoff: ::std::os::raw::c_int, - pub l_linger: ::std::os::raw::c_int, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct osockaddr { - pub sa_family: ::std::os::raw::c_ushort, - pub sa_data: [::std::os::raw::c_uchar; 14usize], -} -pub const SHUT_RD: ::std::os::raw::c_uint = 0; -pub const SHUT_WR: ::std::os::raw::c_uint = 1; -pub const SHUT_RDWR: ::std::os::raw::c_uint = 2; -pub type _bindgen_ty_3 = ::std::os::raw::c_uint; -extern "C" { - pub fn socket( - __domain: ::std::os::raw::c_int, - __type: ::std::os::raw::c_int, - __protocol: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn socketpair( - __domain: ::std::os::raw::c_int, - __type: ::std::os::raw::c_int, - __protocol: ::std::os::raw::c_int, - __fds: *mut ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn bind( - __fd: ::std::os::raw::c_int, - __addr: *const sockaddr, - __len: socklen_t, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn getsockname( - __fd: ::std::os::raw::c_int, - __addr: *mut sockaddr, - __len: *mut socklen_t, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn connect( - __fd: ::std::os::raw::c_int, - __addr: *const sockaddr, - __len: socklen_t, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn getpeername( - __fd: ::std::os::raw::c_int, - __addr: *mut sockaddr, - __len: *mut socklen_t, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn send( - __fd: ::std::os::raw::c_int, - __buf: *const ::std::os::raw::c_void, - __n: size_t, - __flags: ::std::os::raw::c_int, - ) -> ssize_t; -} -extern "C" { - pub fn recv( - __fd: ::std::os::raw::c_int, - __buf: *mut ::std::os::raw::c_void, - __n: size_t, - __flags: ::std::os::raw::c_int, - ) -> ssize_t; -} -extern "C" { - pub fn sendto( - __fd: ::std::os::raw::c_int, - __buf: *const ::std::os::raw::c_void, - __n: size_t, - __flags: ::std::os::raw::c_int, - __addr: *const sockaddr, - __addr_len: socklen_t, - ) -> ssize_t; -} -extern "C" { - pub fn recvfrom( - __fd: ::std::os::raw::c_int, - __buf: *mut ::std::os::raw::c_void, - __n: size_t, - __flags: ::std::os::raw::c_int, - __addr: *mut sockaddr, - __addr_len: *mut socklen_t, - ) -> ssize_t; -} -extern "C" { - pub fn sendmsg( - __fd: ::std::os::raw::c_int, - __message: *const msghdr, - __flags: ::std::os::raw::c_int, - ) -> ssize_t; -} -extern "C" { - pub fn recvmsg( - __fd: ::std::os::raw::c_int, - __message: *mut msghdr, - __flags: ::std::os::raw::c_int, - ) -> ssize_t; -} -extern "C" { - pub fn getsockopt( - __fd: ::std::os::raw::c_int, - __level: ::std::os::raw::c_int, - __optname: ::std::os::raw::c_int, - __optval: *mut ::std::os::raw::c_void, - __optlen: *mut socklen_t, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn setsockopt( - __fd: ::std::os::raw::c_int, - __level: ::std::os::raw::c_int, - __optname: ::std::os::raw::c_int, - __optval: *const ::std::os::raw::c_void, - __optlen: socklen_t, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn listen(__fd: ::std::os::raw::c_int, __n: ::std::os::raw::c_int) - -> ::std::os::raw::c_int; -} -extern "C" { - pub fn accept( - __fd: ::std::os::raw::c_int, - __addr: *mut sockaddr, - __addr_len: *mut socklen_t, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn shutdown( - __fd: ::std::os::raw::c_int, - __how: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn sockatmark(__fd: ::std::os::raw::c_int) -> ::std::os::raw::c_int; -} -extern "C" { - pub fn isfdtype( - __fd: ::std::os::raw::c_int, - __fdtype: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int; -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct sync_serial_settings { - pub clock_rate: ::std::os::raw::c_uint, - pub clock_type: ::std::os::raw::c_uint, - pub loopback: ::std::os::raw::c_ushort, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct te1_settings { - pub clock_rate: ::std::os::raw::c_uint, - pub clock_type: ::std::os::raw::c_uint, - pub loopback: ::std::os::raw::c_ushort, - pub slot_map: ::std::os::raw::c_uint, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct raw_hdlc_proto { - pub encoding: ::std::os::raw::c_ushort, - pub parity: ::std::os::raw::c_ushort, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct fr_proto { - pub t391: ::std::os::raw::c_uint, - pub t392: ::std::os::raw::c_uint, - pub n391: ::std::os::raw::c_uint, - pub n392: ::std::os::raw::c_uint, - pub n393: ::std::os::raw::c_uint, - pub lmi: ::std::os::raw::c_ushort, - pub dce: ::std::os::raw::c_ushort, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct fr_proto_pvc { - pub dlci: ::std::os::raw::c_uint, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct fr_proto_pvc_info { - pub dlci: ::std::os::raw::c_uint, - pub master: [::std::os::raw::c_char; 16usize], -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct cisco_proto { - pub interval: ::std::os::raw::c_uint, - pub timeout: ::std::os::raw::c_uint, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct x25_hdlc_proto { - pub dce: ::std::os::raw::c_ushort, - pub modulo: ::std::os::raw::c_uint, - pub window: ::std::os::raw::c_uint, - pub t1: ::std::os::raw::c_uint, - pub t2: ::std::os::raw::c_uint, - pub n2: ::std::os::raw::c_uint, -} -pub const net_device_flags_IFF_UP: net_device_flags = 1; -pub const net_device_flags_IFF_BROADCAST: net_device_flags = 2; -pub const net_device_flags_IFF_DEBUG: net_device_flags = 4; -pub const net_device_flags_IFF_LOOPBACK: net_device_flags = 8; -pub const net_device_flags_IFF_POINTOPOINT: net_device_flags = 16; -pub const net_device_flags_IFF_NOTRAILERS: net_device_flags = 32; -pub const net_device_flags_IFF_RUNNING: net_device_flags = 64; -pub const net_device_flags_IFF_NOARP: net_device_flags = 128; -pub const net_device_flags_IFF_PROMISC: net_device_flags = 256; -pub const net_device_flags_IFF_ALLMULTI: net_device_flags = 512; -pub const net_device_flags_IFF_MASTER: net_device_flags = 1024; -pub const net_device_flags_IFF_SLAVE: net_device_flags = 2048; -pub const net_device_flags_IFF_MULTICAST: net_device_flags = 4096; -pub const net_device_flags_IFF_PORTSEL: net_device_flags = 8192; -pub const net_device_flags_IFF_AUTOMEDIA: net_device_flags = 16384; -pub const net_device_flags_IFF_DYNAMIC: net_device_flags = 32768; -pub const net_device_flags_IFF_LOWER_UP: net_device_flags = 65536; -pub const net_device_flags_IFF_DORMANT: net_device_flags = 131072; -pub const net_device_flags_IFF_ECHO: net_device_flags = 262144; -#[doc = " enum net_device_flags - &struct net_device flags"] -#[doc = ""] -#[doc = " These are the &struct net_device flags, they can be set by drivers, the"] -#[doc = " kernel and some can be triggered by userspace. Userspace can query and"] -#[doc = " set these flags using userspace utilities but there is also a sysfs"] -#[doc = " entry available for all dev flags which can be queried and set. These flags"] -#[doc = " are shared for all types of net_devices. The sysfs entries are available"] -#[doc = " via /sys/class/net//flags. Flags which can be toggled through sysfs"] -#[doc = " are annotated below, note that only a few flags can be toggled and some"] -#[doc = " other flags are always preserved from the original net_device flags"] -#[doc = " even if you try to set them via sysfs. Flags which are always preserved"] -#[doc = " are kept under the flag grouping @IFF_VOLATILE. Flags which are __volatile__"] -#[doc = " are annotated below as such."] -#[doc = ""] -#[doc = " You should have a pretty good reason to be extending these flags."] -#[doc = ""] -#[doc = " @IFF_UP: interface is up. Can be toggled through sysfs."] -#[doc = " @IFF_BROADCAST: broadcast address valid. Volatile."] -#[doc = " @IFF_DEBUG: turn on debugging. Can be toggled through sysfs."] -#[doc = " @IFF_LOOPBACK: is a loopback net. Volatile."] -#[doc = " @IFF_POINTOPOINT: interface is has p-p link. Volatile."] -#[doc = " @IFF_NOTRAILERS: avoid use of trailers. Can be toggled through sysfs."] -#[doc = "\tVolatile."] -#[doc = " @IFF_RUNNING: interface RFC2863 OPER_UP. Volatile."] -#[doc = " @IFF_NOARP: no ARP protocol. Can be toggled through sysfs. Volatile."] -#[doc = " @IFF_PROMISC: receive all packets. Can be toggled through sysfs."] -#[doc = " @IFF_ALLMULTI: receive all multicast packets. Can be toggled through"] -#[doc = "\tsysfs."] -#[doc = " @IFF_MASTER: master of a load balancer. Volatile."] -#[doc = " @IFF_SLAVE: slave of a load balancer. Volatile."] -#[doc = " @IFF_MULTICAST: Supports multicast. Can be toggled through sysfs."] -#[doc = " @IFF_PORTSEL: can set media type. Can be toggled through sysfs."] -#[doc = " @IFF_AUTOMEDIA: auto media select active. Can be toggled through sysfs."] -#[doc = " @IFF_DYNAMIC: dialup device with changing addresses. Can be toggled"] -#[doc = "\tthrough sysfs."] -#[doc = " @IFF_LOWER_UP: driver signals L1 up. Volatile."] -#[doc = " @IFF_DORMANT: driver signals dormant. Volatile."] -#[doc = " @IFF_ECHO: echo sent packets. Volatile."] -pub type net_device_flags = ::std::os::raw::c_uint; -pub const IF_OPER_UNKNOWN: ::std::os::raw::c_uint = 0; -pub const IF_OPER_NOTPRESENT: ::std::os::raw::c_uint = 1; -pub const IF_OPER_DOWN: ::std::os::raw::c_uint = 2; -pub const IF_OPER_LOWERLAYERDOWN: ::std::os::raw::c_uint = 3; -pub const IF_OPER_TESTING: ::std::os::raw::c_uint = 4; -pub const IF_OPER_DORMANT: ::std::os::raw::c_uint = 5; -pub const IF_OPER_UP: ::std::os::raw::c_uint = 6; -pub type _bindgen_ty_4 = ::std::os::raw::c_uint; -pub const IF_LINK_MODE_DEFAULT: ::std::os::raw::c_uint = 0; -pub const IF_LINK_MODE_DORMANT: ::std::os::raw::c_uint = 1; -pub const IF_LINK_MODE_TESTING: ::std::os::raw::c_uint = 2; -pub type _bindgen_ty_5 = ::std::os::raw::c_uint; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct ifmap { - pub mem_start: ::std::os::raw::c_ulong, - pub mem_end: ::std::os::raw::c_ulong, - pub base_addr: ::std::os::raw::c_ushort, - pub irq: ::std::os::raw::c_uchar, - pub dma: ::std::os::raw::c_uchar, - pub port: ::std::os::raw::c_uchar, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct if_settings { - pub type_: ::std::os::raw::c_uint, - pub size: ::std::os::raw::c_uint, - pub ifs_ifsu: if_settings__bindgen_ty_1, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union if_settings__bindgen_ty_1 { - pub raw_hdlc: *mut raw_hdlc_proto, - pub cisco: *mut cisco_proto, - pub fr: *mut fr_proto, - pub fr_pvc: *mut fr_proto_pvc, - pub fr_pvc_info: *mut fr_proto_pvc_info, - pub x25: *mut x25_hdlc_proto, - pub sync: *mut sync_serial_settings, - pub te1: *mut te1_settings, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct ifreq { - pub ifr_ifrn: ifreq__bindgen_ty_1, - pub ifr_ifru: ifreq__bindgen_ty_2, -} - -impl Default for ifreq { - fn default() -> Self { - // SAFETY: all zeros is a valid pattern for this data type - unsafe { std::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union ifreq__bindgen_ty_1 { - pub ifrn_name: [::std::os::raw::c_uchar; 16usize], -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union ifreq__bindgen_ty_2 { - pub ifru_addr: sockaddr, - pub ifru_dstaddr: sockaddr, - pub ifru_broadaddr: sockaddr, - pub ifru_netmask: sockaddr, - pub ifru_hwaddr: sockaddr, - pub ifru_flags: ::std::os::raw::c_short, - pub ifru_ivalue: ::std::os::raw::c_int, - pub ifru_mtu: ::std::os::raw::c_int, - pub ifru_map: ifmap, - pub ifru_slave: [::std::os::raw::c_uchar; 16usize], - pub ifru_newname: [::std::os::raw::c_uchar; 16usize], - pub ifru_data: *mut ::std::os::raw::c_void, - pub ifru_settings: if_settings, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct ifconf { - pub ifc_len: ::std::os::raw::c_int, - pub ifc_ifcu: ifconf__bindgen_ty_1, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union ifconf__bindgen_ty_1 { - pub ifcu_buf: *mut ::std::os::raw::c_char, - pub ifcu_req: *mut ifreq, -} diff --git a/net_gen/src/inn.rs b/net_gen/src/inn.rs deleted file mode 100644 index f7a4e508a4..0000000000 --- a/net_gen/src/inn.rs +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright © 2021 Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 - -// bindgen /usr/include/linux/in.h --no-layout-tests - -/* automatically generated by rust-bindgen 0.58.1 */ - -pub const __BITS_PER_LONG: u32 = 64; -pub const __FD_SETSIZE: u32 = 1024; -pub const __UAPI_DEF_IF_IFCONF: u32 = 1; -pub const __UAPI_DEF_IF_IFMAP: u32 = 1; -pub const __UAPI_DEF_IF_IFNAMSIZ: u32 = 1; -pub const __UAPI_DEF_IF_IFREQ: u32 = 1; -pub const __UAPI_DEF_IF_NET_DEVICE_FLAGS: u32 = 1; -pub const __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO: u32 = 1; -pub const __UAPI_DEF_IN_ADDR: u32 = 1; -pub const __UAPI_DEF_IN_IPPROTO: u32 = 1; -pub const __UAPI_DEF_IN_PKTINFO: u32 = 1; -pub const __UAPI_DEF_IP_MREQ: u32 = 1; -pub const __UAPI_DEF_SOCKADDR_IN: u32 = 1; -pub const __UAPI_DEF_IN_CLASS: u32 = 1; -pub const __UAPI_DEF_IN6_ADDR: u32 = 1; -pub const __UAPI_DEF_IN6_ADDR_ALT: u32 = 1; -pub const __UAPI_DEF_SOCKADDR_IN6: u32 = 1; -pub const __UAPI_DEF_IPV6_MREQ: u32 = 1; -pub const __UAPI_DEF_IPPROTO_V6: u32 = 1; -pub const __UAPI_DEF_IPV6_OPTIONS: u32 = 1; -pub const __UAPI_DEF_IN6_PKTINFO: u32 = 1; -pub const __UAPI_DEF_IP6_MTUINFO: u32 = 1; -pub const __UAPI_DEF_SOCKADDR_IPX: u32 = 1; -pub const __UAPI_DEF_IPX_ROUTE_DEFINITION: u32 = 1; -pub const __UAPI_DEF_IPX_INTERFACE_DEFINITION: u32 = 1; -pub const __UAPI_DEF_IPX_CONFIG_DATA: u32 = 1; -pub const __UAPI_DEF_IPX_ROUTE_DEF: u32 = 1; -pub const __UAPI_DEF_XATTR: u32 = 1; -pub const _K_SS_MAXSIZE: u32 = 128; -pub const IP_TOS: u32 = 1; -pub const IP_TTL: u32 = 2; -pub const IP_HDRINCL: u32 = 3; -pub const IP_OPTIONS: u32 = 4; -pub const IP_ROUTER_ALERT: u32 = 5; -pub const IP_RECVOPTS: u32 = 6; -pub const IP_RETOPTS: u32 = 7; -pub const IP_PKTINFO: u32 = 8; -pub const IP_PKTOPTIONS: u32 = 9; -pub const IP_MTU_DISCOVER: u32 = 10; -pub const IP_RECVERR: u32 = 11; -pub const IP_RECVTTL: u32 = 12; -pub const IP_RECVTOS: u32 = 13; -pub const IP_MTU: u32 = 14; -pub const IP_FREEBIND: u32 = 15; -pub const IP_IPSEC_POLICY: u32 = 16; -pub const IP_XFRM_POLICY: u32 = 17; -pub const IP_PASSSEC: u32 = 18; -pub const IP_TRANSPARENT: u32 = 19; -pub const IP_RECVRETOPTS: u32 = 7; -pub const IP_ORIGDSTADDR: u32 = 20; -pub const IP_RECVORIGDSTADDR: u32 = 20; -pub const IP_MINTTL: u32 = 21; -pub const IP_NODEFRAG: u32 = 22; -pub const IP_CHECKSUM: u32 = 23; -pub const IP_BIND_ADDRESS_NO_PORT: u32 = 24; -pub const IP_RECVFRAGSIZE: u32 = 25; -pub const IP_PMTUDISC_DONT: u32 = 0; -pub const IP_PMTUDISC_WANT: u32 = 1; -pub const IP_PMTUDISC_DO: u32 = 2; -pub const IP_PMTUDISC_PROBE: u32 = 3; -pub const IP_PMTUDISC_INTERFACE: u32 = 4; -pub const IP_PMTUDISC_OMIT: u32 = 5; -pub const IP_MULTICAST_IF: u32 = 32; -pub const IP_MULTICAST_TTL: u32 = 33; -pub const IP_MULTICAST_LOOP: u32 = 34; -pub const IP_ADD_MEMBERSHIP: u32 = 35; -pub const IP_DROP_MEMBERSHIP: u32 = 36; -pub const IP_UNBLOCK_SOURCE: u32 = 37; -pub const IP_BLOCK_SOURCE: u32 = 38; -pub const IP_ADD_SOURCE_MEMBERSHIP: u32 = 39; -pub const IP_DROP_SOURCE_MEMBERSHIP: u32 = 40; -pub const IP_MSFILTER: u32 = 41; -pub const MCAST_JOIN_GROUP: u32 = 42; -pub const MCAST_BLOCK_SOURCE: u32 = 43; -pub const MCAST_UNBLOCK_SOURCE: u32 = 44; -pub const MCAST_LEAVE_GROUP: u32 = 45; -pub const MCAST_JOIN_SOURCE_GROUP: u32 = 46; -pub const MCAST_LEAVE_SOURCE_GROUP: u32 = 47; -pub const MCAST_MSFILTER: u32 = 48; -pub const IP_MULTICAST_ALL: u32 = 49; -pub const IP_UNICAST_IF: u32 = 50; -pub const MCAST_EXCLUDE: u32 = 0; -pub const MCAST_INCLUDE: u32 = 1; -pub const IP_DEFAULT_MULTICAST_TTL: u32 = 1; -pub const IP_DEFAULT_MULTICAST_LOOP: u32 = 1; -pub const __SOCK_SIZE__: u32 = 16; -pub const IN_CLASSA_NET: u32 = 4278190080; -pub const IN_CLASSA_NSHIFT: u32 = 24; -pub const IN_CLASSA_HOST: u32 = 16777215; -pub const IN_CLASSA_MAX: u32 = 128; -pub const IN_CLASSB_NET: u32 = 4294901760; -pub const IN_CLASSB_NSHIFT: u32 = 16; -pub const IN_CLASSB_HOST: u32 = 65535; -pub const IN_CLASSB_MAX: u32 = 65536; -pub const IN_CLASSC_NET: u32 = 4294967040; -pub const IN_CLASSC_NSHIFT: u32 = 8; -pub const IN_CLASSC_HOST: u32 = 255; -pub const IN_MULTICAST_NET: u32 = 3758096384; -pub const IN_CLASSE_NET: u32 = 4294967295; -pub const IN_CLASSE_NSHIFT: u32 = 0; -pub const IN_LOOPBACKNET: u32 = 127; -pub const INADDR_LOOPBACK: u32 = 2130706433; -pub const INADDR_UNSPEC_GROUP: u32 = 3758096384; -pub const INADDR_ALLHOSTS_GROUP: u32 = 3758096385; -pub const INADDR_ALLRTRS_GROUP: u32 = 3758096386; -pub const INADDR_ALLSNOOPERS_GROUP: u32 = 3758096490; -pub const INADDR_MAX_LOCAL_GROUP: u32 = 3758096639; -pub const __LITTLE_ENDIAN: u32 = 1234; -pub type __s8 = ::std::os::raw::c_schar; -pub type __u8 = ::std::os::raw::c_uchar; -pub type __s16 = ::std::os::raw::c_short; -pub type __u16 = ::std::os::raw::c_ushort; -pub type __s32 = ::std::os::raw::c_int; -pub type __u32 = ::std::os::raw::c_uint; -pub type __s64 = ::std::os::raw::c_longlong; -pub type __u64 = ::std::os::raw::c_ulonglong; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __kernel_fd_set { - pub fds_bits: [::std::os::raw::c_ulong; 16usize], -} -pub type __kernel_sighandler_t = - ::std::option::Option; -pub type __kernel_key_t = ::std::os::raw::c_int; -pub type __kernel_mqd_t = ::std::os::raw::c_int; -pub type __kernel_old_uid_t = ::std::os::raw::c_ushort; -pub type __kernel_old_gid_t = ::std::os::raw::c_ushort; -pub type __kernel_old_dev_t = ::std::os::raw::c_ulong; -pub type __kernel_long_t = ::std::os::raw::c_long; -pub type __kernel_ulong_t = ::std::os::raw::c_ulong; -pub type __kernel_ino_t = __kernel_ulong_t; -pub type __kernel_mode_t = ::std::os::raw::c_uint; -pub type __kernel_pid_t = ::std::os::raw::c_int; -pub type __kernel_ipc_pid_t = ::std::os::raw::c_int; -pub type __kernel_uid_t = ::std::os::raw::c_uint; -pub type __kernel_gid_t = ::std::os::raw::c_uint; -pub type __kernel_suseconds_t = __kernel_long_t; -pub type __kernel_daddr_t = ::std::os::raw::c_int; -pub type __kernel_uid32_t = ::std::os::raw::c_uint; -pub type __kernel_gid32_t = ::std::os::raw::c_uint; -pub type __kernel_size_t = __kernel_ulong_t; -pub type __kernel_ssize_t = __kernel_long_t; -pub type __kernel_ptrdiff_t = __kernel_long_t; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __kernel_fsid_t { - pub val: [::std::os::raw::c_int; 2usize], -} -pub type __kernel_off_t = __kernel_long_t; -pub type __kernel_loff_t = ::std::os::raw::c_longlong; -pub type __kernel_old_time_t = __kernel_long_t; -pub type __kernel_time_t = __kernel_long_t; -pub type __kernel_time64_t = ::std::os::raw::c_longlong; -pub type __kernel_clock_t = __kernel_long_t; -pub type __kernel_timer_t = ::std::os::raw::c_int; -pub type __kernel_clockid_t = ::std::os::raw::c_int; -pub type __kernel_caddr_t = *mut ::std::os::raw::c_char; -pub type __kernel_uid16_t = ::std::os::raw::c_ushort; -pub type __kernel_gid16_t = ::std::os::raw::c_ushort; -pub type __le16 = __u16; -pub type __be16 = __u16; -pub type __le32 = __u32; -pub type __be32 = __u32; -pub type __le64 = __u64; -pub type __be64 = __u64; -pub type __sum16 = __u16; -pub type __wsum = __u32; -pub type __poll_t = ::std::os::raw::c_uint; -pub type __kernel_sa_family_t = ::std::os::raw::c_ushort; -#[repr(C)] -#[derive(Copy, Clone)] -pub struct __kernel_sockaddr_storage { - pub __bindgen_anon_1: __kernel_sockaddr_storage__bindgen_ty_1, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union __kernel_sockaddr_storage__bindgen_ty_1 { - pub __bindgen_anon_1: __kernel_sockaddr_storage__bindgen_ty_1__bindgen_ty_1, - pub __align: *mut ::std::os::raw::c_void, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct __kernel_sockaddr_storage__bindgen_ty_1__bindgen_ty_1 { - pub ss_family: __kernel_sa_family_t, - pub __data: [::std::os::raw::c_char; 126usize], -} -pub const IPPROTO_IP: ::std::os::raw::c_uint = 0; -pub const IPPROTO_ICMP: ::std::os::raw::c_uint = 1; -pub const IPPROTO_IGMP: ::std::os::raw::c_uint = 2; -pub const IPPROTO_IPIP: ::std::os::raw::c_uint = 4; -pub const IPPROTO_TCP: ::std::os::raw::c_uint = 6; -pub const IPPROTO_EGP: ::std::os::raw::c_uint = 8; -pub const IPPROTO_PUP: ::std::os::raw::c_uint = 12; -pub const IPPROTO_UDP: ::std::os::raw::c_uint = 17; -pub const IPPROTO_IDP: ::std::os::raw::c_uint = 22; -pub const IPPROTO_TP: ::std::os::raw::c_uint = 29; -pub const IPPROTO_DCCP: ::std::os::raw::c_uint = 33; -pub const IPPROTO_IPV6: ::std::os::raw::c_uint = 41; -pub const IPPROTO_RSVP: ::std::os::raw::c_uint = 46; -pub const IPPROTO_GRE: ::std::os::raw::c_uint = 47; -pub const IPPROTO_ESP: ::std::os::raw::c_uint = 50; -pub const IPPROTO_AH: ::std::os::raw::c_uint = 51; -pub const IPPROTO_MTP: ::std::os::raw::c_uint = 92; -pub const IPPROTO_BEETPH: ::std::os::raw::c_uint = 94; -pub const IPPROTO_ENCAP: ::std::os::raw::c_uint = 98; -pub const IPPROTO_PIM: ::std::os::raw::c_uint = 103; -pub const IPPROTO_COMP: ::std::os::raw::c_uint = 108; -pub const IPPROTO_SCTP: ::std::os::raw::c_uint = 132; -pub const IPPROTO_UDPLITE: ::std::os::raw::c_uint = 136; -pub const IPPROTO_MPLS: ::std::os::raw::c_uint = 137; -pub const IPPROTO_ETHERNET: ::std::os::raw::c_uint = 143; -pub const IPPROTO_RAW: ::std::os::raw::c_uint = 255; -pub const IPPROTO_MPTCP: ::std::os::raw::c_uint = 262; -pub const IPPROTO_MAX: ::std::os::raw::c_uint = 263; -pub type _bindgen_ty_1 = ::std::os::raw::c_uint; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct in_addr { - pub s_addr: __be32, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct ip_mreq { - pub imr_multiaddr: in_addr, - pub imr_interface: in_addr, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct ip_mreqn { - pub imr_multiaddr: in_addr, - pub imr_address: in_addr, - pub imr_ifindex: ::std::os::raw::c_int, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct ip_mreq_source { - pub imr_multiaddr: __be32, - pub imr_interface: __be32, - pub imr_sourceaddr: __be32, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct ip_msfilter { - pub imsf_multiaddr: __be32, - pub imsf_interface: __be32, - pub imsf_fmode: __u32, - pub imsf_numsrc: __u32, - pub imsf_slist: [__be32; 1usize], -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct group_req { - pub gr_interface: __u32, - pub gr_group: __kernel_sockaddr_storage, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct group_source_req { - pub gsr_interface: __u32, - pub gsr_group: __kernel_sockaddr_storage, - pub gsr_source: __kernel_sockaddr_storage, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct group_filter { - pub gf_interface: __u32, - pub gf_group: __kernel_sockaddr_storage, - pub gf_fmode: __u32, - pub gf_numsrc: __u32, - pub gf_slist: [__kernel_sockaddr_storage; 1usize], -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct in_pktinfo { - pub ipi_ifindex: ::std::os::raw::c_int, - pub ipi_spec_dst: in_addr, - pub ipi_addr: in_addr, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct sockaddr_in { - pub sin_family: __kernel_sa_family_t, - pub sin_port: __be16, - pub sin_addr: in_addr, - pub __pad: [::std::os::raw::c_uchar; 8usize], -} diff --git a/net_gen/src/ipv6.rs b/net_gen/src/ipv6.rs deleted file mode 100644 index 65d9349ec3..0000000000 --- a/net_gen/src/ipv6.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2025 Cloud Hypervisor Authors -// -// SPDX-License-Identifier: Apache-2.0 - -// bindgen /usr/include/linux/ipv6.h --no-layout-tests --constified-enum '*' --allowlist-type 'sockaddr_in6|in6_ifreq' - -/* automatically generated by rust-bindgen 0.71.1 */ - -pub type __u8 = ::std::os::raw::c_uchar; -pub type __u16 = ::std::os::raw::c_ushort; -pub type __u32 = ::std::os::raw::c_uint; -pub type __be16 = __u16; -pub type __be32 = __u32; -#[repr(C)] -#[derive(Copy, Clone)] -pub struct in6_addr { - pub in6_u: in6_addr__bindgen_ty_1, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union in6_addr__bindgen_ty_1 { - pub u6_addr8: [__u8; 16usize], - pub u6_addr16: [__be16; 8usize], - pub u6_addr32: [__be32; 4usize], -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct sockaddr_in6 { - pub sin6_family: ::std::os::raw::c_ushort, - pub sin6_port: __be16, - pub sin6_flowinfo: __be32, - pub sin6_addr: in6_addr, - pub sin6_scope_id: __u32, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct in6_ifreq { - pub ifr6_addr: in6_addr, - pub ifr6_prefixlen: __u32, - pub ifr6_ifindex: ::std::os::raw::c_int, -} diff --git a/net_gen/src/lib.rs b/net_gen/src/lib.rs deleted file mode 100644 index 91a5c8c15d..0000000000 --- a/net_gen/src/lib.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright TUNTAP, 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. -// -// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause - -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -// generated with bindgen /usr/include/linux/if.h --no-unstable-rust -// --constified-enum '*' --with-derive-default -- -D __UAPI_DEF_IF_IFNAMSIZ -D -// __UAPI_DEF_IF_NET_DEVICE_FLAGS -D __UAPI_DEF_IF_IFREQ -D __UAPI_DEF_IF_IFMAP -// Name is "iff" to avoid conflicting with "if" keyword. -// Generated against Linux 4.11 to include fix "uapi: fix linux/if.h userspace -// compilation errors". -// Manual fixup of ifrn_name to be of type c_uchar instead of c_char. -pub mod iff; -// generated with bindgen /usr/include/linux/if_tun.h --no-unstable-rust -// --constified-enum '*' --with-derive-default -pub mod if_tun; -// generated with bindgen /usr/include/linux/in.h --no-unstable-rust -// --constified-enum '*' --with-derive-default -// Name is "inn" to avoid conflicting with "in" keyword. -pub mod inn; -// generated with bindgen /usr/include/linux/ipv6.h --no-layout-tests --constified-enum '*' -// --allowlist-type 'sockaddr_in6|in6_ifreq' -pub mod ipv6; -// generated with bindgen /usr/include/linux/sockios.h --no-unstable-rust -// --constified-enum '*' --with-derive-default -pub mod sockios; -pub use if_tun::{ - sock_fprog, IFF_MULTI_QUEUE, IFF_NO_PI, IFF_TAP, IFF_VNET_HDR, TUN_F_CSUM, TUN_F_TSO4, - TUN_F_TSO6, TUN_F_TSO_ECN, TUN_F_UFO, -}; -pub use iff::{ifreq, net_device_flags_IFF_UP, setsockopt, sockaddr, AF_INET}; -pub use inn::sockaddr_in; -pub use ipv6::{in6_ifreq, sockaddr_in6}; -use vmm_sys_util::{ioctl_ior_nr, ioctl_iow_nr}; - -pub const TUNTAP: ::std::os::raw::c_uint = 84; - -ioctl_iow_nr!(TUNSETNOCSUM, TUNTAP, 200, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETDEBUG, TUNTAP, 201, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETIFF, TUNTAP, 202, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETPERSIST, TUNTAP, 203, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETOWNER, TUNTAP, 204, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETLINK, TUNTAP, 205, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETGROUP, TUNTAP, 206, ::std::os::raw::c_int); -ioctl_ior_nr!(TUNGETFEATURES, TUNTAP, 207, ::std::os::raw::c_uint); -ioctl_iow_nr!(TUNSETOFFLOAD, TUNTAP, 208, ::std::os::raw::c_uint); -ioctl_iow_nr!(TUNSETTXFILTER, TUNTAP, 209, ::std::os::raw::c_uint); -ioctl_ior_nr!(TUNGETIFF, TUNTAP, 210, ::std::os::raw::c_uint); -ioctl_ior_nr!(TUNGETSNDBUF, TUNTAP, 211, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETSNDBUF, TUNTAP, 212, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNATTACHFILTER, TUNTAP, 213, sock_fprog); -ioctl_iow_nr!(TUNDETACHFILTER, TUNTAP, 214, sock_fprog); -ioctl_ior_nr!(TUNGETVNETHDRSZ, TUNTAP, 215, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETVNETHDRSZ, TUNTAP, 216, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETQUEUE, TUNTAP, 217, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETIFINDEX, TUNTAP, 218, ::std::os::raw::c_uint); -ioctl_ior_nr!(TUNGETFILTER, TUNTAP, 219, sock_fprog); -ioctl_iow_nr!(TUNSETVNETLE, TUNTAP, 220, ::std::os::raw::c_int); -ioctl_ior_nr!(TUNGETVNETLE, TUNTAP, 221, ::std::os::raw::c_int); -ioctl_iow_nr!(TUNSETVNETBE, TUNTAP, 222, ::std::os::raw::c_int); -ioctl_ior_nr!(TUNGETVNETBE, TUNTAP, 223, ::std::os::raw::c_int); diff --git a/net_gen/src/sockios.rs b/net_gen/src/sockios.rs deleted file mode 100644 index ad3dee7c8a..0000000000 --- a/net_gen/src/sockios.rs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright © 2021 Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 -// - -// bindgen /usr/include/linux/sockios.h --no-layout-tests - -/* automatically generated by rust-bindgen 0.58.1 */ - -pub const __BITS_PER_LONG: u32 = 64; -pub const FIOSETOWN: u32 = 35073; -pub const SIOCSPGRP: u32 = 35074; -pub const FIOGETOWN: u32 = 35075; -pub const SIOCGPGRP: u32 = 35076; -pub const SIOCATMARK: u32 = 35077; -pub const SIOCGSTAMP_OLD: u32 = 35078; -pub const SIOCGSTAMPNS_OLD: u32 = 35079; -pub const SOCK_IOC_TYPE: u32 = 137; -pub const SIOCGSTAMP: u32 = 35078; -pub const SIOCGSTAMPNS: u32 = 35079; -pub const SIOCADDRT: u32 = 35083; -pub const SIOCDELRT: u32 = 35084; -pub const SIOCRTMSG: u32 = 35085; -pub const SIOCGIFNAME: u32 = 35088; -pub const SIOCSIFLINK: u32 = 35089; -pub const SIOCGIFCONF: u32 = 35090; -pub const SIOCGIFFLAGS: u32 = 35091; -pub const SIOCSIFFLAGS: u32 = 35092; -pub const SIOCGIFADDR: u32 = 35093; -pub const SIOCSIFADDR: u32 = 35094; -pub const SIOCGIFDSTADDR: u32 = 35095; -pub const SIOCSIFDSTADDR: u32 = 35096; -pub const SIOCGIFBRDADDR: u32 = 35097; -pub const SIOCSIFBRDADDR: u32 = 35098; -pub const SIOCGIFNETMASK: u32 = 35099; -pub const SIOCSIFNETMASK: u32 = 35100; -pub const SIOCGIFMETRIC: u32 = 35101; -pub const SIOCSIFMETRIC: u32 = 35102; -pub const SIOCGIFMEM: u32 = 35103; -pub const SIOCSIFMEM: u32 = 35104; -pub const SIOCGIFMTU: u32 = 35105; -pub const SIOCSIFMTU: u32 = 35106; -pub const SIOCSIFNAME: u32 = 35107; -pub const SIOCSIFHWADDR: u32 = 35108; -pub const SIOCGIFENCAP: u32 = 35109; -pub const SIOCSIFENCAP: u32 = 35110; -pub const SIOCGIFHWADDR: u32 = 35111; -pub const SIOCGIFSLAVE: u32 = 35113; -pub const SIOCSIFSLAVE: u32 = 35120; -pub const SIOCADDMULTI: u32 = 35121; -pub const SIOCDELMULTI: u32 = 35122; -pub const SIOCGIFINDEX: u32 = 35123; -pub const SIOGIFINDEX: u32 = 35123; -pub const SIOCSIFPFLAGS: u32 = 35124; -pub const SIOCGIFPFLAGS: u32 = 35125; -pub const SIOCDIFADDR: u32 = 35126; -pub const SIOCSIFHWBROADCAST: u32 = 35127; -pub const SIOCGIFCOUNT: u32 = 35128; -pub const SIOCGIFBR: u32 = 35136; -pub const SIOCSIFBR: u32 = 35137; -pub const SIOCGIFTXQLEN: u32 = 35138; -pub const SIOCSIFTXQLEN: u32 = 35139; -pub const SIOCETHTOOL: u32 = 35142; -pub const SIOCGMIIPHY: u32 = 35143; -pub const SIOCGMIIREG: u32 = 35144; -pub const SIOCSMIIREG: u32 = 35145; -pub const SIOCWANDEV: u32 = 35146; -pub const SIOCOUTQNSD: u32 = 35147; -pub const SIOCGSKNS: u32 = 35148; -pub const SIOCDARP: u32 = 35155; -pub const SIOCGARP: u32 = 35156; -pub const SIOCSARP: u32 = 35157; -pub const SIOCDRARP: u32 = 35168; -pub const SIOCGRARP: u32 = 35169; -pub const SIOCSRARP: u32 = 35170; -pub const SIOCGIFMAP: u32 = 35184; -pub const SIOCSIFMAP: u32 = 35185; -pub const SIOCADDDLCI: u32 = 35200; -pub const SIOCDELDLCI: u32 = 35201; -pub const SIOCGIFVLAN: u32 = 35202; -pub const SIOCSIFVLAN: u32 = 35203; -pub const SIOCBONDENSLAVE: u32 = 35216; -pub const SIOCBONDRELEASE: u32 = 35217; -pub const SIOCBONDSETHWADDR: u32 = 35218; -pub const SIOCBONDSLAVEINFOQUERY: u32 = 35219; -pub const SIOCBONDINFOQUERY: u32 = 35220; -pub const SIOCBONDCHANGEACTIVE: u32 = 35221; -pub const SIOCBRADDBR: u32 = 35232; -pub const SIOCBRDELBR: u32 = 35233; -pub const SIOCBRADDIF: u32 = 35234; -pub const SIOCBRDELIF: u32 = 35235; -pub const SIOCSHWTSTAMP: u32 = 35248; -pub const SIOCGHWTSTAMP: u32 = 35249; -pub const SIOCDEVPRIVATE: u32 = 35312; -pub const SIOCPROTOPRIVATE: u32 = 35296; diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml index fccb89320f..03a40defce 100644 --- a/net_util/Cargo.toml +++ b/net_util/Cargo.toml @@ -10,7 +10,6 @@ epoll = { workspace = true } getrandom = "0.4.2" libc = { workspace = true } log = { workspace = true } -net_gen = { path = "../net_gen" } rate_limiter = { path = "../rate_limiter" } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } diff --git a/net_util/src/lib.rs b/net_util/src/lib.rs index 6cf5791507..7152c1676f 100644 --- a/net_util/src/lib.rs +++ b/net_util/src/lib.rs @@ -59,15 +59,15 @@ unsafe impl ByteValued for VirtioNetConfig {} /// Create a sockaddr_in from an IPv4 address, and expose it as /// an opaque sockaddr suitable for usage by socket ioctls. -fn create_sockaddr(ip_addr: net::Ipv4Addr) -> net_gen::sockaddr { - // IPv4 addresses big-endian (network order), but Ipv4Addr will give us - // a view of those bytes directly so we can avoid any endian trickiness. - let addr_in = net_gen::sockaddr_in { - sin_family: net_gen::AF_INET as u16, +fn create_sockaddr(ip_addr: net::Ipv4Addr) -> libc::sockaddr { + let addr_in = libc::sockaddr_in { + sin_family: libc::AF_INET as u16, sin_port: 0, - // SAFETY: ip_addr can be safely transmute to in_addr - sin_addr: unsafe { mem::transmute::<[u8; 4], net_gen::inn::in_addr>(ip_addr.octets()) }, - __pad: [0; 8usize], + sin_addr: libc::in_addr { + // Use network byte order (big endian). + s_addr: ip_addr.to_bits().to_be(), + }, + sin_zero: [0; 8], }; // SAFETY: addr_in can be safely transmute to sockaddr @@ -167,19 +167,19 @@ pub fn build_net_config_space_with_mq( pub fn virtio_features_to_tap_offload(features: u64) -> c_uint { let mut tap_offloads: c_uint = 0; if features & (1 << VIRTIO_NET_F_GUEST_CSUM) != 0 { - tap_offloads |= net_gen::TUN_F_CSUM; + tap_offloads |= libc::TUN_F_CSUM; } if features & (1 << VIRTIO_NET_F_GUEST_TSO4) != 0 { - tap_offloads |= net_gen::TUN_F_TSO4; + tap_offloads |= libc::TUN_F_TSO4; } if features & (1 << VIRTIO_NET_F_GUEST_TSO6) != 0 { - tap_offloads |= net_gen::TUN_F_TSO6; + tap_offloads |= libc::TUN_F_TSO6; } if features & (1 << VIRTIO_NET_F_GUEST_ECN) != 0 { - tap_offloads |= net_gen::TUN_F_TSO_ECN; + tap_offloads |= libc::TUN_F_TSO_ECN; } if features & (1 << VIRTIO_NET_F_GUEST_UFO) != 0 { - tap_offloads |= net_gen::TUN_F_UFO; + tap_offloads |= libc::TUN_F_UFO; } tap_offloads @@ -194,7 +194,7 @@ mod unit_tests { let addr: net::Ipv4Addr = "10.0.0.1".parse().unwrap(); let sockaddr = create_sockaddr(addr); - assert_eq!(sockaddr.sa_family, net_gen::AF_INET as u16); + assert_eq!(sockaddr.sa_family, libc::AF_INET as u16); let data = &sockaddr.sa_data[..]; diff --git a/net_util/src/open_tap.rs b/net_util/src/open_tap.rs index 39d4285df3..a5168d22a0 100644 --- a/net_util/src/open_tap.rs +++ b/net_util/src/open_tap.rs @@ -48,11 +48,11 @@ fn check_mq_support(if_name: &Option<&str>, queue_pairs: usize) -> Result<()> { return Ok(()); } let tun_flags_str = fs::read_to_string(path).map_err(Error::ReadSysfsTunFlags)?; - let tun_flags = u32::from_str_radix(tun_flags_str.trim().trim_start_matches("0x"), 16) + let tun_flags = i32::from_str_radix(tun_flags_str.trim().trim_start_matches("0x"), 16) .map_err(Error::ConvertHexStringToInt)?; - if (tun_flags & net_gen::IFF_MULTI_QUEUE != 0) && !mq { + if (tun_flags & libc::IFF_MULTI_QUEUE != 0) && !mq { return Err(Error::MultiQueueNoDeviceSupport); - } else if (tun_flags & net_gen::IFF_MULTI_QUEUE == 0) && mq { + } else if (tun_flags & libc::IFF_MULTI_QUEUE == 0) && mq { return Err(Error::MultiQueueNoTapSupport); } } diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 1622add3a6..43b42a4a2e 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -5,12 +5,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the THIRD-PARTY file. +use std::ffi::{CStr, CString}; use std::fs::File; use std::io::{Error as IoError, Read, Result as IoResult, Write}; use std::net::{IpAddr, Ipv6Addr}; use std::os::raw::*; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use libc::{__c_anonymous_ifr_ifru, ifreq}; use thiserror::Error; use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val}; @@ -43,6 +45,8 @@ pub enum Error { NetUtil(#[source] NetUtilError), #[error("Interface name too long (max length is {MAX_INTERFACE_NAME_LEN}): {0}")] IfnameTooLong(String), + #[error("Interface name contains interior NUL byte: {0:?}")] + IfnameContainsNUL(String), #[error("Invalid interface name (does it exist?): {0}")] InvalidIfname(String), #[error("Error parsing MAC data")] @@ -62,7 +66,8 @@ pub type Result = ::std::result::Result; #[derive(Debug)] pub struct Tap { tap_file: File, - if_name: Vec, + /// The name does not exceed [`MAX_INTERFACE_NAME_LEN`] bytes excluding the NUL byte. + if_name: CString, } impl PartialEq for Tap { @@ -80,23 +85,6 @@ impl std::clone::Clone for Tap { } } -// Returns a byte vector representing the contents of a null terminated C string which -// contains if_name. -fn build_terminated_if_name(if_name: &str) -> Result> { - // Convert the string slice to bytes, and shadow the variable, - // since we no longer need the &str version. - let bytes = if_name.as_bytes(); - - if bytes.len() > MAX_INTERFACE_NAME_LEN { - return Err(Error::IfnameTooLong(if_name.to_string())); - } - - let mut terminated_if_name = vec![b'\0'; bytes.len() + 1]; - terminated_if_name[..bytes.len()].copy_from_slice(bytes); - - Ok(terminated_if_name) -} - fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result { let mask = mask.segments(); let mut iter = mask.iter(); @@ -169,7 +157,12 @@ impl Tap { } pub fn open_named(if_name: &str, num_queue_pairs: usize, flags: Option) -> Result { - let terminated_if_name = build_terminated_if_name(if_name)?; + if if_name.len() > MAX_INTERFACE_NAME_LEN { + return Err(Error::IfnameTooLong(if_name.to_string())); + } + + let terminated_if_name = + CString::new(if_name).map_err(|_| Error::IfnameContainsNUL(if_name.to_string()))?; // SAFETY: FFI call let fd = unsafe { @@ -192,42 +185,48 @@ impl Tap { // value. let mut features = 0; // SAFETY: IOCTL with correct arguments - let ret = unsafe { ioctl_with_mut_ref(&tuntap, net_gen::TUNGETFEATURES(), &mut features) }; + let ret = + unsafe { ioctl_with_mut_ref(&tuntap, libc::TUNGETFEATURES as c_ulong, &mut features) }; if ret < 0 { return Err(Error::GetFeatures(IoError::last_os_error())); } // Check if the user parameters match the kernel support for MQ - if (features & net_gen::IFF_MULTI_QUEUE == 0) && num_queue_pairs > 1 { + if (features & libc::IFF_MULTI_QUEUE == 0) && num_queue_pairs > 1 { return Err(Error::MultiQueueKernelSupport); } - // This is pretty messy because of the unions used by ifreq. Since we - // don't call as_mut on the same union field more than once, this block - // is safe. - let mut ifreq: net_gen::ifreq = Default::default(); - // SAFETY: see the comment above. - unsafe { - let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut(); - let name_slice = &mut ifrn_name[..terminated_if_name.len()]; - name_slice.copy_from_slice(terminated_if_name.as_slice()); - ifreq.ifr_ifru.ifru_flags = - (net_gen::IFF_TAP | net_gen::IFF_NO_PI | net_gen::IFF_VNET_HDR) as c_short; - if num_queue_pairs > 1 { - ifreq.ifr_ifru.ifru_flags |= net_gen::IFF_MULTI_QUEUE as c_short; - } + let mut ifru_flags = (libc::IFF_TAP | libc::IFF_NO_PI | libc::IFF_VNET_HDR) as c_short; + if num_queue_pairs > 1 { + ifru_flags |= libc::IFF_MULTI_QUEUE as c_short; } + let mut ifreq = libc::ifreq { + ifr_name: [0; libc::IFNAMSIZ], + ifr_ifru: __c_anonymous_ifr_ifru { ifru_flags }, + }; + + // Convert and copy bytes to `ifr_name` buffer. + // `terminated_if_name` will fit into `ifr_name` since we enforce the length limit + // above. + ifreq + .ifr_name + .iter_mut() + .zip(terminated_if_name.as_bytes_with_nul()) + .for_each(|(ifr_name_char, terminated_if_name_byte)| { + *ifr_name_char = *terminated_if_name_byte as c_char; + }); + // SAFETY: ioctl is safe since we call it with a valid tap fd and check the return // value. - let ret = unsafe { ioctl_with_mut_ref(&tuntap, net_gen::TUNSETIFF(), &mut ifreq) }; + let ret = unsafe { ioctl_with_mut_ref(&tuntap, libc::TUNSETIFF as c_ulong, &mut ifreq) }; if ret < 0 { return Err(Error::ConfigureTap(IoError::last_os_error())); } - // SAFETY: only the name is accessed, and it's cloned out. - let mut if_name = unsafe { ifreq.ifr_ifrn.ifrn_name }.to_vec(); - if_name.truncate(terminated_if_name.len() - 1); + // SAFETY: `ifreq.ifr_name` is set by the `ioctl_with_mut_ref` call and we checked the + // return code, so the name must be a valid `CStr`. + let if_name = unsafe { CStr::from_ptr(ifreq.ifr_name.as_ptr()) }.to_owned(); Ok(Tap { tap_file: tuntap, if_name, @@ -254,27 +253,36 @@ impl Tap { // SAFETY: fd is a tap fd let tap_file = unsafe { File::from_raw_fd(fd) }; - let mut ifreq: net_gen::ifreq = Default::default(); + let mut ifreq: libc::ifreq = ifreq { + ifr_name: [0; libc::IFNAMSIZ], + ifr_ifru: __c_anonymous_ifr_ifru { ifru_flags: 0 }, + }; // Get current config including name // SAFETY: IOCTL with correct arguments - unsafe { Self::ioctl_with_mut_ref(&tap_file, net_gen::TUNGETIFF(), &mut ifreq)? }; - - // SAFETY: We only access one field of the ifru union - let if_name = unsafe { ifreq.ifr_ifrn.ifrn_name }.to_vec(); + unsafe { Self::ioctl_with_mut_ref(&tap_file, libc::TUNGETIFF as c_ulong, &mut ifreq)? }; + + let if_name = { + let ifr_ptr = ifreq.ifr_name.as_ptr(); + // SAFETY: The `ifr_name` field of the union is a valid, nul-terminated C string since it + // was just set by the ioctl call, and we checked for errors. + // We immediately convert the `CStr` to the owned `CString, so the memory of the union field + // is not accessed or mutated during the lifetime of the `Cstr`. + unsafe { CStr::from_ptr(ifr_ptr).to_owned() } + }; // Try and update flags. Depending on how the tap was created (macvtap // or via open_named()) this might return -EEXIST so we just ignore that. // SAFETY: access union fields unsafe { ifreq.ifr_ifru.ifru_flags = - (net_gen::IFF_TAP | net_gen::IFF_NO_PI | net_gen::IFF_VNET_HDR) as c_short; + (libc::IFF_TAP | libc::IFF_NO_PI | libc::IFF_VNET_HDR) as c_short; if num_queue_pairs > 1 { - ifreq.ifr_ifru.ifru_flags |= net_gen::IFF_MULTI_QUEUE as c_short; + ifreq.ifr_ifru.ifru_flags |= libc::IFF_MULTI_QUEUE as c_short; } } // SAFETY: IOCTL with correct arguments - let ret = unsafe { ioctl_with_mut_ref(&tap_file, net_gen::TUNSETIFF(), &mut ifreq) }; + let ret = unsafe { ioctl_with_mut_ref(&tap_file, libc::TUNSETIFF as c_ulong, &mut ifreq) }; if ret < 0 && IoError::last_os_error().raw_os_error().unwrap() != libc::EEXIST { return Err(Error::ConfigureTap(IoError::last_os_error())); } @@ -300,7 +308,7 @@ impl Tap { // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. unsafe { - Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFADDR as c_ulong, &ifreq)?; + Self::ioctl_with_ref(&sock, libc::SIOCSIFADDR as c_ulong, &ifreq)?; } if let Some(IpAddr::V4(mask)) = netmask { @@ -308,11 +316,7 @@ impl Tap { // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. unsafe { - Self::ioctl_with_ref( - &sock, - net_gen::sockios::SIOCSIFNETMASK as c_ulong, - &ifreq, - )?; + Self::ioctl_with_ref(&sock, libc::SIOCSIFNETMASK as c_ulong, &ifreq)?; } } @@ -322,18 +326,14 @@ impl Tap { let ifindex = { // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. unsafe { - Self::ioctl_with_ref( - &sock, - net_gen::sockios::SIOCGIFINDEX as c_ulong, - &ifreq, - )?; + Self::ioctl_with_ref(&sock, libc::SIOCGIFINDEX as c_ulong, &ifreq)?; } // SAFETY: ifru_ivalue contains the ifindex and is set by the previous ioctl unsafe { - match ifreq.ifr_ifru.ifru_ivalue { + match ifreq.ifr_ifru.ifru_ifindex { 0 => { - let name = String::from_utf8_lossy(&self.if_name).to_string(); + let name = self.if_name.to_string_lossy().to_string(); return Err(Error::InvalidIfname(name)); } i => i, @@ -347,19 +347,17 @@ impl Tap { None => 0, }; - let ifreq = net_gen::in6_ifreq { + let ifreq = libc::in6_ifreq { // SAFETY: addr can be safely transmuted to in6_addr ifr6_addr: unsafe { - std::mem::transmute::<[u8; 16], net_gen::ipv6::in6_addr>(addr.octets()) + std::mem::transmute::<[u8; 16], libc::in6_addr>(addr.octets()) }, ifr6_prefixlen: prefixlen as u32, ifr6_ifindex: ifindex, }; // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. - unsafe { - Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFADDR as c_ulong, &ifreq) - } + unsafe { Self::ioctl_with_ref(&sock, libc::SIOCSIFADDR as c_ulong, &ifreq) } } } } @@ -380,18 +378,18 @@ impl Tap { let mut ifreq = self.get_ifreq(); // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. - unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFHWADDR as c_ulong, &ifreq)? }; + unsafe { Self::ioctl_with_ref(&sock, libc::SIOCGIFHWADDR as c_ulong, &ifreq)? }; // SAFETY: We only access one field of the ifru union unsafe { let ifru_hwaddr = &mut ifreq.ifr_ifru.ifru_hwaddr; for (i, v) in addr.get_bytes().iter().enumerate() { - ifru_hwaddr.sa_data[i] = *v as c_uchar; + ifru_hwaddr.sa_data[i] = *v as c_char; } } // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. - unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFHWADDR as c_ulong, &ifreq) } + unsafe { Self::ioctl_with_ref(&sock, libc::SIOCSIFHWADDR as c_ulong, &ifreq) } } /// Get mac addr for tap interface. @@ -401,12 +399,21 @@ impl Tap { let ifreq = self.get_ifreq(); // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. - unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFHWADDR as c_ulong, &ifreq)? }; - - // SAFETY: We only access one field of the ifru union - let addr = unsafe { - MacAddr::from_bytes(&ifreq.ifr_ifru.ifru_hwaddr.sa_data[0..MAC_ADDR_LEN]) - .map_err(Error::MacParsing)? + unsafe { Self::ioctl_with_ref(&sock, libc::SIOCGIFHWADDR as c_ulong, &ifreq)? }; + + let addr = { + let bytes: Vec = + // SAFETY: The `ioctl_with_ref` ensures accessing `ifru_hwaddr` is valid. + unsafe { ifreq.ifr_ifru.ifru_hwaddr.sa_data[0..MAC_ADDR_LEN].iter() } + .map(|byte| { + // On some architectures, `c_char` is already a `u8`. + #[allow(clippy::unnecessary_cast)] + { + *byte as u8 + } + }) + .collect(); + MacAddr::from_bytes(&bytes).map_err(Error::MacParsing)? }; Ok(addr) } @@ -418,7 +425,7 @@ impl Tap { let ifreq = self.get_ifreq(); // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. - unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFMTU as c_ulong, &ifreq)? }; + unsafe { Self::ioctl_with_ref(&sock, libc::SIOCGIFMTU as c_ulong, &ifreq)? }; // SAFETY: access a union field let mtu = unsafe { ifreq.ifr_ifru.ifru_mtu }; @@ -439,13 +446,19 @@ impl Tap { ifreq.ifr_ifru.ifru_mtu = mtu; // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. - unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFMTU as c_ulong, &ifreq) } + unsafe { Self::ioctl_with_ref(&sock, libc::SIOCSIFMTU as c_ulong, &ifreq) } } /// Set the offload flags for the tap interface. pub fn set_offload(&self, flags: c_uint) -> Result<()> { // SAFETY: ioctl is safe. Called with a valid tap fd, and we check the return. - unsafe { Self::ioctl_with_val(&self.tap_file, net_gen::TUNSETOFFLOAD(), flags as c_ulong) } + unsafe { + Self::ioctl_with_val( + &self.tap_file, + libc::TUNSETOFFLOAD as c_ulong, + flags as c_ulong, + ) + } } /// Enable the tap interface. @@ -455,48 +468,44 @@ impl Tap { let mut ifreq = self.get_ifreq(); // SAFETY: IOCTL with correct arguments - unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFFLAGS as c_ulong, &ifreq)? }; + unsafe { Self::ioctl_with_ref(&sock, libc::SIOCGIFFLAGS as c_ulong, &ifreq)? }; // If TAP device is already up don't try and enable it // SAFETY: access a union field let ifru_flags = unsafe { ifreq.ifr_ifru.ifru_flags }; - if ifru_flags & net_gen::net_device_flags_IFF_UP as i16 - == net_gen::net_device_flags_IFF_UP as i16 - { + if ifru_flags & libc::IFF_UP as i16 == libc::IFF_UP as i16 { return Ok(()); } - ifreq.ifr_ifru.ifru_flags = net_gen::net_device_flags_IFF_UP as i16; + ifreq.ifr_ifru.ifru_flags = libc::IFF_UP as i16; // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. - unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFFLAGS as c_ulong, &ifreq) } + unsafe { Self::ioctl_with_ref(&sock, libc::SIOCSIFFLAGS as c_ulong, &ifreq) } } /// Set the size of the vnet hdr. pub fn set_vnet_hdr_size(&self, size: c_int) -> Result<()> { // SAFETY: ioctl is safe. Called with a valid tap fd, and we check the return. - unsafe { Self::ioctl_with_ref(&self.tap_file, net_gen::TUNSETVNETHDRSZ(), &size) } + unsafe { Self::ioctl_with_ref(&self.tap_file, libc::TUNSETVNETHDRSZ as c_ulong, &size) } } - fn get_ifreq(&self) -> net_gen::ifreq { - let mut ifreq: net_gen::ifreq = Default::default(); - - // This sets the name of the interface, which is the only entry - // in a single-field union. - // SAFETY: access union fields and we're sure the copy is okay. - unsafe { - let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut(); - let name_slice = &mut ifrn_name[..self.if_name.len()]; - name_slice.copy_from_slice(&self.if_name); - } + fn get_ifreq(&self) -> libc::ifreq { + let mut ifreq: libc::ifreq = libc::ifreq { + ifr_name: [0; libc::IFNAMSIZ], + ifr_ifru: __c_anonymous_ifr_ifru { ifru_flags: 0 }, + }; + // Convert and copy bytes to `ifr_name` buffer. + // `self.if_name` will fit into `ifr_name` since we enforce the length when setting it. ifreq - } + .ifr_name + .iter_mut() + .zip(self.if_name.as_bytes_with_nul()) + .for_each(|(ifr_name_char, terminated_if_name_byte)| { + *ifr_name_char = *terminated_if_name_byte as c_char; + }); - /// Returns the raw bytes of the interface name, which may or may not be - /// valid UTF-8. - pub fn if_name_as_bytes(&self) -> &[u8] { - &self.if_name + ifreq } /// Returns the interface name as a string, truncated at the first NUL byte @@ -509,19 +518,20 @@ impl Tap { /// thus valid UTF-8. Also, self-generated interface names form CHV are /// also always created from Rust strings, thus valid UTF-8. pub fn if_name_as_str(&self) -> &str { - // All bytes until first NUL. - let nul_terminated = self - .if_name_as_bytes() - .split(|&b| b == 0) - .next() - .unwrap_or(&[]); - // Panicking here is fine, see function documentation. - std::str::from_utf8(nul_terminated).expect("Tap interface name should be valid UTF-8") + std::str::from_utf8(self.if_name.as_bytes()) + .expect("Tap interface name should be valid UTF-8") } #[cfg(fuzzing)] - pub fn new_for_fuzzing(tap_file: File, if_name: Vec) -> Self { + pub fn new_for_fuzzing(tap_file: File, if_name: &str) -> Self { + if if_name.len() > MAX_INTERFACE_NAME_LEN { + panic!("provided name longer than `MAX_INTERFACE_NAME_LEN`") + } + + let if_name = CString::new(if_name) + .map_err(|_| Error::IfnameContainsNUL(if_name.to_string())) + .unwrap(); Tap { tap_file, if_name } } } diff --git a/tpm/Cargo.toml b/tpm/Cargo.toml index 5d6bba1a04..dd87f99371 100644 --- a/tpm/Cargo.toml +++ b/tpm/Cargo.toml @@ -10,7 +10,6 @@ version = "0.1.0" anyhow = { workspace = true } libc = { workspace = true } log = { workspace = true } -net_gen = { path = "../net_gen" } thiserror = { workspace = true } vmm-sys-util = { workspace = true } diff --git a/tpm/src/emulator.rs b/tpm/src/emulator.rs index b27a069aa0..0ffd3a62e4 100644 --- a/tpm/src/emulator.rs +++ b/tpm/src/emulator.rs @@ -150,16 +150,16 @@ impl Emulator { // SAFETY: FFI calls and return value of the unsafe call is checked unsafe { - let tv = net_gen::iff::timeval { + let tv = libc::timeval { tv_sec: 0, tv_usec: 100000, // Set recv timeout to 100ms }; - let ret = net_gen::setsockopt( + let ret = libc::setsockopt( fds[0], - net_gen::iff::SOL_SOCKET as i32, - net_gen::iff::SO_RCVTIMEO as i32, + libc::SOL_SOCKET, + libc::SO_RCVTIMEO, &tv as *const _ as *const libc::c_void, - std::mem::size_of::() as u32, + std::mem::size_of::() as u32, ); if ret == -1 { return Err(Error::PrepareDataFd(anyhow!( From 27a40ed32b9cbaed2c65a5779fddf0a5b78d557b Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 18 Mar 2026 15:47:23 +0100 Subject: [PATCH 0839/1893] ci: remove `net_gen` from allowed titles On-behalf-of: SAP julian.schindel@sap.com Signed-off-by: Julian Schindel --- scripts/gitlint/rules/TitleStartsWithComponent.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/gitlint/rules/TitleStartsWithComponent.py b/scripts/gitlint/rules/TitleStartsWithComponent.py index 1310e45ccd..a25172a629 100644 --- a/scripts/gitlint/rules/TitleStartsWithComponent.py +++ b/scripts/gitlint/rules/TitleStartsWithComponent.py @@ -46,7 +46,6 @@ def validate(self, line, _commit): 'hypervisor', 'main', 'misc', - 'net_gen', 'net_util', 'openapi', 'option_parser', From edfbb7e18086e898fea282b4d350b26c5c792739 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 23 Mar 2026 12:02:14 +0100 Subject: [PATCH 0840/1893] 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 Signed-off-by: Anatol Belski --- block/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 81b3f27c00..e6b23b3870 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -75,6 +75,7 @@ pub const MAX_DISCARD_WRITE_ZEROES_SEG: u32 = 1; /// Size and field offsets within `struct virtio_blk_discard_write_zeroes`. const DISCARD_WZ_SEG_SIZE: u32 = mem::size_of::() as u32; +const DISCARD_WZ_MAX_PAYLOAD: u32 = DISCARD_WZ_SEG_SIZE * MAX_DISCARD_WRITE_ZEROES_SEG; const DISCARD_WZ_SECTOR_OFFSET: u64 = mem::offset_of!(virtio_blk_discard_write_zeroes, sector) as u64; const DISCARD_WZ_NUM_SECTORS_OFFSET: u64 = @@ -600,7 +601,7 @@ impl Request { if data_len < DISCARD_WZ_SEG_SIZE { return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); } - if data_len > DISCARD_WZ_SEG_SIZE * MAX_DISCARD_WRITE_ZEROES_SEG { + if data_len > DISCARD_WZ_MAX_PAYLOAD { return Err(ExecuteError::BadRequest(Error::TooManySegments)); } @@ -649,7 +650,7 @@ impl Request { if data_len < DISCARD_WZ_SEG_SIZE { return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); } - if data_len > DISCARD_WZ_SEG_SIZE * MAX_DISCARD_WRITE_ZEROES_SEG { + if data_len > DISCARD_WZ_MAX_PAYLOAD { return Err(ExecuteError::BadRequest(Error::TooManySegments)); } From 92109136f16dcc664ffa7e9a0bdc16ee2e3b1719 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 23 Mar 2026 12:04:08 +0100 Subject: [PATCH 0841/1893] 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 Signed-off-by: Anatol Belski --- block/src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index e6b23b3870..0640611c78 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -110,8 +110,8 @@ pub enum Error { RawFileError(#[source] std::io::Error), #[error("The requested operation does not support multiple descriptors")] TooManyDescriptors, - #[error("Request contains too many segments")] - TooManySegments, + #[error("Request contains too many segments ({0}, max {MAX_DISCARD_WRITE_ZEROES_SEG})")] + TooManySegments(u32), #[error("Failure in vhdx")] VhdxError(#[source] VhdxError), } @@ -602,7 +602,9 @@ impl Request { return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); } if data_len > DISCARD_WZ_MAX_PAYLOAD { - return Err(ExecuteError::BadRequest(Error::TooManySegments)); + return Err(ExecuteError::BadRequest(Error::TooManySegments( + data_len.div_ceil(DISCARD_WZ_SEG_SIZE), + ))); } let mut discard_sector = [0u8; 8]; @@ -651,7 +653,9 @@ impl Request { return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); } if data_len > DISCARD_WZ_MAX_PAYLOAD { - return Err(ExecuteError::BadRequest(Error::TooManySegments)); + return Err(ExecuteError::BadRequest(Error::TooManySegments( + data_len.div_ceil(DISCARD_WZ_SEG_SIZE), + ))); } let mut wz_sector = [0u8; 8]; From 882f82f04bffa3ae9dc3591217aff40787528958 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Fri, 20 Mar 2026 16:17:16 +0100 Subject: [PATCH 0842/1893] 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 --- virtio-devices/src/net.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index ec8afc2cc7..3bb360c646 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -752,7 +752,15 @@ impl VirtioDevice for Net { let num_queues = queues.len(); let event_idx = self.common.feature_acked(VIRTIO_RING_F_EVENT_IDX.into()); - if self.common.feature_acked(VIRTIO_NET_F_CTRL_VQ.into()) && !num_queues.is_multiple_of(2) { + + // Recompute the barrier size from the queues that are actually activated. + let has_ctrl_queue = + self.common.feature_acked(VIRTIO_NET_F_CTRL_VQ.into()) && !num_queues.is_multiple_of(2); + let ctrl_threads = if has_ctrl_queue { 1 } else { 0 }; + let qp_threads = (num_queues - ctrl_threads) / 2; + self.common.paused_sync = Some(Arc::new(Barrier::new(1 + qp_threads + ctrl_threads))); + + if has_ctrl_queue { let ctrl_queue_index = num_queues - 1; let (_, mut ctrl_queue, ctrl_queue_evt) = queues.remove(ctrl_queue_index); @@ -772,10 +780,6 @@ impl VirtioDevice for Net { }; let paused = self.common.paused.clone(); - // Let's update the barrier as we need 1 for each RX/TX pair + - // 1 for the control queue + 1 for the main thread signalling - // the pause. - self.common.paused_sync = Some(Arc::new(Barrier::new(self.taps.len() + 2))); let paused_sync = self.common.paused_sync.clone(); let mut epoll_threads = Vec::new(); From 5aeb9f55d190abd9a1d220b12d799a020b9800f1 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Fri, 20 Mar 2026 16:21:02 +0100 Subject: [PATCH 0843/1893] 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 --- virtio-devices/src/block.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 97d7c58c15..47309dd877 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -1087,6 +1087,9 @@ impl VirtioDevice for Block { } self.common.activate(&queues, interrupt_cb.clone())?; + // Recompute the barrier size from the queues that are actually activated. + self.common.paused_sync = Some(Arc::new(Barrier::new(queues.len() + 1))); + self.update_writeback(); let mut epoll_threads = Vec::new(); From 7e0f8f7163d79982ad0491a4ec7702006fab9929 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 00:05:50 +0000 Subject: [PATCH 0844/1893] 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] --- Cargo.lock | 156 ++++++++---------------------------------- Cargo.toml | 2 +- fuzz/Cargo.lock | 16 ++--- hypervisor/Cargo.toml | 2 +- 4 files changed, 39 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 048c911371..b009dfa901 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,21 +35,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse 0.2.7", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstream" version = "1.0.0" @@ -57,7 +42,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", - "anstyle-parse 1.0.0", + "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -71,15 +56,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - [[package]] name = "anstyle-parse" version = "1.0.0" @@ -95,7 +71,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -106,7 +82,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -125,9 +101,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" dependencies = [ "rustversion", ] @@ -204,7 +180,7 @@ dependencies = [ "polling", "rustix", "slab", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -262,7 +238,7 @@ dependencies = [ "rustix", "signal-hook-registry", "slab", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -436,7 +412,7 @@ version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream 1.0.0", + "anstream", "anstyle", "clap_lex", "strsim", @@ -641,7 +617,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -679,9 +655,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -689,11 +665,11 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ - "anstream 0.6.21", + "anstream", "anstyle", "env_filter", "jiff", @@ -723,7 +699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1724,7 +1700,7 @@ dependencies = [ "hermit-abi", "pin-project-lite", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1907,7 +1883,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -2115,17 +2091,17 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] name = "terminal_size" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -2171,18 +2147,18 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.5+spec-1.1.0" +version = "0.25.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" dependencies = [ "indexmap", "toml_datetime", @@ -2192,9 +2168,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ "winnow 1.0.0", ] @@ -2259,7 +2235,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -2726,15 +2702,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -2744,71 +2711,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.7.15" @@ -2943,7 +2845,7 @@ dependencies = [ "tracing", "uds_windows", "uuid", - "windows-sys 0.61.2", + "windows-sys", "winnow 0.7.15", "zbus_macros", "zbus_names", diff --git a/Cargo.toml b/Cargo.toml index 39a79ba815..b8320172e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,7 @@ cfg-if = "1.0.4" clap = "4.6.0" 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" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 475f0b8344..d5cda1e24f 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -81,9 +81,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" dependencies = [ "rustversion", ] @@ -1192,18 +1192,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.5+spec-1.1.0" +version = "0.25.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" dependencies = [ "indexmap", "toml_datetime", @@ -1213,9 +1213,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ "winnow", ] diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 707779fefc..1ffaa46b78 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -15,7 +15,7 @@ tdx = [] [dependencies] anyhow = { workspace = true } -arc-swap = "1.8.2" +arc-swap = "1.9.0" bitfield-struct = "0.12.0" byteorder = { workspace = true } cfg-if = { workspace = true } From 040fcaed92f7629f5a0d9ae57b9cb175a52e5e70 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 17 Mar 2026 11:11:54 +0100 Subject: [PATCH 0845/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- cloud-hypervisor/src/bin/ch-remote.rs | 38 +++++++----------- cloud-hypervisor/tests/integration.rs | 13 +++---- vmm/src/api/mod.rs | 55 ++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 33 deletions(-) diff --git a/cloud-hypervisor/src/bin/ch-remote.rs b/cloud-hypervisor/src/bin/ch-remote.rs index 6939eaa385..afc41e7e96 100644 --- a/cloud-hypervisor/src/bin/ch-remote.rs +++ b/cloud-hypervisor/src/bin/ch-remote.rs @@ -16,7 +16,9 @@ use api_client::{ Error as ApiClientError, simple_api_command, simple_api_command_with_fds, simple_api_full_command, }; -use clap::{Arg, ArgAction, ArgMatches, Command}; +#[cfg(feature = "dbus_api")] +use clap::ArgAction; +use clap::{Arg, ArgMatches, Command}; use log::error; use option_parser::{ByteSized, ByteSizedParseError}; use thiserror::Error; @@ -69,6 +71,8 @@ enum Error { ReadingFile(#[source] std::io::Error), #[error("Invalid disk size")] InvalidDiskSize(#[source] ByteSizedParseError), + #[error("Error parsing send migration configuration")] + SendMigrationConfig(#[from] vmm::api::VmSendMigrationParseError), } enum TargetApi<'a> { @@ -519,11 +523,7 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu .unwrap() .get_one::("send_migration_config") .unwrap(), - matches - .subcommand_matches("send-migration") - .unwrap() - .get_flag("send_migration_local"), - ); + )?; simple_api_command(socket, "PUT", "send-migration", Some(&send_migration_data)) .map_err(Error::HttpApiClient) } @@ -743,11 +743,7 @@ fn dbus_api_do_command(matches: &ArgMatches, proxy: &DBusApi1ProxyBlocking<'_>) .unwrap() .get_one::("send_migration_config") .unwrap(), - matches - .subcommand_matches("send-migration") - .unwrap() - .get_flag("send_migration_local"), - ); + )?; proxy.api_vm_send_migration(&send_migration_data) } Some("receive-migration") => { @@ -953,13 +949,11 @@ fn receive_migration_data(url: &str) -> String { serde_json::to_string(&receive_migration_data).unwrap() } -fn send_migration_data(url: &str, local: bool) -> String { - let send_migration_data = vmm::api::VmSendMigrationData { - destination_url: url.to_owned(), - local, - }; - - serde_json::to_string(&send_migration_data).unwrap() +fn send_migration_data(config: &str) -> Result { + let send_migration_data = + vmm::api::VmSendMigrationData::parse(config).map_err(Error::SendMigrationConfig)?; + let send_migration_config = serde_json::to_string(&send_migration_data).unwrap(); + Ok(send_migration_config) } fn create_data(path: &str) -> Result { @@ -1141,13 +1135,7 @@ fn get_cli_commands_sorted() -> Box<[Command]> { .arg( Arg::new("send_migration_config") .index(1) - .help(""), - ) - .arg( - Arg::new("send_migration_local") - .long("local") - .num_args(0) - .action(ArgAction::SetTrue), + .help(vmm::api::VmSendMigrationData::SYNTAX), ), Command::new("shutdown").about("Shutdown the VM"), Command::new("shutdown-vmm").about("Shutdown the VMM"), diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 4b8a6b2e37..5454cae6b6 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -9891,17 +9891,16 @@ mod live_migration { thread::sleep(std::time::Duration::new(1, 0)); // Start to send migration from the source VM - let mut args = [ + let args = [ format!("--api-socket={}", &src_api_socket), "send-migration".to_string(), - format! {"unix:{migration_socket}"}, + format!( + "destination_url=unix:{migration_socket},local={}", + if local { "on" } else { "off" } + ), ] .to_vec(); - if local { - args.insert(2, "--local".to_string()); - } - let mut send_migration = Command::new(clh_command("ch-remote")) .args(&args) .stderr(Stdio::piped()) @@ -11066,7 +11065,7 @@ mod live_migration { .args([ &format!("--api-socket={src_api_socket}"), "send-migration", - &format!("tcp:{host_ip}:{migration_port}"), + &format!("destination_url=tcp:{host_ip}:{migration_port}"), ]) .stdin(Stdio::null()) .stderr(Stdio::piped()) diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 17ba0011b0..fd290a8a7c 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -38,6 +38,7 @@ use std::sync::mpsc::{RecvError, SendError, Sender, channel}; use log::info; use micro_http::Body; +use option_parser::{OptionParser, OptionParserError, Toggle}; use serde::{Deserialize, Serialize}; use thiserror::Error; use vm_migration::MigratableError; @@ -266,7 +267,12 @@ pub struct VmReceiveMigrationData { pub receiver_url: String, } -#[derive(Clone, Deserialize, Serialize, Default, Debug)] +#[derive(Debug, Error)] +#[error("Error parsing send migration parameters")] +pub struct VmSendMigrationParseError(#[source] OptionParserError); + +/// Configuration for an outgoing migration. +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct VmSendMigrationData { /// URL to migrate the VM to pub destination_url: String, @@ -275,6 +281,33 @@ pub struct VmSendMigrationData { pub local: bool, } +impl VmSendMigrationData { + pub const SYNTAX: &'static str = "VM send migration parameters \ + \"destination_url=[,local=on|off]\""; + + pub fn parse(migration: &str) -> Result { + let mut parser = OptionParser::new(); + parser.add("destination_url").add("local"); + parser.parse(migration).map_err(VmSendMigrationParseError)?; + + let destination_url = parser.get("destination_url").ok_or_else(|| { + VmSendMigrationParseError(OptionParserError::InvalidSyntax( + "destination_url is required".to_string(), + )) + })?; + let local = parser + .convert::("local") + .map_err(VmSendMigrationParseError)? + .unwrap_or(Toggle(false)) + .0; + + Ok(Self { + destination_url, + local, + }) + } +} + pub enum ApiResponsePayload { /// No data is sent on the channel. Empty, @@ -1541,3 +1574,23 @@ impl ApiAction for VmNmi { get_response_body(self, api_evt, api_sender, data) } } + +#[cfg(test)] +mod unit_tests { + use super::*; + + #[test] + fn test_vm_send_migration_data_parse() { + // Fully specified + let data = VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,local=on") + .expect("valid migration string should parse"); + assert_eq!(data.destination_url, "tcp://192.168.1.1:8080"); + assert!(data.local); + + // Unknown option is an error + VmSendMigrationData::parse("destination_url=unix:/tmp/sock,unknown_field=foo").unwrap_err(); + + // Invalid toggle value is an error + VmSendMigrationData::parse("destination_url=unix:/tmp/sock,local=yes").unwrap_err(); + } +} From bbb0f083b01cf88bfcb4e2d66f78fe7681158cf4 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 13 Mar 2026 23:26:28 +0100 Subject: [PATCH 0846/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/api/mod.rs | 165 +++++++++++++++++++++++++++++++++++++++++++-- vmm/src/lib.rs | 12 +++- 2 files changed, 170 insertions(+), 7 deletions(-) diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index fd290a8a7c..4b51c7eb3d 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -34,7 +34,10 @@ pub mod dbus; pub mod http; use std::io; +use std::num::NonZeroU64; +use std::str::FromStr; use std::sync::mpsc::{RecvError, SendError, Sender, channel}; +use std::time::Duration; use log::info; use micro_http::Body; @@ -267,27 +270,83 @@ pub struct VmReceiveMigrationData { pub receiver_url: String, } +#[derive(Copy, Clone, Default, Deserialize, Serialize, Debug, PartialEq, Eq)] +/// The migration timeout strategy. +/// +/// This strategy describes the behavior of the migration when the target +/// downtime can't be reached in the given timeout. +pub enum TimeoutStrategy { + #[default] + /// Cancel the migration and keep the VM running on the source. + Cancel, + /// Ignore the timeout and migrate anyway. + Ignore, +} + +impl FromStr for TimeoutStrategy { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "cancel" => Ok(TimeoutStrategy::Cancel), + "ignore" => Ok(TimeoutStrategy::Ignore), + _ => Err(format!("Invalid timeout strategy: {s}")), + } + } +} + #[derive(Debug, Error)] #[error("Error parsing send migration parameters")] pub struct VmSendMigrationParseError(#[source] OptionParserError); /// Configuration for an outgoing migration. #[derive(Clone, Deserialize, Serialize, Debug)] +#[cfg_attr(test, derive(PartialEq))] pub struct VmSendMigrationData { /// URL to migrate the VM to pub destination_url: String, /// Send memory across socket without copying #[serde(default)] pub local: bool, + /// The maximum downtime the migration aims for. + /// + /// Usually, on the order of a few hundred milliseconds. + #[serde(default = "VmSendMigrationData::default_downtime_ms")] + downtime_ms: NonZeroU64, + /// The timeout for the migration, i.e., the maximum duration. + #[serde(default = "VmSendMigrationData::default_timeout_s")] + timeout_s: NonZeroU64, + /// The timeout strategy for the migration. + #[serde(default)] + pub timeout_strategy: TimeoutStrategy, } impl VmSendMigrationData { pub const SYNTAX: &'static str = "VM send migration parameters \ - \"destination_url=[,local=on|off]\""; + \"destination_url=[,local=on|off,\ + downtime_ms=,timeout_s=,\ + timeout_strategy=cancel|ignore]\""; + + // Same as QEMU. + pub const DEFAULT_DOWNTIME: Duration = Duration::from_millis(300); + pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60 * 60 /* one hour */); + + fn default_downtime_ms() -> NonZeroU64 { + let ms_u64 = u64::try_from(Self::DEFAULT_DOWNTIME.as_millis()).unwrap(); + NonZeroU64::new(ms_u64).unwrap() + } + + fn default_timeout_s() -> NonZeroU64 { + NonZeroU64::new(Self::DEFAULT_TIMEOUT.as_secs()).unwrap() + } pub fn parse(migration: &str) -> Result { let mut parser = OptionParser::new(); - parser.add("destination_url").add("local"); + parser + .add("destination_url") + .add("local") + .add("downtime_ms") + .add("timeout_s") + .add("timeout_strategy"); parser.parse(migration).map_err(VmSendMigrationParseError)?; let destination_url = parser.get("destination_url").ok_or_else(|| { @@ -300,12 +359,49 @@ impl VmSendMigrationData { .map_err(VmSendMigrationParseError)? .unwrap_or(Toggle(false)) .0; + let downtime_ms = match parser + .convert::("downtime_ms") + .map_err(VmSendMigrationParseError)? + { + Some(v) => NonZeroU64::new(v).ok_or_else(|| { + VmSendMigrationParseError(OptionParserError::InvalidValue( + "downtime_ms must be non-zero".to_string(), + )) + })?, + None => Self::default_downtime_ms(), + }; + let timeout_s = match parser + .convert::("timeout_s") + .map_err(VmSendMigrationParseError)? + { + Some(v) => NonZeroU64::new(v).ok_or_else(|| { + VmSendMigrationParseError(OptionParserError::InvalidValue( + "timeout_s must be non-zero".to_string(), + )) + })?, + None => Self::default_timeout_s(), + }; + let timeout_strategy = parser + .convert("timeout_strategy") + .map_err(VmSendMigrationParseError)? + .unwrap_or_default(); Ok(Self { destination_url, local, + downtime_ms, + timeout_s, + timeout_strategy, }) } + + pub fn downtime(&self) -> Duration { + Duration::from_millis(self.downtime_ms.get()) + } + + pub fn timeout(&self) -> Duration { + Duration::from_secs(self.timeout_s.get()) + } } pub enum ApiResponsePayload { @@ -1582,15 +1678,76 @@ mod unit_tests { #[test] fn test_vm_send_migration_data_parse() { // Fully specified - let data = VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,local=on") - .expect("valid migration string should parse"); + let data = VmSendMigrationData::parse( + "destination_url=tcp://192.168.1.1:8080,local=on,downtime_ms=200,timeout_s=3600,timeout_strategy=cancel" + ).expect("valid migration string should parse"); assert_eq!(data.destination_url, "tcp://192.168.1.1:8080"); assert!(data.local); + assert_eq!(data.downtime_ms.get(), 200); + assert_eq!(data.timeout_s.get(), 3600); + assert_eq!(data.timeout_strategy, TimeoutStrategy::Cancel); + + // Defaults applied when optional fields are omitted + let data = VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080") + .expect("minimal migration string should parse"); + assert_eq!(data.destination_url, "tcp://192.168.1.1:8080"); + assert!(!data.local); + assert_eq!(data.downtime_ms, VmSendMigrationData::default_downtime_ms()); + assert_eq!(data.timeout_s, VmSendMigrationData::default_timeout_s()); + assert_eq!(data.timeout_strategy, TimeoutStrategy::default()); + + // Missing destination_url is an error + VmSendMigrationData::parse("local=on,downtime_ms=200").unwrap_err(); + + // Zero downtime_ms is rejected + let _data = + VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,downtime_ms=0") + .expect_err("zero downtime_ms should be rejected"); + + // Zero timeout_s is rejected + let _data = VmSendMigrationData::parse("destination_url=unix:/tmp/sock,timeout_s=0") + .expect_err("zero timeout_s should be rejected"); // Unknown option is an error VmSendMigrationData::parse("destination_url=unix:/tmp/sock,unknown_field=foo").unwrap_err(); // Invalid toggle value is an error VmSendMigrationData::parse("destination_url=unix:/tmp/sock,local=yes").unwrap_err(); + + // Timeout strategy + let _data = VmSendMigrationData::parse( + "destination_url=tcp://192.168.1.1:8080,timeout_strategy=invalid", + ) + .expect_err("zero downtime_ms should be rejected"); + + // Happy path with some defaults + let data = + VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,downtime_ms=150") + .unwrap(); + assert_eq!( + data, + VmSendMigrationData { + destination_url: "tcp://192.168.1.1:8080".to_string(), + local: false, + downtime_ms: NonZeroU64::new(150).unwrap(), + timeout_s: VmSendMigrationData::default_timeout_s(), + timeout_strategy: Default::default(), + } + ); + + // Happy path, fully specified + let data = + VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,downtime_ms=150,timeout_s=900,timeout_strategy=ignore") + .unwrap(); + assert_eq!( + data, + VmSendMigrationData { + destination_url: "tcp://192.168.1.1:8080".to_string(), + local: false, + downtime_ms: NonZeroU64::new(150).unwrap(), + timeout_s: NonZeroU64::new(900).unwrap(), + timeout_strategy: TimeoutStrategy::Ignore, + } + ); } } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index cb72d69d96..bb775390a4 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1308,6 +1308,8 @@ impl Vmm { fn do_memory_migration( vm: &mut Vm, socket: &mut SocketStream, + // Used in next commit + _send_data_migration: &VmSendMigrationData, ) -> result::Result<(), MigratableError> { const MAX_ITERATIONS: usize = 5; @@ -1424,7 +1426,7 @@ impl Vmm { // Now pause VM vm.pause()?; } else { - Self::do_memory_migration(vm, &mut socket)?; + Self::do_memory_migration(vm, &mut socket, send_data_migration)?; } // We release the locks early to enable locking them on the destination host. @@ -2438,8 +2440,12 @@ impl RequestHandler for Vmm { send_data_migration: VmSendMigrationData, ) -> result::Result<(), MigratableError> { info!( - "Sending migration: destination_url = {}, local = {}", - send_data_migration.destination_url, send_data_migration.local + "Sending migration: destination_url={},local={},downtime={}ms,timeout={}s,timeout_strategy={:?}", + send_data_migration.destination_url, + send_data_migration.local, + send_data_migration.downtime().as_millis(), + send_data_migration.timeout().as_secs(), + send_data_migration.timeout_strategy ); if !self From 49e03c8bc529fbab3b8d31393fe9f1d069919c29 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 13 Mar 2026 23:26:47 +0100 Subject: [PATCH 0847/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- vm-migration/src/context.rs | 4 +- vmm/src/lib.rs | 115 ++++++++++++++++++++++++++++++++---- 2 files changed, 104 insertions(+), 15 deletions(-) diff --git a/vm-migration/src/context.rs b/vm-migration/src/context.rs index 7dfa5b7d9e..d7680821cc 100644 --- a/vm-migration/src/context.rs +++ b/vm-migration/src/context.rs @@ -40,9 +40,9 @@ pub struct MemoryMigrationContext { /// /// Please note that this ignores any additional migration overhead and /// only looks at the memory transfer itself. - estimated_downtime: Option, + pub estimated_downtime: Option, /// Begin of the memory migration. - migration_begin: Instant, + pub migration_begin: Instant, /// Duration of the memory migration. /// /// This is only `None` until the last iteration is finished. diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index bb775390a4..d1cf6693a3 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -13,6 +13,7 @@ use std::panic::AssertUnwindSafe; use std::path::PathBuf; use std::sync::mpsc::{Receiver, RecvError, SendError, Sender}; use std::sync::{Arc, Mutex}; +use std::time::Duration; #[cfg(not(target_arch = "riscv64"))] use std::time::Instant; use std::{io, result, thread}; @@ -48,8 +49,8 @@ use vmm_sys_util::signal::unblock_signal; use vmm_sys_util::sock_ctrl_msg::ScmSocket; use crate::api::{ - ApiRequest, ApiResponse, RequestHandler, VmInfoResponse, VmReceiveMigrationData, - VmSendMigrationData, VmmPingResponse, + ApiRequest, ApiResponse, RequestHandler, TimeoutStrategy, VmInfoResponse, + VmReceiveMigrationData, VmSendMigrationData, VmmPingResponse, }; use crate::config::{MemoryRestoreMode, RestoreConfig, add_to_config}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] @@ -1258,7 +1259,7 @@ impl Vmm { vm: &mut Vm, socket: &mut SocketStream, ctx: &mut MemoryMigrationContext, - is_converged: impl Fn(&MemoryMigrationContext) -> bool, + is_converged: impl Fn(&MemoryMigrationContext) -> result::Result, ) -> result::Result { loop { let iteration_begin = Instant::now(); @@ -1271,7 +1272,7 @@ impl Vmm { }; ctx.update_metrics_before_transfer(iteration_begin, &iteration_table); - if is_converged(ctx) { + if is_converged(ctx)? { debug!("Precopy converged: {ctx}"); break Ok(iteration_table); } @@ -1299,6 +1300,95 @@ impl Vmm { } } + /// Checks whether the precopy memory migration has converged and it is safe + /// to proceed to the final (paused) memory iteration. + /// + /// Once this returns, the VM is expected to stop as soon as possible. + /// + /// Convergence is reached when any of the following criteria is met: + /// + /// 1. **No dirty pages remain** – the current iteration would transfer zero + /// bytes. + /// 2. **Downtime budget is met** – the estimated downtime for the final + /// (paused) iteration is within the caller-specified + /// [`VmSendMigrationData::downtime`] budget. + /// 3. **Timeout** – the precopy phase has been running for at least + /// [`VmSendMigrationData::timeout`]. The outcome depends on + /// [`VmSendMigrationData::timeout_strategy`]: + /// - [`TimeoutStrategy::Cancel`] – returns + /// - [`TimeoutStrategy::Ignore`] – the migration completes despite not + /// meeting the downtime budget. + /// [`MigratableError::MigrateSend`] so the caller can abort the + /// migration cleanly. + /// + /// # Returns + /// + /// * `Ok(true)` – convergence criterion met; the caller should stop precopy + /// iterations. + /// * `Ok(false)` – not yet converged; the caller should run another + /// dirty-page iteration. + /// * `Err(_)` – the timeout was reached and [`TimeoutStrategy::Cancel`] + /// is in effect. + fn is_precopy_converged( + ctx: &MemoryMigrationContext, + send_data_migration: &VmSendMigrationData, + ) -> result::Result { + if ctx.current_iteration_total_bytes == 0 { + debug!("Precopy: No more memory to transfer"); + return Ok(true); + } + + // We currently ignore the time required to transfer the final + // VM state (device state and vCPUs) and the time needed on the + // receiver to create the VM and initialize its data structures + // before execution can resume. + // + // Manual testing showed that migrating an idle VM on a modern + // AMD CPU (CHV release build) adds ~5 ms of overhead when + // scaling from 1 to 200 vCPUs. Given this small cost, we + // deliberately avoid additional heuristics to estimate the + // downtime more precisely - for now. Instead, we approximate + // the downtime just by the transfer time of the final memory + // delta. + if let Some(memory_downtime) = ctx.estimated_downtime + && memory_downtime <= send_data_migration.downtime() + { + debug!( + "Precopy: Target downtime can be met: {}ms <= {}ms", + memory_downtime.as_millis(), + send_data_migration.downtime().as_millis() + ); + return Ok(true); + } + + // We check the beginning of the precopy migration and not the overall migration, and + // this is fine: precopy takes the longest and the earlier steps are negligible. + if ctx.migration_begin.elapsed() >= send_data_migration.timeout() { + return match send_data_migration.timeout_strategy { + TimeoutStrategy::Cancel => { + let msg = format!( + "Precopy: Timeout reached: {}s: migration didn't converge in time", + send_data_migration.timeout().as_secs() + ); + Err(MigratableError::MigrateSend(anyhow!("{msg}"))) + } + TimeoutStrategy::Ignore => { + info!( + "Precopy: Pausing VM, ignoring target downtime ({}ms) due to timeout ({}s): Estimated downtime: {}ms", + send_data_migration.downtime().as_millis(), + send_data_migration.timeout().as_secs(), + ctx.estimated_downtime + .unwrap_or(Duration::from_secs(0)) + .as_millis() + ); + Ok(true) + } + }; + } + + Ok(false) + } + /// Performs the memory migration including multiple iterations. /// /// This includes: @@ -1308,19 +1398,18 @@ impl Vmm { fn do_memory_migration( vm: &mut Vm, socket: &mut SocketStream, - // Used in next commit - _send_data_migration: &VmSendMigrationData, + send_data_migration: &VmSendMigrationData, ) -> result::Result<(), MigratableError> { - const MAX_ITERATIONS: usize = 5; - let mut ctx = MemoryMigrationContext::new(); - let is_converged = |ctx: &MemoryMigrationContext| { - // TODO: Add check for configurable downtime and max migration time #7111 - ctx.iteration >= MAX_ITERATIONS || ctx.current_iteration_total_bytes == 0 - }; vm.start_dirty_log()?; - let remaining = Self::do_memory_iterations(vm, socket, &mut ctx, is_converged)?; + let remaining = Self::do_memory_iterations( + vm, + socket, + &mut ctx, + // We bind send_data_migration to the callback + |ctx| Self::is_precopy_converged(ctx, send_data_migration), + )?; vm.pause()?; // Send last batch of dirty pages: final iteration From 49868f483ee1b79df7af799d32731e778f570c10 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 13 Mar 2026 23:24:26 +0100 Subject: [PATCH 0848/1893] vmm: update openapi spec Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- vmm/src/api/openapi/cloud-hypervisor.yaml | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index e8d3350dba..14f74018ea 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -1369,6 +1369,15 @@ components: receiver_url: type: string + TimeoutStrategy: + type: string + enum: ["Cancel", "Ignore"] + default: "Cancel" + description: > + The strategy to apply when the migration timeout is reached. + Cancel will abort the migration and keep the VM running on the source. + Ignore will proceed with the migration regardless of the downtime requirement. + SendMigrationData: required: - destination_url @@ -1378,6 +1387,24 @@ components: type: string local: type: boolean + downtime_ms: + type: integer + format: int64 + minimum: 1 + default: 300 + description: > + The maximum downtime the migration aims for, in milliseconds. + Defaults to 300ms. + timeout_s: + type: integer + format: int64 + minimum: 1 + default: 3600 + description: > + The timeout for the migration (maximum total duration), in seconds. + Defaults to 3600s (one hour). + timeout_strategy: + $ref: "#/components/schemas/TimeoutStrategy" VmAddUserDevice: required: From c8cee779b000de8288987becd6957e9ee75e8493 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 13 Mar 2026 23:23:58 +0100 Subject: [PATCH 0849/1893] docs: update live_migration.md Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- docs/live_migration.md | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/live_migration.md b/docs/live_migration.md index ac5bf93f75..ac842d3172 100644 --- a/docs/live_migration.md +++ b/docs/live_migration.md @@ -3,8 +3,9 @@ This document gives examples of how to use the live migration support in Cloud Hypervisor: -1. local migration - migrating a VM from one Cloud Hypervisor instance to another on the same machine; -1. remote migration - migrating a VM between two machines; +1. **Local Migration**: Migrating a VM from one Cloud Hypervisor instance to another on the same machine; also called + UNIX socket migration. +1. **Remote Migration** (TCP Migration): migrating a VM between two TCP/IP hosts. > :warning: These examples place sockets in /tmp. This is done for > simplicity and should not be done in production. @@ -28,7 +29,8 @@ Launch the destination VM from the same directory (on the host machine): $ target/release/cloud-hypervisor --api-socket=/tmp/api2 ``` -Get ready for receiving migration for the destination VM (on the host machine): +Get ready for receiving migration for the destination VM (on the host +machine): ```console $ target/release/ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/sock @@ -37,14 +39,16 @@ $ target/release/ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/so Start to send migration for the source VM (on the host machine): ```console -$ target/release/ch-remote --api-socket=/tmp/api1 send-migration --local unix:/tmp/sock +$ target/release/ch-remote --api-socket=/tmp/api1 send-migration destination_url=unix:/tmp/sock,local=true ``` When the above commands completed, the source VM should be successfully migrated to the destination VM. Now the destination VM is running while the source VM is terminated gracefully. -## Remote Migration +## Remote Migration (TCP Migration) + +_Hint: For developing purposes, same-host TCP migrations are also supported._ In this example, we will migrate a VM from one machine (`src`) to another (`dst`) across the network. To keep it simple, we will use a @@ -171,7 +175,13 @@ After a few seconds the VM should be up and you can interact with it. Initiate the Migration over TCP: ```console -src $ ch-remote --api-socket=/tmp/api send-migration tcp:{dst}:{port} +src $ ch-remote --api-socket=/tmp/api send-migration destination_url=tcp:{dst}:{port} +``` + +With migration parameters: + +```console +src $ ch-remote --api-socket=/tmp/api send-migration destination_url=tcp:{dst}:{port},downtime_ms=200,timeout_s=3600,timeout_strategy=cancel ``` > Replace {dst}:{port} with the actual IP address and port of your destination host. @@ -180,3 +190,20 @@ After completing the above commands, the source VM will be migrated to the destination host and continue running there. The source VM instance will terminate normally. All ongoing processes and connections within the VM should remain intact after the migration. + +#### Migration Parameters + +Cloud Hypervisor supports additional parameters to control the +migration process. Via the API or `ch-remote`, you may specify: + +- `downtime_ms `: \ + The maximum downtime the migration aims for, in milliseconds. + Defaults to `300ms`. +- `timeout_s `: \ + The timeout for the migration (maximum total duration), in seconds. + Defaults to `3600s` (one hour). +- `timeout_strategy ` (`[cancel, ignore]`): \ + The strategy to apply when the migration timeout is reached. + Cancel will abort the migration and keep the VM running on the source. + Ignore will proceed with the migration regardless of the downtime requirement. + Defaults to `cancel`. From 98fd139111491277368b8a426d631ab1449ce14d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 20 Mar 2026 18:04:32 +0100 Subject: [PATCH 0850/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- cloud-hypervisor/tests/integration.rs | 173 ++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5454cae6b6..811dd431f0 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -9868,6 +9868,8 @@ mod vfio { } mod live_migration { + use vmm::api::TimeoutStrategy; + use crate::*; pub fn start_live_migration( @@ -11244,7 +11246,168 @@ mod live_migration { handle_child_output(r, &dest_output); } + fn _test_live_migration_tcp_timeout(timeout_strategy: TimeoutStrategy) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + let net_id = "net1337"; + let net_params = format!( + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 + ); + let memory_param: &[&str] = &["--memory", "size=2G,shared=on"]; + let boot_vcpus = 2; + + let src_vm_path = clh_command("cloud-hypervisor"); + let src_api_socket = temp_api_path(&guest.tmp_dir); + let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); + src_vm_cmd + .args(["--cpus", format!("boot={boot_vcpus}").as_str()]) + .args(memory_param) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--net", net_params.as_str()]) + .args(["--api-socket", &src_api_socket]) + .capture_output(); + let mut src_child = src_vm_cmd.spawn().unwrap(); + + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() > 2_000_000); + + // Start a memory stressor in the background to keep pages dirty, + // ensuring the precopy loop cannot converge within the 1s timeout. + guest + .ssh_command("nohup stress --vm 1 --vm-bytes 1G --vm-keep &>/dev/null &") + .unwrap(); + // Give stress a moment to actually start dirtying memory + thread::sleep(Duration::from_secs(3)); + + let migration_port = get_available_port(); + let host_ip = "127.0.0.1"; + + let mut receive_migration = Command::new(clh_command("ch-remote")) + .args([ + &format!("--api-socket={dest_api_socket}"), + "receive-migration", + &format!("tcp:0.0.0.0:{migration_port}"), + ]) + .stdin(Stdio::null()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + thread::sleep(Duration::from_secs(1)); + + // Use a tight downtime budget (50ms) combined with a 1s timeout so the + // migration cannot converge regardless of strategy. + let mut send_migration = Command::new(clh_command("ch-remote")) + .args([ + &format!("--api-socket={src_api_socket}"), + "send-migration", + &format!( + "destination_url=tcp:{host_ip}:{migration_port},downtime_ms=50,timeout_s=1,timeout_strategy={timeout_strategy:?}" + ), + ]) + .stdin(Stdio::null()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + let send_status = send_migration + .wait_timeout(Duration::from_secs(60)) + .unwrap(); + let receive_status = receive_migration + .wait_timeout(Duration::from_secs(60)) + .unwrap(); + + // Clean up receive-migration regardless of its outcome + if receive_status.is_none() { + let _ = receive_migration.kill(); + } + + // Kill the stressor now that migration has completed or aborted, + // to reduce system load during post-migration checks. + let _ = guest.ssh_command("pkill -f 'stress --vm'"); + + match timeout_strategy { + TimeoutStrategy::Cancel => { + // With cancel strategy the send must fail and the source VM + // must keep running. + let send_failed = match send_status { + Some(status) => !status.success(), + None => { + let _ = send_migration.kill(); + false + } + }; + assert!( + send_failed, + "send-migration should have failed due to 1s timeout with cancel strategy" + ); + + thread::sleep(Duration::from_secs(2)); + assert!( + src_child.try_wait().unwrap().is_none(), + "Source VM should still be running after a cancelled migration" + ); + + // Confirm the source VM is still responsive over SSH + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() >= 2_000_000); + } + TimeoutStrategy::Ignore => { + // With Ignore strategy the send must succeed despite the timeout + // being reached, and the source VM must have terminated. + let send_succeeded = match send_status { + Some(status) => status.success(), + None => { + let _ = send_migration.kill(); + false + } + }; + assert!( + send_succeeded, + "send-migration should have succeeded with timeout_strategy=ignore" + ); + + thread::sleep(Duration::from_secs(3)); + assert!( + src_child.try_wait().unwrap().is_some(), + "Source VM should have terminated after a forced migration" + ); + + // Confirm the VM is still responsive over SSH on the new host + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() >= 2_000_000); + } + } + })); + + let _ = src_child.kill(); + let src_output = src_child.wait_with_output().unwrap(); + let _ = dest_child.kill(); + let _dest_output = dest_child.wait_with_output().unwrap(); + + handle_child_output(r, &src_output); + } + mod live_migration_parallel { + use vmm::api::TimeoutStrategy; + use super::*; #[test] fn test_live_migration_basic() { @@ -11261,6 +11424,16 @@ mod live_migration { _test_live_migration_tcp(); } + #[test] + fn test_live_migration_tcp_timeout_cancel() { + _test_live_migration_tcp_timeout(TimeoutStrategy::Cancel); + } + + #[test] + fn test_live_migration_tcp_timeout_ignore() { + _test_live_migration_tcp_timeout(TimeoutStrategy::Ignore); + } + #[test] fn test_live_migration_watchdog() { _test_live_migration_watchdog(false, false); From 81495241eb1f49e7a1623501f8a49b092be55b05 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sat, 21 Mar 2026 09:11:32 +0100 Subject: [PATCH 0851/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- cloud-hypervisor/tests/integration.rs | 35 ++++++++++++--------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 811dd431f0..87cf4f2396 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -10026,9 +10026,9 @@ mod live_migration { ); let memory_param: &[&str] = if local { - &["--memory", "size=4G,shared=on"] + &["--memory", "size=1500M,shared=on"] } else { - &["--memory", "size=4G"] + &["--memory", "size=1500M"] }; let boot_vcpus = 2; @@ -10084,7 +10084,7 @@ mod live_migration { assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); // Check the guest virtio-devices, e.g. block, rng, console, and net guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); @@ -10151,7 +10151,7 @@ mod live_migration { let r = std::panic::catch_unwind(|| { // Perform same checks to validate VM has been properly migrated assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); }); @@ -10616,9 +10616,9 @@ mod live_migration { ); let memory_param: &[&str] = if local { - &["--memory", "size=4G,shared=on"] + &["--memory", "size=1500M,shared=on"] } else { - &["--memory", "size=4G"] + &["--memory", "size=1500M"] }; let boot_vcpus = 2; @@ -10674,7 +10674,7 @@ mod live_migration { // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); // Check the guest virtio-devices, e.g. block, rng, console, and net guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); // x86_64: Following what's done in the `test_snapshot_restore`, we need @@ -10759,7 +10759,7 @@ mod live_migration { let r = std::panic::catch_unwind(|| { // Perform same checks to validate VM has been properly migrated assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); @@ -10934,7 +10934,7 @@ mod live_migration { "--cpus", format!("boot={boot_vcpus},max={max_vcpus}").as_str(), ]) - .args(["--memory", "size=4G,shared=on"]) + .args(["--memory", "size=1500M,shared=on"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -10966,7 +10966,7 @@ mod live_migration { assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); // Check Landlock is enabled by hot-plugging a disk. assert!(!remote_command( @@ -11016,7 +11016,7 @@ mod live_migration { let r = std::panic::catch_unwind(|| { // Perform same checks to validate VM has been properly migrated assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); }); // Check Landlock is enabled on destination VM by hot-plugging a disk. @@ -11128,7 +11128,7 @@ mod live_migration { "id={},tap=,mac={},ip={},mask=255.255.255.128", net_id, guest.network.guest_mac0, guest.network.host_ip0 ); - let memory_param: &[&str] = &["--memory", "size=4G,shared=on"]; + let memory_param: &[&str] = &["--memory", "size=1500M,shared=on"]; let boot_vcpus = 2; let max_vcpus = 4; let pmem_temp_file = TempFile::new().unwrap(); @@ -11178,7 +11178,7 @@ mod live_migration { guest.wait_vm_boot().unwrap(); // Ensure the source VM is running normally assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); // On x86_64 architecture, remove and re-add the virtio-net device @@ -11230,7 +11230,7 @@ mod live_migration { let r = std::panic::catch_unwind(|| { // Perform the same checks to ensure the VM has migrated correctly assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); }); @@ -11255,7 +11255,7 @@ mod live_migration { "id={},tap=,mac={},ip={},mask=255.255.255.128", net_id, guest.network.guest_mac0, guest.network.host_ip0 ); - let memory_param: &[&str] = &["--memory", "size=2G,shared=on"]; + let memory_param: &[&str] = &["--memory", "size=1500M,shared=on"]; let boot_vcpus = 2; let src_vm_path = clh_command("cloud-hypervisor"); @@ -11284,12 +11284,11 @@ mod live_migration { guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 2_000_000); // Start a memory stressor in the background to keep pages dirty, // ensuring the precopy loop cannot converge within the 1s timeout. guest - .ssh_command("nohup stress --vm 1 --vm-bytes 1G --vm-keep &>/dev/null &") + .ssh_command("nohup stress --vm 2 --vm-bytes 200M --vm-keep &>/dev/null &") .unwrap(); // Give stress a moment to actually start dirtying memory thread::sleep(Duration::from_secs(3)); @@ -11367,7 +11366,6 @@ mod live_migration { // Confirm the source VM is still responsive over SSH assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() >= 2_000_000); } TimeoutStrategy::Ignore => { // With Ignore strategy the send must succeed despite the timeout @@ -11392,7 +11390,6 @@ mod live_migration { // Confirm the VM is still responsive over SSH on the new host assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() >= 2_000_000); } } })); From 00b3a48900bbed866011bc6ef5738ddcfe31ca71 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 24 Mar 2026 18:40:42 +0100 Subject: [PATCH 0852/1893] 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 On-behalf-of: SAP philipp.schuster@sap.com --- CONTRIBUTING.md | 51 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1518d0f1fb..ead1cbe200 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,12 +164,49 @@ Signed-off-by: Sebastien Boeuf 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. + +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. From 56771a0f4cdee8b7c044403a5176e01fcd06d7c9 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 24 Mar 2026 18:40:55 +0100 Subject: [PATCH 0853/1893] docs: remove trailing spaces in CONTRIBUTING.md Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- CONTRIBUTING.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ead1cbe200..cdda170b4a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ convention and enforce it through the Continuous Integration (CI) process callin ```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 +36,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 '' + bash ./scripts/dev_cli.sh tests --integration -- --test-filter '' ``` ### Setup Commit Hook @@ -71,35 +71,35 @@ We require patches to: - Follow the pattern: \ ``` : Change summary - + 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. - + Signed-off-by: ``` - + Valid components are listed in `TitleStartsWithComponent.py`. In short, each -cargo workspace member is a valid component as well as `build`, `ci`, `docs` and +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 ``` @@ -109,11 +109,11 @@ 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 +`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 +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). @@ -150,14 +150,14 @@ 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 ``` From c75504c833e3c0fc5c614986145b260b5db3dce7 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 00:17:38 -0700 Subject: [PATCH 0854/1893] 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 --- cloud-hypervisor/tests/integration.rs | 207 -------------------- cloud-hypervisor/tests/integration_cvm.rs | 223 ++++++++++++++++++++++ 2 files changed, 223 insertions(+), 207 deletions(-) create mode 100644 cloud-hypervisor/tests/integration_cvm.rs diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 87cf4f2396..3af45c35c3 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -11982,210 +11982,3 @@ mod fw_cfg { handle_child_output(r, &output); } } - -#[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] -mod common_cvm { - use crate::*; - - #[test] - fn test_focal_simple_launch() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); - - _test_simple_launch(&guest); - } - - #[test] - fn test_api_http_create_boot() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); - let target_api = TargetApi::new_http_api(&guest.tmp_dir); - _test_api_create_boot(&target_api, &guest); - } - - #[test] - fn test_api_http_shutdown() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); - - let target_api = TargetApi::new_http_api(&guest.tmp_dir); - _test_api_shutdown(&target_api, &guest); - } - - #[test] - fn test_api_http_delete() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); - - let target_api = TargetApi::new_http_api(&guest.tmp_dir); - _test_api_delete(&target_api, &guest); - } - - #[test] - fn test_power_button() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); - _test_power_button(&guest); - } - - #[test] - fn test_virtio_vsock() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); - _test_virtio_vsock(&guest, false); - } - - #[test] - fn test_multi_cpu() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); - _test_multi_cpu(&guest); - } - - #[test] - fn test_cpu_affinity() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(2); - _test_cpu_affinity(&guest); - } - - #[test] - fn test_virtio_queue_affinity() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); - _test_virtio_queue_affinity(&guest); - } - - #[test] - fn test_pci_msi() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); - _test_pci_msi(&guest); - } - - #[test] - fn test_virtio_net_ctrl_queue() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); - _test_virtio_net_ctrl_queue(&guest); - } - - #[test] - fn test_pci_multiple_segments() { - // Use 8 segments to test the multiple segment support since it's more than the default 6 - // supported by Linux - // IGVM file used by Sev-Snp Guest now support up to 8 segments, so we can use 8 segments for testing. - let num_pci_segments: u16 = 8; - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); - _test_pci_multiple_segments(&guest, num_pci_segments, 5); - } - - #[test] - fn test_direct_kernel_boot() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); - _test_direct_kernel_boot(&guest); - } - - #[test] - fn test_virtio_block_io_uring() { - let guest = make_virtio_block_guest( - &GuestFactory::new_confidential_guest_factory(), - FOCAL_IMAGE_NAME, - ); - _test_virtio_block(&guest, false, true, false, false, ImageType::Raw); - } - - #[test] - fn test_virtio_block_aio() { - let guest = make_virtio_block_guest( - &GuestFactory::new_confidential_guest_factory(), - FOCAL_IMAGE_NAME, - ); - _test_virtio_block(&guest, true, false, false, false, ImageType::Raw); - } - - #[test] - fn test_virtio_block_sync() { - let guest = make_virtio_block_guest( - &GuestFactory::new_confidential_guest_factory(), - FOCAL_IMAGE_NAME, - ); - _test_virtio_block(&guest, true, true, false, false, ImageType::Raw); - } - - #[test] - fn test_virtio_block_qcow2() { - let guest = make_virtio_block_guest( - &GuestFactory::new_confidential_guest_factory(), - JAMMY_IMAGE_NAME_QCOW2, - ); - _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); - } - - #[test] - fn test_virtio_block_qcow2_zlib() { - let guest = make_virtio_block_guest( - &GuestFactory::new_confidential_guest_factory(), - JAMMY_IMAGE_NAME_QCOW2_ZLIB, - ); - _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); - } - - #[test] - fn test_virtio_block_qcow2_zstd() { - let guest = make_virtio_block_guest( - &GuestFactory::new_confidential_guest_factory(), - JAMMY_IMAGE_NAME_QCOW2_ZSTD, - ); - _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); - } - - #[test] - fn test_virtio_block_qcow2_backing_zstd_file() { - let guest = make_virtio_block_guest( - &GuestFactory::new_confidential_guest_factory(), - JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE, - ); - - _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); - } - - #[test] - fn test_virtio_block_qcow2_backing_uncompressed_file() { - let guest = make_virtio_block_guest( - &GuestFactory::new_confidential_guest_factory(), - JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE, - ); - - _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); - } - - #[test] - fn test_virtio_block_qcow2_backing_raw_file() { - let guest = make_virtio_block_guest( - &GuestFactory::new_confidential_guest_factory(), - JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE, - ); - _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); - } -} diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs new file mode 100644 index 0000000000..ed848c8f1d --- /dev/null +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -0,0 +1,223 @@ +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#![cfg(any(devcli_testenv, clippy))] +#![allow(clippy::undocumented_unsafe_blocks)] +// When enabling the `mshv` feature, we skip quite some tests and +// hence have known dead-code. This annotation silences dead-code +// related warnings for our quality workflow to pass. +#![allow(dead_code)] +mod common; + +#[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] +mod common_cvm { + use block::ImageType; + use common::tests_wrappers::*; + use common::utils::*; + use test_infra::*; + + use super::*; + + #[test] + fn test_focal_simple_launch() { + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + + _test_simple_launch(&guest); + } + + #[test] + fn test_api_http_create_boot() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_create_boot(&target_api, &guest); + } + + #[test] + fn test_api_http_shutdown() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_shutdown(&target_api, &guest); + } + + #[test] + fn test_api_http_delete() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + + let target_api = TargetApi::new_http_api(&guest.tmp_dir); + _test_api_delete(&target_api, &guest); + } + + #[test] + fn test_power_button() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_power_button(&guest); + } + + #[test] + fn test_virtio_vsock() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_virtio_vsock(&guest, false); + } + + #[test] + fn test_multi_cpu() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_multi_cpu(&guest); + } + + #[test] + fn test_cpu_affinity() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(2); + _test_cpu_affinity(&guest); + } + + #[test] + fn test_virtio_queue_affinity() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_confidential_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); + _test_virtio_queue_affinity(&guest); + } + + #[test] + fn test_pci_msi() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_pci_msi(&guest); + } + + #[test] + fn test_virtio_net_ctrl_queue() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_virtio_net_ctrl_queue(&guest); + } + + #[test] + fn test_pci_multiple_segments() { + // Use 8 segments to test the multiple segment support since it's more than the default 6 + // supported by Linux + // IGVM file used by Sev-Snp Guest now support up to 8 segments, so we can use 8 segments for testing. + let num_pci_segments: u16 = 8; + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_pci_multiple_segments(&guest, num_pci_segments, 5); + } + + #[test] + fn test_direct_kernel_boot() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + _test_direct_kernel_boot(&guest); + } + + #[test] + fn test_virtio_block_io_uring() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + FOCAL_IMAGE_NAME, + ); + _test_virtio_block(&guest, false, true, false, false, ImageType::Raw); + } + + #[test] + fn test_virtio_block_aio() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + FOCAL_IMAGE_NAME, + ); + _test_virtio_block(&guest, true, false, false, false, ImageType::Raw); + } + + #[test] + fn test_virtio_block_sync() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + FOCAL_IMAGE_NAME, + ); + _test_virtio_block(&guest, true, true, false, false, ImageType::Raw); + } + + #[test] + fn test_virtio_block_qcow2() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2, + ); + _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_zlib() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_ZLIB, + ); + _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_zstd() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_ZSTD, + ); + _test_virtio_block(&guest, false, false, true, false, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_backing_zstd_file() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_BACKING_ZSTD_FILE, + ); + + _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_backing_uncompressed_file() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_BACKING_UNCOMPRESSED_FILE, + ); + + _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_qcow2_backing_raw_file() { + let guest = make_virtio_block_guest( + &GuestFactory::new_confidential_guest_factory(), + JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE, + ); + _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); + } +} From 7802470906cc1cf7c034345116f1cf5ea26cb88e Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 16:17:55 -0700 Subject: [PATCH 0855/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 64 +++++++---------------- 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index ed848c8f1d..e2b7b08add 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -18,32 +18,30 @@ mod common_cvm { use test_infra::*; use super::*; + macro_rules! basic_cvm_guest { + ($image_name:expr) => {{ + let disk_config = UbuntuDiskConfig::new($image_name.to_string()); + GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)) + }}; + } #[test] fn test_focal_simple_launch() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_cvm_guest!(FOCAL_IMAGE_NAME); _test_simple_launch(&guest); } #[test] fn test_api_http_create_boot() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_create_boot(&target_api, &guest); } #[test] fn test_api_http_shutdown() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_shutdown(&target_api, &guest); @@ -51,70 +49,50 @@ mod common_cvm { #[test] fn test_api_http_delete() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); - + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_delete(&target_api, &guest); } #[test] fn test_power_button() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_power_button(&guest); } #[test] fn test_virtio_vsock() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_virtio_vsock(&guest, false); } #[test] fn test_multi_cpu() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_multi_cpu(&guest); } #[test] fn test_cpu_affinity() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(2); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(2); _test_cpu_affinity(&guest); } #[test] fn test_virtio_queue_affinity() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_confidential_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(4); _test_virtio_queue_affinity(&guest); } #[test] fn test_pci_msi() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_pci_msi(&guest); } #[test] fn test_virtio_net_ctrl_queue() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_virtio_net_ctrl_queue(&guest); } @@ -124,17 +102,13 @@ mod common_cvm { // supported by Linux // IGVM file used by Sev-Snp Guest now support up to 8 segments, so we can use 8 segments for testing. let num_pci_segments: u16 = 8; - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_pci_multiple_segments(&guest, num_pci_segments, 5); } #[test] fn test_direct_kernel_boot() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = - GuestFactory::new_confidential_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_direct_kernel_boot(&guest); } From 0fc0f2bd0e84c38d6b61bd77c09b85710353f32d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 16:36:41 -0700 Subject: [PATCH 0856/1893] 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 --- cloud-hypervisor/tests/integration.rs | 11 +++++++++-- test_infra/src/lib.rs | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 3af45c35c3..04f61f59ee 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -29,6 +29,13 @@ mod common; use common::tests_wrappers::*; use common::utils::*; +macro_rules! basic_regular_guest { + ($image_name:expr) => {{ + let disk_config = UbuntuDiskConfig::new($image_name.to_string()); + GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)) + }}; +} + mod common_parallel { use std::io::{self, SeekFrom}; use std::process::Command; @@ -41,8 +48,8 @@ mod common_parallel { #[cfg(target_arch = "x86_64")] fn test_focal_hypervisor_fw() { let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let mut guest = Guest::new(Box::new(disk_config)); - guest.kernel_path = Some(fw_path(FwType::RustHypervisorFirmware)); + let guest = basic_regular_guest!(FOCAL_IMAGE_NAME) + .with_kernel(fw_path(FwType::RustHypervisorFirmware)); _test_simple_launch(&guest); } diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index b6d7641f6b..a299b2c494 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1018,6 +1018,11 @@ impl Guest { self } + pub fn with_kernel(mut self, kernel: String) -> Self { + self.kernel_path = Some(kernel); + self + } + pub fn default_net_string(&self) -> String { format!( "tap=,mac={},ip={},mask=255.255.255.128", From 6aad6e40c8df3e94ee7d99dd83dec7510cf4fb31 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 16:59:34 -0700 Subject: [PATCH 0857/1893] 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 --- cloud-hypervisor/tests/integration.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 04f61f59ee..972b979fb9 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -47,7 +47,6 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_focal_hypervisor_fw() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest = basic_regular_guest!(FOCAL_IMAGE_NAME) .with_kernel(fw_path(FwType::RustHypervisorFirmware)); _test_simple_launch(&guest); @@ -56,9 +55,7 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_focal_ovmf() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let mut guest = Guest::new(Box::new(disk_config)); - guest.kernel_path = Some(fw_path(FwType::Ovmf)); + let guest = basic_regular_guest!(FOCAL_IMAGE_NAME).with_kernel(fw_path(FwType::Ovmf)); _test_simple_launch(&guest); } From ac8d4c295352507210e49bb031f7b40621694d53 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 00:39:00 -0700 Subject: [PATCH 0858/1893] 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 --- .../tests/common/tests_wrappers.rs | 82 ++++++++++++++++++ cloud-hypervisor/tests/integration.rs | 86 +------------------ 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index afe54ed5ef..fa14ac6b93 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2057,3 +2057,85 @@ pub(crate) fn _test_virtio_block( ); } } + +pub fn _test_virtio_block_dynamic_vhdx_expand(guest: &Guest) { + const VIRTUAL_DISK_SIZE: u64 = 100 << 20; + const EMPTY_VHDX_FILE_SIZE: u64 = 8 << 20; + const FULL_VHDX_FILE_SIZE: u64 = 112 << 20; + const DYNAMIC_VHDX_NAME: &str = "dynamic.vhdx"; + + let vhdx_pathbuf = guest.tmp_dir.as_path().join(DYNAMIC_VHDX_NAME); + let vhdx_path = vhdx_pathbuf.to_str().unwrap(); + + // Generate a 100 MiB dynamic VHDX file + std::process::Command::new("qemu-img") + .arg("create") + .args(["-f", "vhdx"]) + .arg(vhdx_path) + .arg(VIRTUAL_DISK_SIZE.to_string()) + .output() + .expect("Expect generating dynamic VHDX image"); + + // Check if the size matches with empty VHDx file size + assert_eq!(vhdx_image_size(vhdx_path), EMPTY_VHDX_FILE_SIZE); + + let mut cloud_child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={vhdx_path}").as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Check both if /dev/vdc exists and if the block size is 100 MiB. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 100M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Write 100 MB of data to the VHDx disk + guest + .ssh_command("sudo dd if=/dev/urandom of=/dev/vdc bs=1M count=100") + .unwrap(); + }); + + // Check if the size matches with expected expanded VHDx file size + assert_eq!(vhdx_image_size(vhdx_path), FULL_VHDX_FILE_SIZE); + + kill_child(&mut cloud_child); + let output = cloud_child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + + disk_check_consistency(vhdx_path, None); +} + +fn vhdx_image_size(disk_name: &str) -> u64 { + std::fs::File::open(disk_name) + .unwrap() + .seek(SeekFrom::End(0)) + .unwrap() +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 972b979fb9..b17ecfa7e4 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1601,90 +1601,8 @@ mod common_parallel { #[test] fn test_virtio_block_dynamic_vhdx_expand() { - const VIRTUAL_DISK_SIZE: u64 = 100 << 20; - const EMPTY_VHDX_FILE_SIZE: u64 = 8 << 20; - const FULL_VHDX_FILE_SIZE: u64 = 112 << 20; - const DYNAMIC_VHDX_NAME: &str = "dynamic.vhdx"; - - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - - let vhdx_pathbuf = guest.tmp_dir.as_path().join(DYNAMIC_VHDX_NAME); - let vhdx_path = vhdx_pathbuf.to_str().unwrap(); - - // Generate a 100 MiB dynamic VHDX file - std::process::Command::new("qemu-img") - .arg("create") - .args(["-f", "vhdx"]) - .arg(vhdx_path) - .arg(VIRTUAL_DISK_SIZE.to_string()) - .output() - .expect("Expect generating dynamic VHDX image"); - - // Check if the size matches with empty VHDx file size - assert_eq!(vhdx_image_size(vhdx_path), EMPTY_VHDX_FILE_SIZE); - - let mut cloud_child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args([ - "--disk", - format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - format!( - "path={}", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - format!("path={vhdx_path}").as_str(), - ]) - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Check both if /dev/vdc exists and if the block size is 100 MiB. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 100M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Write 100 MB of data to the VHDx disk - guest - .ssh_command("sudo dd if=/dev/urandom of=/dev/vdc bs=1M count=100") - .unwrap(); - }); - - // Check if the size matches with expected expanded VHDx file size - assert_eq!(vhdx_image_size(vhdx_path), FULL_VHDX_FILE_SIZE); - - kill_child(&mut cloud_child); - let output = cloud_child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - - disk_check_consistency(vhdx_path, None); - } - - fn vhdx_image_size(disk_name: &str) -> u64 { - std::fs::File::open(disk_name) - .unwrap() - .seek(SeekFrom::End(0)) - .unwrap() + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_virtio_block_dynamic_vhdx_expand(&guest); } #[test] From 2929cc9d1615d4bd354b9519bb90f1c213ffe751 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 00:50:38 -0700 Subject: [PATCH 0859/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index e2b7b08add..6e5b0bd32c 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -194,4 +194,10 @@ mod common_cvm { ); _test_virtio_block(&guest, false, false, true, true, ImageType::Qcow2); } + + #[test] + fn test_virtio_block_dynamic_vhdx_expand() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_virtio_block_dynamic_vhdx_expand(&guest); + } } From 56696ed901d6f0045eb9e5fd6c91a635562235a4 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 00:55:52 -0700 Subject: [PATCH 0860/1893] 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 --- .../tests/common/tests_wrappers.rs | 41 ++++++++++++++++++ cloud-hypervisor/tests/integration.rs | 43 +------------------ 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index fa14ac6b93..2663a6499d 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2139,3 +2139,44 @@ fn vhdx_image_size(disk_name: &str) -> u64 { .seek(SeekFrom::End(0)) .unwrap() } + +#[cfg(target_arch = "x86_64")] +pub fn _test_split_irqchip(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("grep -c IO-APIC.*timer /proc/interrupts || true") + .unwrap() + .trim() + .parse::() + .unwrap_or(1), + 0 + ); + assert_eq!( + guest + .ssh_command("grep -c IO-APIC.*cascade /proc/interrupts || true") + .unwrap() + .trim() + .parse::() + .unwrap_or(1), + 0 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index b17ecfa7e4..1b6562fab5 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1731,47 +1731,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_split_irqchip() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!( - guest - .ssh_command("grep -c IO-APIC.*timer /proc/interrupts || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); - assert_eq!( - guest - .ssh_command("grep -c IO-APIC.*cascade /proc/interrupts || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_split_irqchip(&guest); } #[test] From b65a3a58f379b9effe5c4943b3c25013920d52ba Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 00:56:42 -0700 Subject: [PATCH 0861/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 6e5b0bd32c..1df8d0f115 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -200,4 +200,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_virtio_block_dynamic_vhdx_expand(&guest); } + + #[test] + fn test_split_irqchip() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_split_irqchip(&guest); + } } From 5f4ad4bb1e78d5311b692dccb153259b9c6be395 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 16:08:51 -0700 Subject: [PATCH 0862/1893] 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 --- .../tests/common/tests_wrappers.rs | 30 +++++++++++++++++ cloud-hypervisor/tests/integration.rs | 32 ++----------------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 2663a6499d..053cdf4414 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2180,3 +2180,33 @@ pub fn _test_split_irqchip(guest: &Guest) { handle_child_output(r, &output); } + +#[cfg(target_arch = "x86_64")] +pub(crate) fn _test_dmi_serial_number(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline_with_platform(Some("serial_number=a=b;c=d")) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("sudo cat /sys/class/dmi/id/product_serial") + .unwrap() + .trim(), + "a=b;c=d" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 1b6562fab5..9cb2dc209d 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1738,37 +1738,9 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_dmi_serial_number() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(["--platform", "serial_number=a=b;c=d"]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!( - guest - .ssh_command("sudo cat /sys/class/dmi/id/product_serial") - .unwrap() - .trim(), - "a=b;c=d" - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); - handle_child_output(r, &output); + _test_dmi_serial_number(&guest); } #[test] From b1a0ab4d1f78daac35c62e03aed3c87b0c0eb92a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:34:09 -0700 Subject: [PATCH 0863/1893] 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 --- cloud-hypervisor/tests/integration.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 9cb2dc209d..b09d72cf9c 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -61,8 +61,7 @@ mod common_parallel { #[test] fn test_multi_cpu() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); _test_multi_cpu(&guest); } From bcdbc875be71fced1ccbddc84a6bdcac634cb096 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:34:21 -0700 Subject: [PATCH 0864/1893] 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 --- cloud-hypervisor/tests/integration.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index b09d72cf9c..5927597393 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -164,10 +164,7 @@ mod common_parallel { #[test] fn test_cpu_affinity() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(2); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(2); _test_cpu_affinity(&guest); } From 43fb142afb135378745da114598463486e585ef3 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:34:34 -0700 Subject: [PATCH 0865/1893] 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 --- cloud-hypervisor/tests/integration.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5927597393..3d8c3a8917 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -170,10 +170,7 @@ mod common_parallel { #[test] fn test_virtio_queue_affinity() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(4); _test_virtio_queue_affinity(&guest); } From 43731149c874109366db5fa31b332bbc8c13b599 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:34:46 -0700 Subject: [PATCH 0866/1893] 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 --- cloud-hypervisor/tests/integration.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 3d8c3a8917..82f9c54530 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -243,8 +243,7 @@ mod common_parallel { #[test] fn test_power_button() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); _test_power_button(&guest); } From 4c4cf940ec2e5041698377ae02b14a5b6b85cf2a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:35:01 -0700 Subject: [PATCH 0867/1893] 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 --- cloud-hypervisor/tests/integration.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 82f9c54530..2f0a85f137 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -402,8 +402,7 @@ mod common_parallel { #[test] fn test_pci_msi() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); _test_pci_msi(&guest); } From 54c91b02814addfe1992104b72de94143e6a0c87 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:35:13 -0700 Subject: [PATCH 0868/1893] 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 --- cloud-hypervisor/tests/integration.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 2f0a85f137..ca7cc88378 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -408,8 +408,7 @@ mod common_parallel { #[test] fn test_virtio_net_ctrl_queue() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); _test_virtio_net_ctrl_queue(&guest); } From 84951127b7509a47d1e05aaa1d0280e1093e1002 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:35:29 -0700 Subject: [PATCH 0869/1893] 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 --- cloud-hypervisor/tests/integration.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index ca7cc88378..2eac9f0697 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -414,8 +414,7 @@ mod common_parallel { #[test] fn test_pci_multiple_segments() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); _test_pci_multiple_segments(&guest, MAX_NUM_PCI_SEGMENTS, 15u16); } From a6724ba79f1b13736c3666ffae8267bfa0df4e81 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:35:41 -0700 Subject: [PATCH 0870/1893] 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 --- cloud-hypervisor/tests/integration.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 2eac9f0697..acbbdbb27b 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -499,8 +499,7 @@ mod common_parallel { #[test] fn test_direct_kernel_boot() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); _test_direct_kernel_boot(&guest); } From b9c9e7265d1ad19ab72ef1269d353094084a539d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:35:53 -0700 Subject: [PATCH 0871/1893] 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 --- cloud-hypervisor/tests/integration.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index acbbdbb27b..b5c8f04773 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2724,8 +2724,7 @@ mod common_parallel { #[test] fn test_virtio_vsock() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); _test_virtio_vsock(&guest, false); } From aead6dd59f96f5bdfdf2ef829ec91cc4f502484d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:36:12 -0700 Subject: [PATCH 0872/1893] 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 --- cloud-hypervisor/tests/integration.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index b5c8f04773..d56c25f783 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2730,13 +2730,11 @@ mod common_parallel { #[test] fn test_virtio_vsock_hotplug() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); #[cfg(target_arch = "x86_64")] - let guest = GuestFactory::new_regular_guest_factory().create_guest(Box::new(disk_config)); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); #[cfg(target_arch = "aarch64")] - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_kernel_path(edk2_path().to_str().unwrap()); + let guest = + basic_regular_guest!(JAMMY_IMAGE_NAME).with_kernel_path(edk2_path().to_str().unwrap()); _test_virtio_vsock(&guest, true); } From e5b589ee542f8e5db8de7dd583a1bee7c0136ca8 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:37:19 -0700 Subject: [PATCH 0873/1893] 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 --- cloud-hypervisor/tests/integration.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index d56c25f783..2c68b82f17 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2740,10 +2740,7 @@ mod common_parallel { #[test] fn test_api_http_shutdown() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_shutdown(&target_api, &guest); From 1b3cd88b25ba365a97e7b8b4f30283cc3a28fffd Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:37:32 -0700 Subject: [PATCH 0874/1893] 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 --- cloud-hypervisor/tests/integration.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 2c68b82f17..97009f470d 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2748,10 +2748,7 @@ mod common_parallel { #[test] fn test_api_http_delete() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_delete(&target_api, &guest); From 698084f3d7d521d1f39a00ae48b401f4fdf63d3b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:37:45 -0700 Subject: [PATCH 0875/1893] 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 --- cloud-hypervisor/tests/integration.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 97009f470d..4d5f56270a 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2756,10 +2756,7 @@ mod common_parallel { #[test] fn test_api_http_pause_resume() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_pause_resume(&target_api, &guest); From 6f9794ded04d4f1d02a395b1f8da902b0bdcb2e7 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:37:58 -0700 Subject: [PATCH 0876/1893] 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 --- cloud-hypervisor/tests/integration.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 4d5f56270a..e0279c1f0e 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2764,10 +2764,7 @@ mod common_parallel { #[test] fn test_api_http_create_boot() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(4); let target_api = TargetApi::new_http_api(&guest.tmp_dir); _test_api_create_boot(&target_api, &guest); From dcb4d99a50760c26894ffac12ec23f28ce51ccd7 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:49:45 -0700 Subject: [PATCH 0877/1893] 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 --- .../tests/common/tests_wrappers.rs | 29 ++++++++++++++++ cloud-hypervisor/tests/integration.rs | 33 ++----------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 053cdf4414..972c115f0e 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2210,3 +2210,32 @@ pub(crate) fn _test_dmi_serial_number(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_dmi_uuid(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline_with_platform(Some("uuid=1e8aa28a-435d-4027-87f4-40dceff1fa0a")) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("sudo cat /sys/class/dmi/id/product_uuid") + .unwrap() + .trim(), + "1e8aa28a-435d-4027-87f4-40dceff1fa0a" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} \ No newline at end of file diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index e0279c1f0e..fc78b62fe7 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1734,37 +1734,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_dmi_uuid() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(["--platform", "uuid=1e8aa28a-435d-4027-87f4-40dceff1fa0a"]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!( - guest - .ssh_command("sudo cat /sys/class/dmi/id/product_uuid") - .unwrap() - .trim(), - "1e8aa28a-435d-4027-87f4-40dceff1fa0a" - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_dmi_uuid(&guest); } #[test] From 43641cf7146edfbf84852e55aad4a58e2ee17492 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:51:16 -0700 Subject: [PATCH 0878/1893] 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 --- cloud-hypervisor/tests/common/tests_wrappers.rs | 2 +- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 972c115f0e..d421e9c136 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2238,4 +2238,4 @@ pub(crate) fn _test_dmi_uuid(guest: &Guest) { let output = child.wait_with_output().unwrap(); handle_child_output(r, &output); -} \ No newline at end of file +} diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 1df8d0f115..e599541f6c 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -206,4 +206,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_split_irqchip(&guest); } + + #[test] + fn test_dmi_uuid() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_dmi_uuid(&guest); + } } From c3976ccbdae0fe86c109dbd47f0cd3c590f99161 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:56:23 -0700 Subject: [PATCH 0879/1893] 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 --- .../tests/common/tests_wrappers.rs | 50 +++++++++++++++++ cloud-hypervisor/tests/integration.rs | 54 +------------------ 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index d421e9c136..7c1c19e6be 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2239,3 +2239,53 @@ pub(crate) fn _test_dmi_uuid(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_dmi_oem_strings(guest: &Guest) { + let s1 = "io.systemd.credential:xx=yy"; + let s2 = "This is a test string"; + + let oem_strings = format!("oem_strings=[{s1},{s2}]"); + + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline_with_platform(Some(&oem_strings)) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("sudo dmidecode --oem-string count") + .unwrap() + .trim(), + "2" + ); + + assert_eq!( + guest + .ssh_command("sudo dmidecode --oem-string 1") + .unwrap() + .trim(), + s1 + ); + + assert_eq!( + guest + .ssh_command("sudo dmidecode --oem-string 2") + .unwrap() + .trim(), + s2 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index fc78b62fe7..25b043e8f2 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1741,58 +1741,8 @@ mod common_parallel { #[test] #[cfg(target_arch = "x86_64")] fn test_dmi_oem_strings() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let s1 = "io.systemd.credential:xx=yy"; - let s2 = "This is a test string"; - - let oem_strings = format!("oem_strings=[{s1},{s2}]"); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(["--platform", &oem_strings]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!( - guest - .ssh_command("sudo dmidecode --oem-string count") - .unwrap() - .trim(), - "2" - ); - - assert_eq!( - guest - .ssh_command("sudo dmidecode --oem-string 1") - .unwrap() - .trim(), - s1 - ); - - assert_eq!( - guest - .ssh_command("sudo dmidecode --oem-string 2") - .unwrap() - .trim(), - s2 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_dmi_oem_strings(&guest); } #[test] From b7a7366ef90ac2b40f3dc425af8a5a838ced4cf4 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 17:56:38 -0700 Subject: [PATCH 0880/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index e599541f6c..fd47e21f01 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -212,4 +212,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_dmi_uuid(&guest); } + + #[test] + fn test_dmi_oem_strings() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_dmi_oem_strings(&guest); + } } From 8d9e8ea6f822810b0bd25b440fe763e8628b08ef Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:20:42 -0700 Subject: [PATCH 0881/1893] 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 --- .../tests/common/tests_wrappers.rs | 33 +++++++++++++++++ cloud-hypervisor/tests/integration.rs | 35 ++----------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 7c1c19e6be..f983de5519 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2289,3 +2289,36 @@ pub(crate) fn _test_dmi_oem_strings(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_serial_off(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .args(["--serial", "off"]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Test that there is no ttyS0 + assert_eq!( + guest + .ssh_command(GREP_SERIAL_IRQ_CMD) + .unwrap() + .trim() + .parse::() + .unwrap_or(1), + 0 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 25b043e8f2..990847de35 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1936,39 +1936,8 @@ mod common_parallel { #[test] fn test_serial_off() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .args(["--serial", "off"]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Test that there is no ttyS0 - assert_eq!( - guest - .ssh_command(GREP_SERIAL_IRQ_CMD) - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_serial_off(&guest); } #[test] From 77266daf86d31b2a3753e5fe1c0cbbb0021cba1b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:21:54 -0700 Subject: [PATCH 0882/1893] 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 --- .../tests/common/tests_wrappers.rs | 40 +++++++++++++++++ cloud-hypervisor/tests/integration.rs | 45 +------------------ 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index f983de5519..c1a21986bb 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2322,3 +2322,43 @@ pub(crate) fn _test_serial_off(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_multiple_network_interfaces(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .args([ + "--net", + guest.default_net_string().as_str(), + "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.128", + "tap=mytap1,mac=fe:1f:9e:e1:60:f2,ip=192.168.4.1,mask=255.255.255.128", + ]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + let tap_count = exec_host_command_output("ip link | grep -c mytap1"); + assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1"); + + // 3 network interfaces + default localhost ==> 4 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 990847de35..3677fbe2ad 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1852,49 +1852,8 @@ mod common_parallel { #[test] fn test_multiple_network_interfaces() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let kernel_path = direct_kernel_boot_path(); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args([ - "--net", - guest.default_net_string().as_str(), - "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.128", - "tap=mytap1,mac=fe:1f:9e:e1:60:f2,ip=192.168.4.1,mask=255.255.255.128", - ]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - let tap_count = exec_host_command_output("ip link | grep -c mytap1"); - assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1"); - - // 3 network interfaces + default localhost ==> 4 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 4 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_multiple_network_interfaces(&guest); } #[test] From dfd07ae6d0f48c1f856a60d642594543fdc4c4a9 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:22:59 -0700 Subject: [PATCH 0883/1893] 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 --- .../tests/common/tests_wrappers.rs | 39 ++++++++++++++++ cloud-hypervisor/tests/integration.rs | 44 +------------------ 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index c1a21986bb..874ad795c3 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2362,3 +2362,42 @@ pub(crate) fn _test_multiple_network_interfaces(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_virtio_console(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .args(["--console", "tty"]) + .args(["--serial", "null"]) + .capture_output() + .spawn() + .unwrap(); + + let text = String::from("On a branch floating down river a cricket, singing."); + let cmd = format!("echo {text} | sudo tee /dev/hvc0"); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert!( + guest + .does_device_vendor_pair_match("0x1043", "0x1af4") + .unwrap_or_default() + ); + + guest.ssh_command(&cmd).unwrap(); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&text)); + }); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 3677fbe2ad..55f081d12f 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2197,48 +2197,8 @@ mod common_parallel { #[test] fn test_virtio_console() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let kernel_path = direct_kernel_boot_path(); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .args(["--console", "tty"]) - .args(["--serial", "null"]) - .capture_output() - .spawn() - .unwrap(); - - let text = String::from("On a branch floating down river a cricket, singing."); - let cmd = format!("echo {text} | sudo tee /dev/hvc0"); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert!( - guest - .does_device_vendor_pair_match("0x1043", "0x1af4") - .unwrap_or_default() - ); - - guest.ssh_command(&cmd).unwrap(); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); - - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&text)); - }); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_virtio_console(&guest); } #[test] From fb02146e2cbc99767e9e41f437fbfe5c4062494b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:24:50 -0700 Subject: [PATCH 0884/1893] 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 --- .../tests/common/tests_wrappers.rs | 48 ++++++++++++++++++- cloud-hypervisor/tests/integration.rs | 48 +------------------ 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 874ad795c3..f268ed75dd 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::ffi::CStr; use std::fs::{self, OpenOptions}; -use std::io::{Seek, SeekFrom, Write}; +use std::io::{Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; use std::string::String; use std::sync::mpsc; @@ -14,6 +14,7 @@ use net_util::MacAddr; use test_infra::*; use vmm_sys_util::tempdir::TempDir; use vmm_sys_util::tempfile::TempFile; +use wait_timeout::ChildExt; use crate::common::utils::{TargetApi, *}; @@ -2401,3 +2402,48 @@ pub(crate) fn _test_virtio_console(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_console_file(guest: &Guest) { + let console_path = guest.tmp_dir.as_path().join("console-output"); + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .args([ + "--console", + format!("file={}", console_path.to_str().unwrap()).as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + guest.wait_vm_boot().unwrap(); + + guest.ssh_command("sudo shutdown -h now").unwrap(); + + let _ = child.wait_timeout(std::time::Duration::from_secs(20)); + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + let r = std::panic::catch_unwind(|| { + // Check that the cloud-hypervisor binary actually terminated + assert!(output.status.success()); + + // Do this check after shutdown of the VM as an easy way to ensure + // all writes are flushed to disk + let mut f = std::fs::File::open(console_path).unwrap(); + let mut buf = String::new(); + f.read_to_string(&mut buf).unwrap(); + + if !buf.contains(CONSOLE_TEST_STRING) { + eprintln!( + "\n\n==== Console file output ====\n\n{buf}\n\n==== End console file output ====" + ); + } + assert!(buf.contains(CONSOLE_TEST_STRING)); + }); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 55f081d12f..4e63ad9320 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2203,52 +2203,8 @@ mod common_parallel { #[test] fn test_console_file() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let console_path = guest.tmp_dir.as_path().join("console-output"); - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .args([ - "--console", - format!("file={}", console_path.to_str().unwrap()).as_str(), - ]) - .capture_output() - .spawn() - .unwrap(); - - guest.wait_vm_boot().unwrap(); - - guest.ssh_command("sudo shutdown -h now").unwrap(); - - let _ = child.wait_timeout(std::time::Duration::from_secs(20)); - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - let r = std::panic::catch_unwind(|| { - // Check that the cloud-hypervisor binary actually terminated - assert!(output.status.success()); - - // Do this check after shutdown of the VM as an easy way to ensure - // all writes are flushed to disk - let mut f = std::fs::File::open(console_path).unwrap(); - let mut buf = String::new(); - f.read_to_string(&mut buf).unwrap(); - - if !buf.contains(CONSOLE_TEST_STRING) { - eprintln!( - "\n\n==== Console file output ====\n\n{buf}\n\n==== End console file output ====" - ); - } - assert!(buf.contains(CONSOLE_TEST_STRING)); - }); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_console_file(&guest); } #[test] From bff6e40eaaca6de2b495a776140ad8411af38420 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:25:41 -0700 Subject: [PATCH 0885/1893] 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 --- .../tests/common/tests_wrappers.rs | 24 ++++++++++++++ cloud-hypervisor/tests/integration.rs | 33 ++----------------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index f268ed75dd..dbabe1503b 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2447,3 +2447,27 @@ pub(crate) fn _test_console_file(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_direct_kernel_boot_noacpi(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); + assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 4e63ad9320..ec8db5f8ee 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2455,36 +2455,9 @@ mod common_parallel { #[test] fn test_direct_kernel_boot_noacpi() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let kernel_path = direct_kernel_boot_path(); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args([ - "--cmdline", - format!("{DIRECT_KERNEL_BOOT_CMDLINE} acpi=off").as_str(), - ]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let mut guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + guest.kernel_cmdline = Some(format!("{DIRECT_KERNEL_BOOT_CMDLINE} acpi=off")); + _test_direct_kernel_boot_noacpi(&guest); } #[test] From b478fa0ffd0a48f52e37ff405905443e721a5a97 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:27:35 -0700 Subject: [PATCH 0886/1893] 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 --- .../tests/common/tests_wrappers.rs | 81 ++++++++++++++++ cloud-hypervisor/tests/integration.rs | 95 +------------------ 2 files changed, 86 insertions(+), 90 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index dbabe1503b..5088192b11 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2471,3 +2471,84 @@ pub(crate) fn _test_direct_kernel_boot_noacpi(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_pci_bar_reprogramming(guest: &Guest) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .args([ + "--net", + guest.default_net_string().as_str(), + "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.128", + ]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // 2 network interfaces + default localhost ==> 3 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + let init_bar_addr = guest + .ssh_command("sudo awk '{print $1; exit}' /sys/bus/pci/devices/0000:00:05.0/resource") + .unwrap(); + + // Remove the PCI device + guest + .ssh_command("echo 1 | sudo tee /sys/bus/pci/devices/0000:00:05.0/remove") + .unwrap(); + + // Only 1 network interface left + default localhost ==> 2 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 2 + ); + + // Remove the PCI device + guest + .ssh_command("echo 1 | sudo tee /sys/bus/pci/rescan") + .unwrap(); + + // Back to 2 network interface + default localhost ==> 3 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + let new_bar_addr = guest + .ssh_command("sudo awk '{print $1; exit}' /sys/bus/pci/devices/0000:00:05.0/resource") + .unwrap(); + + // Let's compare the BAR addresses for our virtio-net device. + // They should be different as we expect the BAR reprogramming + // to have happened. + assert_ne!(init_bar_addr, new_bar_addr); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index ec8db5f8ee..746291f36f 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2524,97 +2524,12 @@ mod common_parallel { // properly probed first, then removing it, and adding it again by doing a // rescan. fn test_pci_bar_reprogramming() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - #[cfg(target_arch = "x86_64")] - let kernel_path = direct_kernel_boot_path(); #[cfg(target_arch = "aarch64")] - let kernel_path = edk2_path(); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args([ - "--net", - guest.default_net_string().as_str(), - "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.128", - ]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // 2 network interfaces + default localhost ==> 3 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - let init_bar_addr = guest - .ssh_command( - "sudo awk '{print $1; exit}' /sys/bus/pci/devices/0000:00:05.0/resource", - ) - .unwrap(); - - // Remove the PCI device - guest - .ssh_command("echo 1 | sudo tee /sys/bus/pci/devices/0000:00:05.0/remove") - .unwrap(); - - // Only 1 network interface left + default localhost ==> 2 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 2 - ); - - // Remove the PCI device - guest - .ssh_command("echo 1 | sudo tee /sys/bus/pci/rescan") - .unwrap(); - - // Back to 2 network interface + default localhost ==> 3 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - let new_bar_addr = guest - .ssh_command( - "sudo awk '{print $1; exit}' /sys/bus/pci/devices/0000:00:05.0/resource", - ) - .unwrap(); - - // Let's compare the BAR addresses for our virtio-net device. - // They should be different as we expect the BAR reprogramming - // to have happened. - assert_ne!(init_bar_addr, new_bar_addr); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = + basic_regular_guest!(JAMMY_IMAGE_NAME).with_kernel_path(edk2_path().to_str().unwrap()); + #[cfg(target_arch = "x86_64")] + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_pci_bar_reprogramming(&guest); } #[test] From af7fabd2c665a0ff409af2b0bd1261cbf5bbfe72 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:29:16 -0700 Subject: [PATCH 0887/1893] 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 --- .../tests/common/tests_wrappers.rs | 25 ++++++++++++++ cloud-hypervisor/tests/integration.rs | 34 +++---------------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 5088192b11..965e644333 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2552,3 +2552,28 @@ pub(crate) fn _test_pci_bar_reprogramming(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_memory_overhead(guest: &Guest, guest_memory_size_kb: u32) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_net() + .default_disks() + .capture_output() + .spawn() + .unwrap(); + + guest.wait_vm_boot().unwrap(); + + let r = std::panic::catch_unwind(|| { + let overhead = get_vmm_overhead(child.id(), guest_memory_size_kb); + eprintln!("Guest memory overhead: {overhead} vs {MAXIMUM_VMM_OVERHEAD_KB}"); + assert!(overhead <= MAXIMUM_VMM_OVERHEAD_KB); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 746291f36f..cb3616cc44 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2852,36 +2852,10 @@ mod common_parallel { #[test] fn test_memory_overhead() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let kernel_path = direct_kernel_boot_path(); - - let guest_memory_size_kb = 512 * 1024; - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .args(["--memory", format!("size={guest_memory_size_kb}K").as_str()]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_net() - .default_disks() - .capture_output() - .spawn() - .unwrap(); - - guest.wait_vm_boot().unwrap(); - - let r = std::panic::catch_unwind(|| { - let overhead = get_vmm_overhead(child.id(), guest_memory_size_kb); - eprintln!("Guest memory overhead: {overhead} vs {MAXIMUM_VMM_OVERHEAD_KB}"); - assert!(overhead <= MAXIMUM_VMM_OVERHEAD_KB); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest_memory_size_kb: u32 = 512 * 1024; + let guest = + basic_regular_guest!(JAMMY_IMAGE_NAME).with_memory(&format!("{guest_memory_size_kb}K")); + _test_memory_overhead(&guest, guest_memory_size_kb); } #[test] From c1d929d0cf243b6d3f752e5ce92e1eaee9444ed9 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:30:30 -0700 Subject: [PATCH 0888/1893] 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 --- .../tests/common/tests_wrappers.rs | 54 ++++++++++++++++ cloud-hypervisor/tests/integration.rs | 62 +------------------ 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 965e644333..ecfc9ac2e2 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2577,3 +2577,57 @@ pub(crate) fn _test_memory_overhead(guest: &Guest, guest_memory_size_kb: u32) { handle_child_output(r, &output); } + +pub(crate) fn _test_landlock(guest: &Guest) { + let api_socket = temp_api_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(guest) + .args(["--api-socket", &api_socket]) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .args(["--landlock"]) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Check /dev/vdc is not there + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc.*16M || true") + .unwrap() + .trim() + .parse::() + .unwrap_or(1), + 0 + ); + + // Now let's add the extra disk. + let mut blk_file_path = dirs::home_dir().unwrap(); + blk_file_path.push("workloads"); + blk_file_path.push("blk.img"); + // As the path to the hotplug disk is not pre-added, this remote + // command will fail. + assert!(!remote_command( + &api_socket, + "add-disk", + Some( + format!( + "path={},id=test0,readonly=true", + blk_file_path.to_str().unwrap() + ) + .as_str() + ), + )); + }); + + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index cb3616cc44..18801ff1d3 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2864,66 +2864,8 @@ mod common_parallel { // the path for the hotplug disk is not pre-added to Landlock rules, this // the test will result in a failure. fn test_landlock() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - #[cfg(target_arch = "x86_64")] - let kernel_path = direct_kernel_boot_path(); - #[cfg(target_arch = "aarch64")] - let kernel_path = edk2_path(); - - let api_socket = temp_api_path(&guest.tmp_dir); - - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket]) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(["--landlock"]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Check /dev/vdc is not there - assert_eq!( - guest - .ssh_command("lsblk | grep -c vdc.*16M || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); - - // Now let's add the extra disk. - let mut blk_file_path = dirs::home_dir().unwrap(); - blk_file_path.push("workloads"); - blk_file_path.push("blk.img"); - // As the path to the hotplug disk is not pre-added, this remote - // command will fail. - assert!(!remote_command( - &api_socket, - "add-disk", - Some( - format!( - "path={},id=test0,readonly=true", - blk_file_path.to_str().unwrap() - ) - .as_str() - ), - )); - }); - - let _ = child.kill(); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_landlock(&guest); } fn _test_disk_hotplug(landlock_enabled: bool) { From 8f40aed0ca1e5074269ba6c90c9465d2131d82cd Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:32:15 -0700 Subject: [PATCH 0889/1893] 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 --- .../tests/common/tests_wrappers.rs | 172 ++++++++++++++++ cloud-hypervisor/tests/integration.rs | 188 +----------------- 2 files changed, 179 insertions(+), 181 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index ecfc9ac2e2..f4379127c7 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2631,3 +2631,175 @@ pub(crate) fn _test_landlock(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_disk_hotplug(guest: &Guest, landlock_enabled: bool) { + let api_socket = temp_api_path(&guest.tmp_dir); + + let mut blk_file_path = dirs::home_dir().unwrap(); + blk_file_path.push("workloads"); + blk_file_path.push("blk.img"); + + let mut cmd = GuestCommand::new(guest); + if landlock_enabled { + cmd.args(["--landlock"]).args([ + "--landlock-rules", + format!("path={blk_file_path:?},access=rw").as_str(), + ]); + } + + cmd.args(["--api-socket", &api_socket]) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .default_net() + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Check /dev/vdc is not there + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc.*16M || true") + .unwrap() + .trim() + .parse::() + .unwrap_or(1), + 0 + ); + + // Now let's add the extra disk. + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-disk", + Some( + format!( + "path={},id=test0,readonly=true", + blk_file_path.to_str().unwrap() + ) + .as_str(), + ), + ); + assert!(cmd_success); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); + + thread::sleep(std::time::Duration::new(10, 0)); + + // Check that /dev/vdc exists and the block size is 16M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 16M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + // And check the block device can be read. + guest + .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M iflag=direct count=16") + .unwrap(); + + // Let's remove it the extra disk. + assert!(remote_command(&api_socket, "remove-device", Some("test0"))); + thread::sleep(std::time::Duration::new(5, 0)); + // And check /dev/vdc is not there + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc.*16M || true") + .unwrap() + .trim() + .parse::() + .unwrap_or(1), + 0 + ); + + // And add it back to validate unplug did work correctly. + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-disk", + Some( + format!( + "path={},id=test0,readonly=true", + blk_file_path.to_str().unwrap() + ) + .as_str(), + ), + ); + assert!(cmd_success); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); + + thread::sleep(std::time::Duration::new(10, 0)); + + // Check that /dev/vdc exists and the block size is 16M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 16M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + // And check the block device can be read. + guest + .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M iflag=direct count=16") + .unwrap(); + + // Reboot the VM. + guest.reboot_linux(0); + + // Check still there after reboot + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 16M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + assert!(remote_command(&api_socket, "remove-device", Some("test0"))); + + thread::sleep(std::time::Duration::new(20, 0)); + + // Check device has gone away + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc.*16M || true") + .unwrap() + .trim() + .parse::() + .unwrap_or(1), + 0 + ); + + guest.reboot_linux(1); + + // Check device still absent + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdc.*16M || true") + .unwrap() + .trim() + .parse::() + .unwrap_or(1), + 0 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 18801ff1d3..4bc3a36bbb 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -2868,196 +2868,22 @@ mod common_parallel { _test_landlock(&guest); } - fn _test_disk_hotplug(landlock_enabled: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - + #[test] + fn test_disk_hotplug() { #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); #[cfg(target_arch = "aarch64")] let kernel_path = edk2_path(); - - let api_socket = temp_api_path(&guest.tmp_dir); - - let mut blk_file_path = dirs::home_dir().unwrap(); - blk_file_path.push("workloads"); - blk_file_path.push("blk.img"); - - let mut cmd = GuestCommand::new(&guest); - if landlock_enabled { - cmd.args(["--landlock"]).args([ - "--landlock-rules", - format!("path={blk_file_path:?},access=rw").as_str(), - ]); - } - - cmd.args(["--api-socket", &api_socket]) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .capture_output(); - - let mut child = cmd.spawn().unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Check /dev/vdc is not there - assert_eq!( - guest - .ssh_command("lsblk | grep -c vdc.*16M || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); - - // Now let's add the extra disk. - let (cmd_success, cmd_output) = remote_command_w_output( - &api_socket, - "add-disk", - Some( - format!( - "path={},id=test0,readonly=true", - blk_file_path.to_str().unwrap() - ) - .as_str(), - ), - ); - assert!(cmd_success); - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") - ); - - thread::sleep(std::time::Duration::new(10, 0)); - - // Check that /dev/vdc exists and the block size is 16M. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 16M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - // And check the block device can be read. - guest - .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M iflag=direct count=16") - .unwrap(); - - // Let's remove it the extra disk. - assert!(remote_command(&api_socket, "remove-device", Some("test0"))); - thread::sleep(std::time::Duration::new(5, 0)); - // And check /dev/vdc is not there - assert_eq!( - guest - .ssh_command("lsblk | grep -c vdc.*16M || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); - - // And add it back to validate unplug did work correctly. - let (cmd_success, cmd_output) = remote_command_w_output( - &api_socket, - "add-disk", - Some( - format!( - "path={},id=test0,readonly=true", - blk_file_path.to_str().unwrap() - ) - .as_str(), - ), - ); - assert!(cmd_success); - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") - ); - - thread::sleep(std::time::Duration::new(10, 0)); - - // Check that /dev/vdc exists and the block size is 16M. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 16M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - // And check the block device can be read. - guest - .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M iflag=direct count=16") - .unwrap(); - - // Reboot the VM. - guest.reboot_linux(0); - - // Check still there after reboot - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 16M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - assert!(remote_command(&api_socket, "remove-device", Some("test0"))); - - thread::sleep(std::time::Duration::new(20, 0)); - - // Check device has gone away - assert_eq!( - guest - .ssh_command("lsblk | grep -c vdc.*16M || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); - - guest.reboot_linux(1); - - // Check device still absent - assert_eq!( - guest - .ssh_command("lsblk | grep -c vdc.*16M || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - } - - #[test] - fn test_disk_hotplug() { - _test_disk_hotplug(false); + let guest = + basic_regular_guest!(JAMMY_IMAGE_NAME).with_kernel_path(kernel_path.to_str().unwrap()); + _test_disk_hotplug(&guest, false); } #[test] #[cfg(target_arch = "x86_64")] fn test_disk_hotplug_with_landlock() { - _test_disk_hotplug(true); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_disk_hotplug(&guest, true); } #[test] From ef81bb135898932dd355357e8df09e055b0d6cee Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:35:09 -0700 Subject: [PATCH 0890/1893] 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 --- .../tests/common/tests_wrappers.rs | 65 +++++++++++++++++ cloud-hypervisor/tests/integration.rs | 71 +------------------ 2 files changed, 67 insertions(+), 69 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index f4379127c7..4c39043aa3 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2803,3 +2803,68 @@ pub(crate) fn _test_disk_hotplug(guest: &Guest, landlock_enabled: bool) { handle_child_output(r, &output); } + +pub(crate) fn _test_virtio_block_topology(guest: &Guest, loop_dev: &str) { + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!("path={loop_dev}").as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // MIN-IO column + assert_eq!( + guest + .ssh_command("lsblk -t| grep vdc | awk '{print $3}'") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4096 + ); + // PHY-SEC column + assert_eq!( + guest + .ssh_command("lsblk -t| grep vdc | awk '{print $5}'") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4096 + ); + // LOG-SEC column + assert_eq!( + guest + .ssh_command("lsblk -t| grep vdc | awk '{print $6}'") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 4096 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 4bc3a36bbb..d67570ee22 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -3251,10 +3251,7 @@ mod common_parallel { #[test] fn test_virtio_block_topology() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - - let kernel_path = direct_kernel_boot_path(); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); let test_disk_path = guest.tmp_dir.as_path().join("test.img"); let output = exec_host_command_output( @@ -3271,71 +3268,7 @@ mod common_parallel { } let loop_dev = create_loop_device(test_disk_path.to_str().unwrap(), 4096, 5); - - let mut child = GuestCommand::new(&guest) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args([ - "--disk", - format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - format!( - "path={}", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ) - .as_str(), - format!("path={}", &loop_dev).as_str(), - ]) - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // MIN-IO column - assert_eq!( - guest - .ssh_command("lsblk -t| grep vdc | awk '{print $3}'") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 4096 - ); - // PHY-SEC column - assert_eq!( - guest - .ssh_command("lsblk -t| grep vdc | awk '{print $5}'") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 4096 - ); - // LOG-SEC column - assert_eq!( - guest - .ssh_command("lsblk -t| grep vdc | awk '{print $6}'") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 4096 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - + _test_virtio_block_topology(&guest, &loop_dev); Command::new("losetup") .args(["-d", &loop_dev]) .output() From 7284c02d31cb4cd3d8b8898e112da72b059cbcb4 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:37:48 -0700 Subject: [PATCH 0891/1893] 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 --- .../tests/common/tests_wrappers.rs | 172 ++++++++++++++++ cloud-hypervisor/tests/integration.rs | 186 ++---------------- 2 files changed, 183 insertions(+), 175 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 4c39043aa3..6f002fee71 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2868,3 +2868,175 @@ pub(crate) fn _test_virtio_block_topology(guest: &Guest, loop_dev: &str) { handle_child_output(r, &output); } + +pub(crate) fn _test_net_hotplug( + guest: &Guest, + max_num_pci_segments: u16, + pci_segment: Option, +) { + let api_socket = temp_api_path(&guest.tmp_dir); + + // Boot without network + let mut cmd = GuestCommand::new(guest); + + cmd.args(["--api-socket", &api_socket]) + .default_cpus() + .default_memory() + .default_net() + .default_disks() + .capture_output(); + + if pci_segment.is_some() { + cmd.default_kernel_cmdline_with_platform(Some(&format!( + "num_pci_segments={max_num_pci_segments}" + ))); + } else { + cmd.default_kernel_cmdline(); + } + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + let r = std::panic::catch_unwind(|| { + // Add network + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test0,tap=,mac={},ip={},mask=255.255.255.128{}", + guest.network.guest_mac1, + guest.network.host_ip1, + if let Some(pci_segment) = pci_segment { + format!(",pci_segment={pci_segment}") + } else { + String::new() + } + ) + .as_str(), + ), + ); + assert!(cmd_success); + + if let Some(pci_segment) = pci_segment { + assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( + "{{\"id\":\"test0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" + ))); + } else { + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); + } + + thread::sleep(std::time::Duration::new(5, 0)); + + // 2 network interfaces + default localhost ==> 3 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + // Test the same using the added network interface's IP + assert_eq!( + ssh_command_ip( + "ip -o link | wc -l", + &guest.network.guest_ip1, + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT + ) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + // Remove network + assert!(remote_command(&api_socket, "remove-device", Some("test0"),)); + thread::sleep(std::time::Duration::new(5, 0)); + + // Add network + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test1,tap=,mac={},ip={},mask=255.255.255.128{}", + guest.network.guest_mac1, + guest.network.host_ip1, + if let Some(pci_segment) = pci_segment { + format!(",pci_segment={pci_segment}") + } else { + String::new() + } + ) + .as_str(), + ), + ); + assert!(cmd_success); + + if let Some(pci_segment) = pci_segment { + assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( + "{{\"id\":\"test1\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" + ))); + } else { + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test1\",\"bdf\":\"0000:00:06.0\"}") + ); + } + + thread::sleep(std::time::Duration::new(5, 0)); + + // 2 network interfaces + default localhost ==> 3 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + guest.reboot_linux(0); + + // 2 network interfaces + default localhost ==> 3 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + // Test the same using the added network interface's IP + assert_eq!( + ssh_command_ip( + "ip -o link | wc -l", + &guest.network.guest_ip1, + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT + ) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index d67570ee22..770313eeee 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -4936,189 +4936,25 @@ mod common_parallel { #[test] fn test_net_hotplug() { - _test_net_hotplug(None); + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = edk2_path(); + let guest = + basic_regular_guest!(JAMMY_IMAGE_NAME).with_kernel_path(kernel_path.to_str().unwrap()); + + _test_net_hotplug(&guest, MAX_NUM_PCI_SEGMENTS, None); } #[test] fn test_net_multi_segment_hotplug() { - _test_net_hotplug(Some(15)); - } - - fn _test_net_hotplug(pci_segment: Option) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); #[cfg(target_arch = "aarch64")] let kernel_path = edk2_path(); - - let api_socket = temp_api_path(&guest.tmp_dir); - - // Boot without network - let mut cmd = GuestCommand::new(&guest); - - cmd.args(["--api-socket", &api_socket]) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_net() - .default_disks() - .capture_output(); - - if pci_segment.is_some() { - cmd.args([ - "--platform", - &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS}"), - ]); - } - - let mut child = cmd.spawn().unwrap(); - - guest.wait_vm_boot().unwrap(); - - let r = std::panic::catch_unwind(|| { - // Add network - let (cmd_success, cmd_output) = remote_command_w_output( - &api_socket, - "add-net", - Some( - format!( - "id=test0,tap=,mac={},ip={},mask=255.255.255.128{}", - guest.network.guest_mac1, - guest.network.host_ip1, - if let Some(pci_segment) = pci_segment { - format!(",pci_segment={pci_segment}") - } else { - String::new() - } - ) - .as_str(), - ), - ); - assert!(cmd_success); - - if let Some(pci_segment) = pci_segment { - assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( - "{{\"id\":\"test0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" - ))); - } else { - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") - ); - } - - thread::sleep(std::time::Duration::new(5, 0)); - - // 2 network interfaces + default localhost ==> 3 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - // Test the same using the added network interface's IP - assert_eq!( - ssh_command_ip( - "ip -o link | wc -l", - &guest.network.guest_ip1, - DEFAULT_SSH_RETRIES, - DEFAULT_SSH_TIMEOUT - ) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - // Remove network - assert!(remote_command(&api_socket, "remove-device", Some("test0"),)); - thread::sleep(std::time::Duration::new(5, 0)); - - // Add network - let (cmd_success, cmd_output) = remote_command_w_output( - &api_socket, - "add-net", - Some( - format!( - "id=test1,tap=,mac={},ip={},mask=255.255.255.128{}", - guest.network.guest_mac1, - guest.network.host_ip1, - if let Some(pci_segment) = pci_segment { - format!(",pci_segment={pci_segment}") - } else { - String::new() - } - ) - .as_str(), - ), - ); - assert!(cmd_success); - - if let Some(pci_segment) = pci_segment { - assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( - "{{\"id\":\"test1\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" - ))); - } else { - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test1\",\"bdf\":\"0000:00:06.0\"}") - ); - } - - thread::sleep(std::time::Duration::new(5, 0)); - - // 2 network interfaces + default localhost ==> 3 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - guest.reboot_linux(0); - - // 2 network interfaces + default localhost ==> 3 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - // Test the same using the added network interface's IP - assert_eq!( - ssh_command_ip( - "ip -o link | wc -l", - &guest.network.guest_ip1, - DEFAULT_SSH_RETRIES, - DEFAULT_SSH_TIMEOUT - ) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = + basic_regular_guest!(JAMMY_IMAGE_NAME).with_kernel_path(kernel_path.to_str().unwrap()); + _test_net_hotplug(&guest, MAX_NUM_PCI_SEGMENTS, Some(15)); } #[test] From 4b77ac26d14c1ee432710415522be46b504681df Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:39:05 -0700 Subject: [PATCH 0892/1893] 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 --- .../tests/common/tests_wrappers.rs | 34 ++++++++++++++++++ cloud-hypervisor/tests/integration.rs | 36 ++----------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 6f002fee71..81afdadc60 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -3040,3 +3040,37 @@ pub(crate) fn _test_net_hotplug( handle_child_output(r, &output); } + +pub(crate) fn _test_counters(guest: &Guest) { + let api_socket = temp_api_path(&guest.tmp_dir); + + let mut cmd = GuestCommand::new(guest); + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .args(["--net", guest.default_net_string().as_str()]) + .args(["--api-socket", &api_socket]) + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + let orig_counters = get_counters(&api_socket); + guest + .ssh_command("dd if=/dev/zero of=test count=8 bs=1M") + .unwrap(); + + let new_counters = get_counters(&api_socket); + + // Check that all the counters have increased + assert!(new_counters > orig_counters); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 770313eeee..47b8980298 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5008,40 +5008,8 @@ mod common_parallel { #[test] fn test_counters() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - - let mut cmd = GuestCommand::new(&guest); - cmd.default_cpus() - .default_memory() - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", guest.default_net_string().as_str()]) - .args(["--api-socket", &api_socket]) - .capture_output(); - - let mut child = cmd.spawn().unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - let orig_counters = get_counters(&api_socket); - guest - .ssh_command("dd if=/dev/zero of=test count=8 bs=1M") - .unwrap(); - - let new_counters = get_counters(&api_socket); - - // Check that all the counters have increased - assert!(new_counters > orig_counters); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_counters(&guest); } #[test] From 93a3fc5b913a1458463c989a15ab5d7faaaba56c Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:40:43 -0700 Subject: [PATCH 0893/1893] 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 --- .../tests/common/tests_wrappers.rs | 77 ++++++++++++++++++ cloud-hypervisor/tests/integration.rs | 81 +------------------ 2 files changed, 79 insertions(+), 79 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 81afdadc60..65d3be97ac 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -3074,3 +3074,80 @@ pub(crate) fn _test_counters(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_watchdog(guest: &Guest) { + let api_socket = temp_api_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut cmd = GuestCommand::new(guest); + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .args(["--net", guest.default_net_string().as_str()]) + .args(["--watchdog"]) + .args(["--api-socket", &api_socket]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + let mut expected_reboot_count = 1; + + // Enable the watchdog with a 15s timeout + enable_guest_watchdog(guest, 15); + + assert_eq!(get_reboot_count(guest), expected_reboot_count); + assert_eq!( + guest + .ssh_command("sudo journalctl | grep -c -- \"Watchdog started\"") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Allow some normal time to elapse to check we don't get spurious reboots + thread::sleep(std::time::Duration::new(40, 0)); + // Check no reboot + assert_eq!(get_reboot_count(guest), expected_reboot_count); + + // Trigger a panic (sync first). We need to do this inside a screen with a delay so the SSH command returns. + guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); + // Allow some time for the watchdog to trigger (max 30s) and reboot to happen + guest.wait_vm_boot_custom_timeout(50).unwrap(); + // Check a reboot is triggered by the watchdog + expected_reboot_count += 1; + assert_eq!(get_reboot_count(guest), expected_reboot_count); + + #[cfg(target_arch = "x86_64")] + { + // Now pause the VM and remain offline for 30s + assert!(remote_command(&api_socket, "pause", None)); + let latest_events = [ + &MetaEvent { + event: "pausing".to_string(), + device_id: None, + }, + &MetaEvent { + event: "paused".to_string(), + device_id: None, + }, + ]; + assert!(check_latest_events_exact(&latest_events, &event_path)); + assert!(remote_command(&api_socket, "resume", None)); + + // Check no reboot + assert_eq!(get_reboot_count(guest), expected_reboot_count); + } + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 47b8980298..b91ba34966 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5099,85 +5099,8 @@ mod common_parallel { #[test] fn test_watchdog() { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - - let kernel_path = direct_kernel_boot_path(); - let event_path = temp_event_monitor_path(&guest.tmp_dir); - - let mut cmd = GuestCommand::new(&guest); - cmd.default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", guest.default_net_string().as_str()]) - .args(["--watchdog"]) - .args(["--api-socket", &api_socket]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .capture_output(); - - let mut child = cmd.spawn().unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - let mut expected_reboot_count = 1; - - // Enable the watchdog with a 15s timeout - enable_guest_watchdog(&guest, 15); - - assert_eq!(get_reboot_count(&guest), expected_reboot_count); - assert_eq!( - guest - .ssh_command("sudo journalctl | grep -c -- \"Watchdog started\"") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Allow some normal time to elapse to check we don't get spurious reboots - thread::sleep(std::time::Duration::new(40, 0)); - // Check no reboot - assert_eq!(get_reboot_count(&guest), expected_reboot_count); - - // Trigger a panic (sync first). We need to do this inside a screen with a delay so the SSH command returns. - guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); - // Allow some time for the watchdog to trigger (max 30s) and reboot to happen - guest.wait_vm_boot_custom_timeout(50).unwrap(); - // Check a reboot is triggered by the watchdog - expected_reboot_count += 1; - assert_eq!(get_reboot_count(&guest), expected_reboot_count); - - #[cfg(target_arch = "x86_64")] - { - // Now pause the VM and remain offline for 30s - assert!(remote_command(&api_socket, "pause", None)); - let latest_events = [ - &MetaEvent { - event: "pausing".to_string(), - device_id: None, - }, - &MetaEvent { - event: "paused".to_string(), - device_id: None, - }, - ]; - assert!(check_latest_events_exact(&latest_events, &event_path)); - assert!(remote_command(&api_socket, "resume", None)); - - // Check no reboot - assert_eq!(get_reboot_count(&guest), expected_reboot_count); - } - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(FOCAL_IMAGE_NAME); + _test_watchdog(&guest); } #[test] From 17774ee5644464007113a3bf05ec72b003afb075 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:41:52 -0700 Subject: [PATCH 0894/1893] 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 --- .../tests/common/tests_wrappers.rs | 42 +++++++++++++++++ cloud-hypervisor/tests/integration.rs | 46 +------------------ 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 65d3be97ac..bfc954b256 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -3151,3 +3151,45 @@ pub(crate) fn _test_watchdog(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_pvpanic(guest: &Guest) { + let api_socket = temp_api_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut cmd = GuestCommand::new(guest); + cmd.default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .args(["--net", guest.default_net_string().as_str()]) + .args(["--pvpanic"]) + .args(["--api-socket", &api_socket]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Trigger guest a panic + make_guest_panic(guest); + + // Wait a while for guest + thread::sleep(std::time::Duration::new(10, 0)); + + let expected_sequential_events = [&MetaEvent { + event: "panic".to_string(), + device_id: None, + }]; + assert!(check_latest_events_exact( + &expected_sequential_events, + &event_path + )); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index b91ba34966..e31c08a408 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5105,50 +5105,8 @@ mod common_parallel { #[test] fn test_pvpanic() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - let event_path = temp_event_monitor_path(&guest.tmp_dir); - - let kernel_path = direct_kernel_boot_path(); - - let mut cmd = GuestCommand::new(&guest); - cmd.default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", guest.default_net_string().as_str()]) - .args(["--pvpanic"]) - .args(["--api-socket", &api_socket]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .capture_output(); - - let mut child = cmd.spawn().unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Trigger guest a panic - make_guest_panic(&guest); - - // Wait a while for guest - thread::sleep(std::time::Duration::new(10, 0)); - - let expected_sequential_events = [&MetaEvent { - event: "panic".to_string(), - device_id: None, - }]; - assert!(check_latest_events_exact( - &expected_sequential_events, - &event_path - )); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME); + _test_pvpanic(&guest); } #[test] From a3423a4483762dd9d7743fa19fae67e80cb71540 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:43:39 -0700 Subject: [PATCH 0895/1893] 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 --- .../tests/common/tests_wrappers.rs | 70 ++++++++++++++++++ cloud-hypervisor/tests/integration.rs | 73 +------------------ 2 files changed, 72 insertions(+), 71 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index bfc954b256..0b8ffd7fe6 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -4,6 +4,7 @@ use std::ffi::CStr; use std::fs::{self, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; +use std::os::unix::io::AsRawFd; use std::path::{Path, PathBuf}; use std::string::String; use std::sync::mpsc; @@ -3193,3 +3194,72 @@ pub(crate) fn _test_pvpanic(guest: &Guest) { handle_child_output(r, &output); } + +pub(crate) fn _test_tap_from_fd(guest: &Guest) { + // Create a TAP interface with multi-queue enabled + let num_queue_pairs: usize = 2; + + use std::str::FromStr; + let taps = net_util::open_tap( + Some("chtap0"), + Some(std::net::IpAddr::V4( + std::net::Ipv4Addr::from_str(&guest.network.host_ip0).unwrap(), + )), + None, + &mut None, + None, + num_queue_pairs, + Some(libc::O_RDWR | libc::O_NONBLOCK), + ) + .unwrap(); + + let mut child = GuestCommand::new(guest) + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .args([ + "--net", + &format!( + "fd=[{},{}],mac={},num_queues={}", + taps[0].as_raw_fd(), + taps[1].as_raw_fd(), + guest.network.guest_mac0, + num_queue_pairs * 2 + ), + ]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 2 + ); + + guest.reboot_linux(0); + + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 2 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index e31c08a408..744986fee9 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5111,77 +5111,8 @@ mod common_parallel { #[test] fn test_tap_from_fd() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - - // Create a TAP interface with multi-queue enabled - let num_queue_pairs: usize = 2; - - use std::str::FromStr; - let taps = net_util::open_tap( - Some("chtap0"), - Some(std::net::IpAddr::V4( - std::net::Ipv4Addr::from_str(&guest.network.host_ip0).unwrap(), - )), - None, - &mut None, - None, - num_queue_pairs, - Some(libc::O_RDWR | libc::O_NONBLOCK), - ) - .unwrap(); - - let mut child = GuestCommand::new(&guest) - .args(["--cpus", &format!("boot={num_queue_pairs}")]) - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args([ - "--net", - &format!( - "fd=[{},{}],mac={},num_queues={}", - taps[0].as_raw_fd(), - taps[1].as_raw_fd(), - guest.network.guest_mac0, - num_queue_pairs * 2 - ), - ]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 2 - ); - - guest.reboot_linux(0); - - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 2 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(2); + _test_tap_from_fd(&guest); } // By design, a guest VM won't be able to connect to the host From 205bb5ff5e040bab83fc0b9c0e2c21c9599a589f Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:46:58 -0700 Subject: [PATCH 0896/1893] 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 --- .../tests/common/tests_wrappers.rs | 145 +++++++++++++++- cloud-hypervisor/tests/integration.rs | 158 +----------------- 2 files changed, 148 insertions(+), 155 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 0b8ffd7fe6..64db4e7de8 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -1,7 +1,7 @@ // Copyright 2025 The Cloud Hypervisor Authors. All rights reserved. // // SPDX-License-Identifier: Apache-2.0 -use std::ffi::CStr; +use std::ffi::{CStr, CString}; use std::fs::{self, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; use std::os::unix::io::AsRawFd; @@ -3263,3 +3263,146 @@ pub(crate) fn _test_tap_from_fd(guest: &Guest) { handle_child_output(r, &output); } + +// test creates two macvtap interfaces in 'bridge' mode on the +// same physical net interface, one for the guest and one for +// the host. With additional setup on the IP address and the +// routing table, it enables the communications between the +// guest VM and the host machine. +// Details: https://wiki.libvirt.org/page/TroubleshootMacvtapHostFail +pub(crate) fn _test_macvtap( + guest: &Guest, + hotplug: bool, + guest_macvtap_name: &str, + host_macvtap_name: &str, +) { + let api_socket = temp_api_path(&guest.tmp_dir); + + let phy_net = "eth0"; + + // Create a macvtap interface for the guest VM to use + assert!( + exec_host_command_status(&format!( + "sudo ip link add link {phy_net} name {guest_macvtap_name} type macvtap mod bridge" + )) + .success() + ); + assert!( + exec_host_command_status(&format!( + "sudo ip link set {} address {} up", + guest_macvtap_name, guest.network.guest_mac0 + )) + .success() + ); + assert!(exec_host_command_status(&format!("sudo ip link show {guest_macvtap_name}")).success()); + + let tap_index = + fs::read_to_string(format!("/sys/class/net/{guest_macvtap_name}/ifindex")).unwrap(); + let tap_device = format!("/dev/tap{}", tap_index.trim()); + + assert!(exec_host_command_status(&format!("sudo chown $UID.$UID {tap_device}")).success()); + + let cstr_tap_device = CString::new(tap_device).unwrap(); + let tap_fd1 = unsafe { libc::open(cstr_tap_device.as_ptr(), libc::O_RDWR) }; + assert!(tap_fd1 > 0); + let tap_fd2 = unsafe { libc::open(cstr_tap_device.as_ptr(), libc::O_RDWR) }; + assert!(tap_fd2 > 0); + + // Create a macvtap on the same physical net interface for + // the host machine to use + assert!( + exec_host_command_status(&format!( + "sudo ip link add link {phy_net} name {host_macvtap_name} type macvtap mod bridge" + )) + .success() + ); + // Use default mask "255.255.255.0" + assert!( + exec_host_command_status(&format!( + "sudo ip address add {}/24 dev {}", + guest.network.host_ip0, host_macvtap_name + )) + .success() + ); + assert!( + exec_host_command_status(&format!("sudo ip link set dev {host_macvtap_name} up")).success() + ); + + let mut guest_command = GuestCommand::new(guest); + guest_command + .default_cpus() + .default_memory() + .default_kernel_cmdline() + .default_disks() + .args(["--api-socket", &api_socket]); + + let net_params = format!( + "fd=[{},{}],mac={},num_queues=4", + tap_fd1, tap_fd2, guest.network.guest_mac0 + ); + + if !hotplug { + guest_command.args(["--net", &net_params]); + } + + let mut child = guest_command.capture_output().spawn().unwrap(); + + if hotplug { + // Give some time to the VMM process to listen to the API + // socket. This is the only requirement to avoid the following + // call to ch-remote from failing. + thread::sleep(std::time::Duration::new(10, 0)); + // Hotplug the virtio-net device + let (cmd_success, cmd_output) = + remote_command_w_output(&api_socket, "add-net", Some(&net_params)); + assert!(cmd_success); + #[cfg(target_arch = "x86_64")] + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"_net2\",\"bdf\":\"0000:00:05.0\"}") + ); + #[cfg(target_arch = "aarch64")] + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"_net0\",\"bdf\":\"0000:00:05.0\"}") + ); + } + + // The functional connectivity provided by the virtio-net device + // gets tested through wait_vm_boot() as it expects to receive a + // HTTP request, and through the SSH command as well. + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 2 + ); + + guest.reboot_linux(0); + + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 2 + ); + }); + + kill_child(&mut child); + + exec_host_command_status(&format!("sudo ip link del {guest_macvtap_name}")); + exec_host_command_status(&format!("sudo ip link del {host_macvtap_name}")); + + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 744986fee9..b4042400c8 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5115,168 +5115,18 @@ mod common_parallel { _test_tap_from_fd(&guest); } - // By design, a guest VM won't be able to connect to the host - // machine when using a macvtap network interface (while it can - // communicate externally). As a workaround, this integration - // test creates two macvtap interfaces in 'bridge' mode on the - // same physical net interface, one for the guest and one for - // the host. With additional setup on the IP address and the - // routing table, it enables the communications between the - // guest VM and the host machine. - // Details: https://wiki.libvirt.org/page/TroubleshootMacvtapHostFail - fn _test_macvtap(hotplug: bool, guest_macvtap_name: &str, host_macvtap_name: &str) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - - #[cfg(target_arch = "x86_64")] - let kernel_path = direct_kernel_boot_path(); - #[cfg(target_arch = "aarch64")] - let kernel_path = edk2_path(); - - let phy_net = "eth0"; - - // Create a macvtap interface for the guest VM to use - assert!( - exec_host_command_status(&format!( - "sudo ip link add link {phy_net} name {guest_macvtap_name} type macvtap mod bridge" - )) - .success() - ); - assert!( - exec_host_command_status(&format!( - "sudo ip link set {} address {} up", - guest_macvtap_name, guest.network.guest_mac0 - )) - .success() - ); - assert!( - exec_host_command_status(&format!("sudo ip link show {guest_macvtap_name}")).success() - ); - - let tap_index = - fs::read_to_string(format!("/sys/class/net/{guest_macvtap_name}/ifindex")).unwrap(); - let tap_device = format!("/dev/tap{}", tap_index.trim()); - - assert!(exec_host_command_status(&format!("sudo chown $UID.$UID {tap_device}")).success()); - - let cstr_tap_device = std::ffi::CString::new(tap_device).unwrap(); - let tap_fd1 = unsafe { libc::open(cstr_tap_device.as_ptr(), libc::O_RDWR) }; - assert!(tap_fd1 > 0); - let tap_fd2 = unsafe { libc::open(cstr_tap_device.as_ptr(), libc::O_RDWR) }; - assert!(tap_fd2 > 0); - - // Create a macvtap on the same physical net interface for - // the host machine to use - assert!( - exec_host_command_status(&format!( - "sudo ip link add link {phy_net} name {host_macvtap_name} type macvtap mod bridge" - )) - .success() - ); - // Use default mask "255.255.255.0" - assert!( - exec_host_command_status(&format!( - "sudo ip address add {}/24 dev {}", - guest.network.host_ip0, host_macvtap_name - )) - .success() - ); - assert!( - exec_host_command_status(&format!("sudo ip link set dev {host_macvtap_name} up")) - .success() - ); - - let mut guest_command = GuestCommand::new(&guest); - guest_command - .args(["--cpus", "boot=2"]) - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--api-socket", &api_socket]); - - let net_params = format!( - "fd=[{},{}],mac={},num_queues=4", - tap_fd1, tap_fd2, guest.network.guest_mac0 - ); - - if !hotplug { - guest_command.args(["--net", &net_params]); - } - - let mut child = guest_command.capture_output().spawn().unwrap(); - - if hotplug { - // Give some time to the VMM process to listen to the API - // socket. This is the only requirement to avoid the following - // call to ch-remote from failing. - thread::sleep(std::time::Duration::new(10, 0)); - // Hotplug the virtio-net device - let (cmd_success, cmd_output) = - remote_command_w_output(&api_socket, "add-net", Some(&net_params)); - assert!(cmd_success); - #[cfg(target_arch = "x86_64")] - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"_net2\",\"bdf\":\"0000:00:05.0\"}") - ); - #[cfg(target_arch = "aarch64")] - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"_net0\",\"bdf\":\"0000:00:05.0\"}") - ); - } - - // The functional connectivity provided by the virtio-net device - // gets tested through wait_vm_boot() as it expects to receive a - // HTTP request, and through the SSH command as well. - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 2 - ); - - guest.reboot_linux(0); - - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 2 - ); - }); - - kill_child(&mut child); - - exec_host_command_status(&format!("sudo ip link del {guest_macvtap_name}")); - exec_host_command_status(&format!("sudo ip link del {host_macvtap_name}")); - - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - } - #[test] #[cfg_attr(target_arch = "aarch64", ignore = "See #5443")] fn test_macvtap() { - _test_macvtap(false, "guestmacvtap0", "hostmacvtap0"); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(2); + _test_macvtap(&guest, false, "guestmacvtap0", "hostmacvtap0"); } #[test] #[cfg_attr(target_arch = "aarch64", ignore = "See #5443")] fn test_macvtap_hotplug() { - _test_macvtap(true, "guestmacvtap1", "hostmacvtap1"); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(2); + _test_macvtap(&guest, true, "guestmacvtap1", "hostmacvtap1"); } #[test] From e89b58469453deaa17697ca84c6c32ba68210ba7 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:49:43 -0700 Subject: [PATCH 0897/1893] 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 --- .../tests/common/tests_wrappers.rs | 112 +++++++++++++++++ cloud-hypervisor/tests/integration.rs | 117 +----------------- 2 files changed, 114 insertions(+), 115 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 64db4e7de8..159a7b0ca8 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -3406,3 +3406,115 @@ pub(crate) fn _test_macvtap( handle_child_output(r, &output); } + +pub(crate) fn _test_vdpa_block(guest: &Guest) { + let api_socket = temp_api_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(guest) + .default_cpus() + .args(["--memory", "size=512M,hugepages=on"]) + .default_kernel_cmdline_with_platform(Some("num_pci_segments=2,iommu_segments=1")) + .default_disks() + .default_net() + .args(["--vdpa", "path=/dev/vhost-vdpa-0,num_queues=1"]) + .args(["--api-socket", &api_socket]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Check both if /dev/vdc exists and if the block size is 128M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdc | grep -c 128M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Check the content of the block device after we wrote to it. + // The vpda-sim-blk should let us read what we previously wrote. + guest + .ssh_command("sudo bash -c 'echo foobar > /dev/vdc'") + .unwrap(); + assert_eq!( + guest.ssh_command("sudo head -1 /dev/vdc").unwrap().trim(), + "foobar" + ); + + // Hotplug an extra vDPA block device behind the vIOMMU + // Add a new vDPA device to the VM + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-vdpa", + Some("id=myvdpa0,path=/dev/vhost-vdpa-1,num_queues=1,pci_segment=1,iommu=on"), + ); + assert!(cmd_success); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"myvdpa0\",\"bdf\":\"0001:00:01.0\"}") + ); + + thread::sleep(std::time::Duration::new(10, 0)); + + // Check IOMMU setup + assert!( + guest + .does_device_vendor_pair_match("0x1057", "0x1af4") + .unwrap_or_default() + ); + assert!( + guest + .ssh_command("ls /sys/kernel/iommu_groups/*/devices") + .unwrap() + .contains("0001:00:01.0") + ); + + // Check both if /dev/vdd exists and if the block size is 128M. + assert_eq!( + guest + .ssh_command("lsblk | grep vdd | grep -c 128M") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + + // Write some content to the block device we've just plugged. + guest + .ssh_command("sudo bash -c 'echo foobar > /dev/vdd'") + .unwrap(); + + // Check we can read the content back. + assert_eq!( + guest.ssh_command("sudo head -1 /dev/vdd").unwrap().trim(), + "foobar" + ); + + // Unplug the device + let cmd_success = remote_command(&api_socket, "remove-device", Some("myvdpa0")); + assert!(cmd_success); + thread::sleep(std::time::Duration::new(10, 0)); + + // Check /dev/vdd doesn't exist anymore + assert_eq!( + guest + .ssh_command("lsblk | grep -c vdd || true") + .unwrap() + .trim() + .parse::() + .unwrap_or(1), + 0 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index b4042400c8..a9acca630d 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5408,121 +5408,8 @@ mod common_parallel { // Before trying to run the test, verify the vdpa_sim_blk module is correctly loaded. assert!(exec_host_command_status("lsmod | grep vdpa_sim_blk").success()); - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - - let kernel_path = direct_kernel_boot_path(); - - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=2"]) - .args(["--memory", "size=512M,hugepages=on"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .args(["--vdpa", "path=/dev/vhost-vdpa-0,num_queues=1"]) - .args(["--platform", "num_pci_segments=2,iommu_segments=1"]) - .args(["--api-socket", &api_socket]) - .capture_output() - .spawn() - .unwrap(); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Check both if /dev/vdc exists and if the block size is 128M. - assert_eq!( - guest - .ssh_command("lsblk | grep vdc | grep -c 128M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Check the content of the block device after we wrote to it. - // The vpda-sim-blk should let us read what we previously wrote. - guest - .ssh_command("sudo bash -c 'echo foobar > /dev/vdc'") - .unwrap(); - assert_eq!( - guest.ssh_command("sudo head -1 /dev/vdc").unwrap().trim(), - "foobar" - ); - - // Hotplug an extra vDPA block device behind the vIOMMU - // Add a new vDPA device to the VM - let (cmd_success, cmd_output) = remote_command_w_output( - &api_socket, - "add-vdpa", - Some("id=myvdpa0,path=/dev/vhost-vdpa-1,num_queues=1,pci_segment=1,iommu=on"), - ); - assert!(cmd_success); - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"myvdpa0\",\"bdf\":\"0001:00:01.0\"}") - ); - - thread::sleep(std::time::Duration::new(10, 0)); - - // Check IOMMU setup - assert!( - guest - .does_device_vendor_pair_match("0x1057", "0x1af4") - .unwrap_or_default() - ); - assert!( - guest - .ssh_command("ls /sys/kernel/iommu_groups/*/devices") - .unwrap() - .contains("0001:00:01.0") - ); - - // Check both if /dev/vdd exists and if the block size is 128M. - assert_eq!( - guest - .ssh_command("lsblk | grep vdd | grep -c 128M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - - // Write some content to the block device we've just plugged. - guest - .ssh_command("sudo bash -c 'echo foobar > /dev/vdd'") - .unwrap(); - - // Check we can read the content back. - assert_eq!( - guest.ssh_command("sudo head -1 /dev/vdd").unwrap().trim(), - "foobar" - ); - - // Unplug the device - let cmd_success = remote_command(&api_socket, "remove-device", Some("myvdpa0")); - assert!(cmd_success); - thread::sleep(std::time::Duration::new(10, 0)); - - // Check /dev/vdd doesn't exist anymore - assert_eq!( - guest - .ssh_command("lsblk | grep -c vdd || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + let guest = basic_regular_guest!(JAMMY_IMAGE_NAME).with_cpu(2); + _test_vdpa_block(&guest); } #[test] From 108391f720587176e6c79065184c80daaec03b01 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:51:42 -0700 Subject: [PATCH 0898/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index fd47e21f01..25c6730d3b 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -218,4 +218,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_dmi_oem_strings(&guest); } + + #[test] + fn test_multiple_network_interfaces() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_multiple_network_interfaces(&guest); + } } From 36871b3547fef9c42e78aa1ba56d8ec1225d4179 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:51:52 -0700 Subject: [PATCH 0899/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 25c6730d3b..3f464961d3 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -224,4 +224,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_multiple_network_interfaces(&guest); } + + #[test] + fn test_serial_off() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_serial_off(&guest); + } } From e9d8ecf38ae7fa9c442cdf5e678b295daeef0802 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:52:05 -0700 Subject: [PATCH 0900/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 3f464961d3..b8261b30a9 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -230,4 +230,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_serial_off(&guest); } + + #[test] + fn test_virtio_console() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_virtio_console(&guest); + } } From a4f1c65b14edeb54ce585a315c9af49f403810ad Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:52:15 -0700 Subject: [PATCH 0901/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index b8261b30a9..02ad0e730c 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -236,4 +236,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_virtio_console(&guest); } + + #[test] + fn test_console_file() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_console_file(&guest); + } } From f4df95c142b70fdee3e3d4fa6393a982efbfeebc Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:52:25 -0700 Subject: [PATCH 0902/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 02ad0e730c..26cb3b1921 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -242,4 +242,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_console_file(&guest); } + + #[test] + fn test_direct_kernel_boot_noacpi() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_direct_kernel_boot_noacpi(&guest); + } } From 0c477c12ecfebc3d653c3bea6940809ed881ed1e Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:52:36 -0700 Subject: [PATCH 0903/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 26cb3b1921..c28ecc810f 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -248,4 +248,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_direct_kernel_boot_noacpi(&guest); } + + #[test] + fn test_pci_bar_reprogramming() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_pci_bar_reprogramming(&guest); + } } From 833d1d39a5a6f87fdfe7ba9066d0b8c3674853ce Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:53:59 -0700 Subject: [PATCH 0904/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index c28ecc810f..41cd75192f 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -254,4 +254,12 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_pci_bar_reprogramming(&guest); } + + #[test] + fn test_memory_overhead() { + let guest_memory_size_kb: u32 = 512 * 1024; + let guest = + basic_cvm_guest!(JAMMY_IMAGE_NAME).with_memory(&format!("{guest_memory_size_kb}K")); + _test_memory_overhead(&guest, guest_memory_size_kb); + } } From 9564b4f278a737f088da5a3c8ff1cc461ac8be74 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:54:09 -0700 Subject: [PATCH 0905/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 41cd75192f..9b2647c5e7 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -262,4 +262,10 @@ mod common_cvm { basic_cvm_guest!(JAMMY_IMAGE_NAME).with_memory(&format!("{guest_memory_size_kb}K")); _test_memory_overhead(&guest, guest_memory_size_kb); } + + #[test] + fn test_landlock() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_landlock(&guest); + } } From 60abdf0ea15d3db357b7adf1a9661deb0e98334b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:54:18 -0700 Subject: [PATCH 0906/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 9b2647c5e7..d18ddc2c8b 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -268,4 +268,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_landlock(&guest); } + + #[test] + fn test_disk_hotplug() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_disk_hotplug(&guest, false); + } } From 9a88b9a42e45bde82decb8c3d6fbb07d1c725c6b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:54:27 -0700 Subject: [PATCH 0907/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index d18ddc2c8b..b99b262538 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -16,6 +16,7 @@ mod common_cvm { use common::tests_wrappers::*; use common::utils::*; use test_infra::*; + const NUM_PCI_SEGMENTS: u16 = 8; use super::*; macro_rules! basic_cvm_guest { @@ -101,9 +102,8 @@ mod common_cvm { // Use 8 segments to test the multiple segment support since it's more than the default 6 // supported by Linux // IGVM file used by Sev-Snp Guest now support up to 8 segments, so we can use 8 segments for testing. - let num_pci_segments: u16 = 8; let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); - _test_pci_multiple_segments(&guest, num_pci_segments, 5); + _test_pci_multiple_segments(&guest, NUM_PCI_SEGMENTS, 5); } #[test] @@ -274,4 +274,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_disk_hotplug(&guest, false); } + + #[test] + fn test_net_hotplug() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_net_hotplug(&guest, NUM_PCI_SEGMENTS, None); + } } From 81f26bd99514444007d374df031169b0af7d0102 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:54:37 -0700 Subject: [PATCH 0908/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index b99b262538..2b0275bfe1 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -280,4 +280,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_net_hotplug(&guest, NUM_PCI_SEGMENTS, None); } + + #[test] + fn test_counters() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_counters(&guest); + } } From 5ad9822a5005eb8d257560c234951c4f630602d7 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:54:46 -0700 Subject: [PATCH 0909/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 2b0275bfe1..fedc1c6080 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -286,4 +286,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_counters(&guest); } + + #[test] + fn test_watchdog() { + let guest = basic_cvm_guest!(FOCAL_IMAGE_NAME); + _test_watchdog(&guest); + } } From 7d672872d634eb9e5ab087a35af2425a53a51fba Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:54:55 -0700 Subject: [PATCH 0910/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index fedc1c6080..254542427c 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -292,4 +292,10 @@ mod common_cvm { let guest = basic_cvm_guest!(FOCAL_IMAGE_NAME); _test_watchdog(&guest); } + + #[test] + fn test_pvpanic() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); + _test_pvpanic(&guest); + } } From 6fa6ead2b3830fb2f1d1330e0ac1079bfc89fc0b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:55:05 -0700 Subject: [PATCH 0911/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 254542427c..0002f6084e 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -298,4 +298,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_pvpanic(&guest); } + + #[test] + fn test_tap_from_fd() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(2); + _test_tap_from_fd(&guest); + } } From b7bd19b24a34d1c728c49941a754d94120f519f7 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:55:49 -0700 Subject: [PATCH 0912/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 0002f6084e..8b8ab00f2b 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -304,4 +304,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(2); _test_tap_from_fd(&guest); } + + #[test] + fn test_macvtap() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(2); + _test_macvtap(&guest, false, "guestmacvtap0", "hostmacvtap0"); + } } From 7e62565cca6371d2aaf6c0a0be1c70b56f74d5ae Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:56:04 -0700 Subject: [PATCH 0913/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 8b8ab00f2b..9ee575ca7a 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -310,4 +310,10 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(2); _test_macvtap(&guest, false, "guestmacvtap0", "hostmacvtap0"); } + + #[test] + fn test_macvtap_hotplug() { + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(2); + _test_macvtap(&guest, true, "guestmacvtap1", "hostmacvtap1"); + } } From 80ce6960062a001f92afe19d5f69f290818c6654 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:56:19 -0700 Subject: [PATCH 0914/1893] 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 --- cloud-hypervisor/tests/integration_cvm.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 9ee575ca7a..039f8840ff 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -316,4 +316,12 @@ mod common_cvm { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(2); _test_macvtap(&guest, true, "guestmacvtap1", "hostmacvtap1"); } + + #[test] + fn test_vdpa_block() { + assert!(exec_host_command_status("lsmod | grep vdpa_sim_blk").success()); + + let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME).with_cpu(2); + _test_vdpa_block(&guest); + } } From 11d8ac48cbf5ef2e8417c9e58cd3406769d0c07f Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 25 Mar 2026 05:33:14 -0700 Subject: [PATCH 0915/1893] build: Use released vfio & vhost crates Replace the git dependencies with newly released versions Signed-off-by: Rob Bradford --- Cargo.lock | 25 +++++++++++++++---------- Cargo.toml | 10 +++++----- fuzz/Cargo.lock | 20 ++++++++++++-------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b009dfa901..ad3fe2b934 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2277,16 +2277,18 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vfio-bindings" -version = "0.6.1" -source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "188dac3057a0cbc94470085204c84b82ff7ec5dac629a514323cd133d1f9abe0" dependencies = [ "vmm-sys-util", ] [[package]] name = "vfio-ioctls" -version = "0.5.2" -source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80c6d52f8e592e31a8f7eb45e882a9617aa61ec2479981a175e9f0a79f2434e" dependencies = [ "byteorder", "kvm-bindings", @@ -2303,8 +2305,9 @@ dependencies = [ [[package]] name = "vfio_user" -version = "0.1.2" -source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731c2582dd43f4f174ab47b4c933a1a9bb872d9d1b7f54c5867e12dbc1491b75" dependencies = [ "bitflags 2.11.0", "libc", @@ -2320,8 +2323,9 @@ dependencies = [ [[package]] name = "vhost" -version = "0.15.0" -source = "git+https://github.com/rust-vmm/vhost?rev=c9b80a1c93bac7820e4aee4269aa904568937035#c9b80a1c93bac7820e4aee4269aa904568937035" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee90657203a8644e9a0860a0db6a7887d8ef0c7bc09fc22dfa4ae75df65bac86" dependencies = [ "bitflags 2.11.0", "libc", @@ -2332,8 +2336,9 @@ dependencies = [ [[package]] name = "vhost-user-backend" -version = "0.21.0" -source = "git+https://github.com/rust-vmm/vhost?rev=c9b80a1c93bac7820e4aee4269aa904568937035#c9b80a1c93bac7820e4aee4269aa904568937035" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5925983d8fb537752ad3e26604c0a17abfa5de77cb6773a096c8a959c9eca0f" dependencies = [ "libc", "log", diff --git a/Cargo.toml b/Cargo.toml index b8320172e1..cc2bd3c175 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,11 +59,11 @@ linux-loader = "0.13.2" mshv-bindings = "0.6.7" mshv-ioctls = "0.6.7" seccompiler = "0.5.0" -vfio-bindings = { git = "https://github.com/rust-vmm/vfio", rev = "df861a878168ad71602d8a1945bd3b7acbd22693", default-features = false } -vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", rev = "df861a878168ad71602d8a1945bd3b7acbd22693", default-features = false } -vfio_user = { git = "https://github.com/rust-vmm/vfio", rev = "df861a878168ad71602d8a1945bd3b7acbd22693", default-features = false } -vhost = { git = "https://github.com/rust-vmm/vhost", rev = "c9b80a1c93bac7820e4aee4269aa904568937035", default-features = false } -vhost-user-backend = { git = "https://github.com/rust-vmm/vhost", rev = "c9b80a1c93bac7820e4aee4269aa904568937035", default-features = false } +vfio-bindings = { version = "0.6.2", default-features = false } +vfio-ioctls = { version = "0.5.3", 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.17.0" vm-fdt = "0.3.0" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index d5cda1e24f..559fd35ce6 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1273,16 +1273,18 @@ dependencies = [ [[package]] name = "vfio-bindings" -version = "0.6.1" -source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "188dac3057a0cbc94470085204c84b82ff7ec5dac629a514323cd133d1f9abe0" dependencies = [ "vmm-sys-util", ] [[package]] name = "vfio-ioctls" -version = "0.5.2" -source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80c6d52f8e592e31a8f7eb45e882a9617aa61ec2479981a175e9f0a79f2434e" dependencies = [ "byteorder", "kvm-bindings", @@ -1297,8 +1299,9 @@ dependencies = [ [[package]] name = "vfio_user" -version = "0.1.2" -source = "git+https://github.com/rust-vmm/vfio?rev=df861a878168ad71602d8a1945bd3b7acbd22693#df861a878168ad71602d8a1945bd3b7acbd22693" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731c2582dd43f4f174ab47b4c933a1a9bb872d9d1b7f54c5867e12dbc1491b75" dependencies = [ "bitflags 2.11.0", "libc", @@ -1314,8 +1317,9 @@ dependencies = [ [[package]] name = "vhost" -version = "0.15.0" -source = "git+https://github.com/rust-vmm/vhost?rev=c9b80a1c93bac7820e4aee4269aa904568937035#c9b80a1c93bac7820e4aee4269aa904568937035" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee90657203a8644e9a0860a0db6a7887d8ef0c7bc09fc22dfa4ae75df65bac86" dependencies = [ "bitflags 2.11.0", "libc", From a8d962640f0dabde12dd7738384a13081c23b3a2 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 25 Mar 2026 06:28:19 -0700 Subject: [PATCH 0916/1893] 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 --- scripts/run_integration_tests_aarch64.sh | 2 +- scripts/run_integration_tests_x86_64.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 2489d7892a..b4c3482d0e 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -13,7 +13,7 @@ build_virtiofsd() { VIRTIOFSD_DIR="$WORKLOADS_DIR/virtiofsd_build" VIRTIOFSD_REPO="https://gitlab.com/virtio-fs/virtiofsd.git" - checkout_repo "$VIRTIOFSD_DIR" "$VIRTIOFSD_REPO" v1.13.3 "bbf82173682a3e48083771a0a23331e5c23b4924" + checkout_repo "$VIRTIOFSD_DIR" "$VIRTIOFSD_REPO" main "0f5865629dc995a3e9d5a73b4eb45bb91740bccb" if [ ! -f "$VIRTIOFSD_DIR/.built" ]; then pushd "$VIRTIOFSD_DIR" || exit diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 80ac279349..858bd2f872 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -124,7 +124,7 @@ if [ ! -f "$VIRTIOFSD" ]; then pushd "$WORKLOADS_DIR" || exit git clone "https://gitlab.com/virtio-fs/virtiofsd.git" $VIRTIOFSD_DIR pushd $VIRTIOFSD_DIR || exit - git checkout v1.13.3 + git checkout 0f5865629dc995a3e9d5a73b4eb45bb91740bccb time cargo build --release cp target/release/virtiofsd "$VIRTIOFSD" || exit 1 popd || exit From 0b90180266363e39019538b61312709380aed1d1 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 25 Mar 2026 14:49:22 +0100 Subject: [PATCH 0917/1893] 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 --- vmm/src/cpu.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 852850b9fa..e855204566 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -230,14 +230,22 @@ const PR_SCHED_CORE: libc::c_int = 62; const PR_SCHED_CORE_GET: libc::c_int = 0; const PR_SCHED_CORE_CREATE: libc::c_int = 1; const PR_SCHED_CORE_SHARE_FROM: libc::c_int = 3; -const PIDTYPE_PID: libc::c_int = 0; +const PR_SCHED_CORE_SCOPE_THREAD: libc::c_int = 0; /// Create a new unique core scheduling cookie for the current thread. /// Silently succeeds on kernels that don't support PR_SCHED_CORE. fn core_scheduling_create() -> Result<()> { // SAFETY: prctl with PR_SCHED_CORE_CREATE on the current thread (pid=0). // All arguments are valid constants. We check the return value. - let ret = unsafe { libc::prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, 0, PIDTYPE_PID, 0) }; + let ret = unsafe { + libc::prctl( + PR_SCHED_CORE, + PR_SCHED_CORE_CREATE, + 0, + PR_SCHED_CORE_SCOPE_THREAD, + 0, + ) + }; if ret == -1 { let err = io::Error::last_os_error(); // EINVAL: kernel < 5.14 where PR_SCHED_CORE is unknown. @@ -261,7 +269,15 @@ fn core_scheduling_create() -> Result<()> { fn core_scheduling_share_from(tid: i32) -> Result<()> { // SAFETY: prctl with PR_SCHED_CORE_SHARE_FROM targeting tid. // All arguments are valid. We check the return value. - let ret = unsafe { libc::prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, tid, PIDTYPE_PID, 0) }; + let ret = unsafe { + libc::prctl( + PR_SCHED_CORE, + PR_SCHED_CORE_SHARE_FROM, + tid, + PR_SCHED_CORE_SCOPE_THREAD, + 0, + ) + }; if ret == -1 { let err = io::Error::last_os_error(); match err.raw_os_error() { @@ -283,7 +299,7 @@ fn core_scheduling_cookie() -> u64 { PR_SCHED_CORE, PR_SCHED_CORE_GET, 0, - PIDTYPE_PID, + PR_SCHED_CORE_SCOPE_THREAD, &mut cookie as *mut u64, ) }; From 65073259c6a9567f8f40475da00e093aff61498c Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Wed, 25 Mar 2026 18:08:21 -0700 Subject: [PATCH 0918/1893] 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 --- vmm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index d1cf6693a3..03edec26b5 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2061,7 +2061,7 @@ impl RequestHandler for Vmm { let mut memory_actual_size = config.memory.total_size(); if let Some(vm) = &self.vm { - memory_actual_size -= vm.balloon_size(); + memory_actual_size = memory_actual_size.saturating_sub(vm.balloon_size()); } let device_tree = self From 57e766bdbbfcdf1f36f696fc735fbebbea97f5ca Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 06:44:23 -0700 Subject: [PATCH 0919/1893] 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 --- virtio-devices/src/transport/pci_device.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 3e2a96ccd9..1eb1cc03f7 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -1179,6 +1179,7 @@ impl PciDevice for VirtioPciDevice { } fn write_bar(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { + let initial_ready = self.is_driver_ready(); match offset { o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => self.common_config.write( o - COMMON_CONFIG_BAR_OFFSET, @@ -1230,8 +1231,8 @@ impl PciDevice for VirtioPciDevice { _ => (), } - // Try and activate the device if the driver status has changed - if self.needs_activation() { + // Try and activate the device if the driver status has changed (from unready to ready) + if !initial_ready && self.needs_activation() { let barrier = Arc::new(Barrier::new(2)); let activator = self.prepare_activator(Some(barrier.clone())); self.pending_activations.lock().unwrap().push(activator); From 5bb4ea20a57615439629cc83736e5cde14e10820 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 26 Mar 2026 18:49:56 -0700 Subject: [PATCH 0920/1893] 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 --- scripts/dev_cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 0e190a0072..0c0a2d5e4f 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -165,7 +165,7 @@ process_volumes_args() { return fi exported_volumes="" - arr_vols=("${arg_vols//#/ }") + IFS='#' read -ra arr_vols <<<"$arg_vols" for var in "${arr_vols[@]}"; do dev=$(echo "$var" | cut -d ':' -f 1) if [[ ! -e "$dev" ]]; then From 7360bfe33a848f7d13b3ccc7df0759715f8e2f87 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Wed, 21 May 2025 21:02:56 +0000 Subject: [PATCH 0921/1893] 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 --- pci/src/vfio.rs | 12 +++++------- vmm/src/device_manager.rs | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index ee4e43e31e..7f97e73dad 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -19,9 +19,7 @@ use log::{error, info}; use serde::{Deserialize, Serialize}; use thiserror::Error; use vfio_bindings::bindings::vfio::*; -use vfio_ioctls::{ - VfioContainer, VfioDevice, VfioIrq, VfioRegionInfoCap, VfioRegionSparseMmapArea, -}; +use vfio_ioctls::{VfioDevice, VfioIrq, VfioOps, VfioRegionInfoCap, VfioRegionSparseMmapArea}; use vm_allocator::page_size::{ align_page_size_down, align_page_size_up, is_4k_aligned, is_4k_multiple, is_page_size_aligned, }; @@ -1468,7 +1466,7 @@ pub struct VfioPciDevice { id: String, vm: Arc, device: Arc, - container: Arc, + container: Arc, common: VfioCommon, iommu_attached: bool, memory_slot_allocator: MemorySlotAllocator, @@ -1483,7 +1481,7 @@ impl VfioPciDevice { id: String, vm: Arc, device: VfioDevice, - container: Arc, + container: Arc, msi_interrupt_manager: Arc>, legacy_interrupt_group: Option>, iommu_attached: bool, @@ -2000,7 +1998,7 @@ impl Migratable for VfioPciDevice {} /// be used when the caller tries to provide a way to update the mappings /// associated with a specific VFIO container. pub struct VfioDmaMapping { - container: Arc, + container: Arc, memory: Arc, mmio_regions: Arc>>, } @@ -2012,7 +2010,7 @@ impl VfioDmaMapping { /// * `memory`: guest memory to mmap. /// * `mmio_regions`: mmio_regions to mmap. pub fn new( - container: Arc, + container: Arc, memory: Arc, mmio_regions: Arc>>, ) -> Self { diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 50b82772d0..8dc2399e34 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1032,7 +1032,7 @@ pub struct DeviceManager { // VFIO container // Only one container can be created, therefore it is stored as part of the // DeviceManager to be reused. - vfio_container: Option>, + vfio_container: Option>, // Paravirtualized IOMMU iommu_device: Option>>, @@ -3798,7 +3798,7 @@ impl DeviceManager { self.add_vfio_device(device_cfg) } - fn create_vfio_container(&self) -> DeviceManagerResult> { + fn create_vfio_container(&self) -> DeviceManagerResult> { let passthrough_device = self .passthrough_device .as_ref() From 1bc49758a0e10bc8a8515baba105c40bc30678b0 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 26 Mar 2026 22:38:01 +0000 Subject: [PATCH 0922/1893] 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 --- pci/src/vfio.rs | 44 ++++++++++----------- vmm/src/device_manager.rs | 81 ++++++++++++++++++++------------------- 2 files changed, 64 insertions(+), 61 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 7f97e73dad..dbcf59fbe8 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -1466,7 +1466,7 @@ pub struct VfioPciDevice { id: String, vm: Arc, device: Arc, - container: Arc, + vfio_ops: Arc, common: VfioCommon, iommu_attached: bool, memory_slot_allocator: MemorySlotAllocator, @@ -1481,7 +1481,7 @@ impl VfioPciDevice { id: String, vm: Arc, device: VfioDevice, - container: Arc, + vfio_ops: Arc, msi_interrupt_manager: Arc>, legacy_interrupt_group: Option>, iommu_attached: bool, @@ -1510,7 +1510,7 @@ impl VfioPciDevice { id, vm, device, - container, + vfio_ops, common, iommu_attached, memory_slot_allocator, @@ -1705,7 +1705,7 @@ impl VfioPciDevice { // user_memory_region.mapping.len() bytes of // valid memory that will only be unmapped with munmap(). unsafe { - self.container.vfio_dma_map( + self.vfio_ops.vfio_dma_map( user_memory_region.start, user_memory_region.mapping.len(), user_memory_region.mapping.addr(), @@ -1726,15 +1726,15 @@ impl VfioPciDevice { for user_memory_region in region.user_memory_regions.drain(..) { let len = user_memory_region.mapping.len(); let host_addr = user_memory_region.mapping.addr(); - // Unmap from vfio container + // Unmap MMIO region from the host IOMMU address space via VfioOps if !self.iommu_attached && let Err(e) = self - .container + .vfio_ops .vfio_dma_unmap(user_memory_region.start, len) .map_err(|e| VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf)) { error!( - "Could not unmap mmio region from vfio container: \ + "Could not unmap MMIO region from the host IOMMU address space: \ iova 0x{:x}, size 0x{:x}: {}, ", user_memory_region.start, len, e ); @@ -1884,17 +1884,17 @@ impl PciDevice for VfioPciDevice { for user_memory_region in region.user_memory_regions.iter_mut() { let len = user_memory_region.mapping.len(); let host_addr = user_memory_region.mapping.addr(); - // Unmap the old MMIO region from vfio container + // Unmap the old MMIO region from the host IOMMU address space via VfioOps if !self.iommu_attached && let Err(e) = self - .container + .vfio_ops .vfio_dma_unmap(user_memory_region.start, len) .map_err(|e| { VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf) }) { error!( - "Could not unmap mmio region from vfio container: \ + "Could not unmap MMIO region from the host IOMMU address space: \ iova 0x{:x}, size 0x{:x}: {}, ", user_memory_region.start, len, e ); @@ -1938,7 +1938,7 @@ iova 0x{:x}, size 0x{:x}: {}, ", } .map_err(io::Error::other)?; - // Map the moved mmio region to vfio container + // Map the moved MMIO region into the host IOMMU address space via VfioOps if !self.iommu_attached { // vfio_dma_map is unsound and ought to be marked as unsafe #[allow(unused_unsafe)] @@ -1946,13 +1946,13 @@ iova 0x{:x}, size 0x{:x}: {}, ", // host_addr points to len bytes of // valid memory that will only be unmapped with munmap(). unsafe { - self.container + self.vfio_ops .vfio_dma_map(user_memory_region.start, len, host_addr) } .map_err(|e| VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf)) .map_err(|e| { io::Error::other(format!( - "Could not map mmio region to vfio container: \ + "Could not map MMIO region into the host IOMMU address space: \ iova 0x{:x}, size 0x{:x}: {}, ", user_memory_region.start, len, e )) @@ -1996,9 +1996,9 @@ impl Migratable for VfioPciDevice {} /// This structure implements the ExternalDmaMapping trait. It is meant to /// be used when the caller tries to provide a way to update the mappings -/// associated with a specific VFIO container. +/// associated with a specific VfioOps instance. pub struct VfioDmaMapping { - container: Arc, + vfio_ops: Arc, memory: Arc, mmio_regions: Arc>>, } @@ -2006,16 +2006,16 @@ pub struct VfioDmaMapping { impl VfioDmaMapping { /// Create a DmaMapping object. /// # Parameters - /// * `container`: VFIO container object. + /// * `vfio_ops`: VfioOps instance. /// * `memory`: guest memory to mmap. /// * `mmio_regions`: mmio_regions to mmap. pub fn new( - container: Arc, + vfio_ops: Arc, memory: Arc, mmio_regions: Arc>>, ) -> Self { VfioDmaMapping { - container, + vfio_ops, memory, mmio_regions, } @@ -2062,20 +2062,20 @@ impl ExternalDmaMapping for VfioDmaMapping std::result::Result<(), io::Error> { - self.container + self.vfio_ops .vfio_dma_unmap(iova, size as usize) .map_err(|e| { io::Error::other(format!( - "failed to unmap memory for VFIO container, \ + "failed to unmap memory from the host IOMMU address space, \ iova 0x{iova:x}, size 0x{size:x}: {e:?}" )) }) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 8dc2399e34..95814cc92d 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1029,10 +1029,10 @@ pub struct DeviceManager { // Passthrough device handle passthrough_device: Option, - // VFIO container - // Only one container can be created, therefore it is stored as part of the + // VFIO operation instance + // Only one can be created, therefore it is stored as part of the // DeviceManager to be reused. - vfio_container: Option>, + vfio_ops: Option>, // Paravirtualized IOMMU iommu_device: Option>>, @@ -1350,7 +1350,7 @@ impl DeviceManager { msi_interrupt_manager, legacy_interrupt_manager: None, passthrough_device: None, - vfio_container: None, + vfio_ops: None, iommu_device: None, iommu_mapping: None, iommu_attached_devices: None, @@ -3798,7 +3798,7 @@ impl DeviceManager { self.add_vfio_device(device_cfg) } - fn create_vfio_container(&self) -> DeviceManagerResult> { + fn create_vfio_ops(&self) -> DeviceManagerResult> { let passthrough_device = self .passthrough_device .as_ref() @@ -3830,19 +3830,24 @@ impl DeviceManager { let mut needs_dma_mapping = false; - // Here we create a new VFIO container for two reasons. Either this is - // the first VFIO device, meaning we need a new VFIO container, which - // will be shared with other VFIO devices. Or the new VFIO device is - // attached to a vIOMMU, meaning we must create a dedicated VFIO - // container. In the vIOMMU use case, we can't let all devices under - // the same VFIO container since we couldn't map/unmap memory for each - // device. That's simply because the map/unmap operations happen at the - // VFIO container level. - let vfio_container = if device_cfg.iommu { - let vfio_container = self.create_vfio_container()?; + // Here we create a new VfioOps for two reasons: + // 1) This is the first VFIO device, meaning we need a new VfioOps + // which will be shared with other VFIO devices. + // 2) The new VFIO device is attached to a vIOMMU, meaning we must + // create a dedicated VfioOps. In the vIOMMU use case, we can't + // let all devices share the same VfioOps since we couldn't + // map/unmap memory for each device independently. That's simply + // because the map/unmap operations happen at the VfioOps level. + // + // Note: this is a limitation of the legacy VFIO interface using + // container/group. The VFIO cdev and iommufd do not have such a + // limitation, and this will be revised once we have VFIO cdev and + // iommufd support. + let vfio_ops = if device_cfg.iommu { + let vfio_ops = self.create_vfio_ops()?; let vfio_mapping = Arc::new(VfioDmaMapping::new( - Arc::clone(&vfio_container), + Arc::clone(&vfio_ops), Arc::new(self.memory_manager.lock().unwrap().guest_memory()), Arc::clone(&self.mmio_regions), )); @@ -3856,22 +3861,20 @@ impl DeviceManager { return Err(DeviceManagerError::MissingVirtualIommu); } - vfio_container - } else if let Some(vfio_container) = &self.vfio_container { - Arc::clone(vfio_container) + vfio_ops + } else if let Some(vfio_ops) = &self.vfio_ops { + Arc::clone(vfio_ops) } else { - let vfio_container = self.create_vfio_container()?; + let vfio_ops = self.create_vfio_ops()?; needs_dma_mapping = true; - self.vfio_container = Some(Arc::clone(&vfio_container)); + self.vfio_ops = Some(Arc::clone(&vfio_ops)); - vfio_container + vfio_ops }; - let vfio_device = VfioDevice::new( - &device_cfg.path, - Arc::clone(&vfio_container) as Arc, - ) - .map_err(DeviceManagerError::VfioCreate)?; + let vfio_device = + VfioDevice::new(&device_cfg.path, Arc::clone(&vfio_ops) as Arc) + .map_err(DeviceManagerError::VfioCreate)?; if needs_dma_mapping { // Register DMA mapping in IOMMU. @@ -3885,7 +3888,7 @@ impl DeviceManager { // to len bytes of valid memory starting at as_ptr() // that will only be freed with munmap(). unsafe { - vfio_container.vfio_dma_map( + vfio_ops.vfio_dma_map( region.start_addr().raw_value(), region.len() as usize, region.as_ptr(), @@ -3896,7 +3899,7 @@ impl DeviceManager { } let vfio_mapping = Arc::new(VfioDmaMapping::new( - Arc::clone(&vfio_container), + Arc::clone(&vfio_ops), Arc::new(self.memory_manager.lock().unwrap().guest_memory()), Arc::clone(&self.mmio_regions), )); @@ -3934,7 +3937,7 @@ impl DeviceManager { vfio_name.clone(), self.address_manager.vm.clone(), vfio_device, - vfio_container, + vfio_ops, self.msi_interrupt_manager.clone(), legacy_interrupt_group, device_cfg.iommu, @@ -4513,14 +4516,14 @@ impl DeviceManager { } // Take care of updating the memory for VFIO PCI devices. - if let Some(vfio_container) = &self.vfio_container { + if let Some(vfio_ops) = &self.vfio_ops { // vfio_dma_map is unsound and ought to be marked as unsafe #[allow(unused_unsafe)] // SAFETY: GuestMemoryMmap guarantees that region points // to len bytes of valid memory starting at as_ptr() // that will only be freed with munmap(). unsafe { - vfio_container.vfio_dma_map( + vfio_ops.vfio_dma_map( new_region.start_addr().raw_value(), new_region.len() as usize, new_region.as_ptr(), @@ -4764,7 +4767,7 @@ impl DeviceManager { let (pci_device, bus_device, virtio_device, remove_dma_handler) = match pci_device_handle { // VirtioMemMappingSource::Container cleanup is handled by - // cleanup_vfio_container when the last VFIO device is removed. + // cleanup_vfio_ops when the last VFIO device is removed. PciDeviceHandle::Vfio(vfio_pci_device) => { // Remove this device's MMIO regions from the DeviceManager's // mmio_regions list. We match on UserMemoryRegion slot numbers @@ -5154,11 +5157,11 @@ impl DeviceManager { &self.acpi_platform_addresses } - fn cleanup_vfio_container(&mut self) { - // Drop the 'vfio container' instance when "Self" is the only reference - if let Some(1) = self.vfio_container.as_ref().map(Arc::strong_count) { - debug!("Drop 'vfio container' given no active 'vfio devices'."); - self.vfio_container = None; + fn cleanup_vfio_ops(&mut self) { + // Drop the VfioOps instance when "Self" is the only reference + if let Some(1) = self.vfio_ops.as_ref().map(Arc::strong_count) { + debug!("Drop VfioOps given no active VFIO devices."); + self.vfio_ops = None; } } } @@ -5644,7 +5647,7 @@ impl BusDevice for DeviceManager { if let Err(e) = self.eject_device(self.selected_segment as u16, slot_id as u8) { error!("Failed ejecting device {slot_id}: {e:?}"); } - self.cleanup_vfio_container(); + self.cleanup_vfio_ops(); slot_bitmap &= !(1 << slot_id); } } From 9156758828dd3774abbad245ce7fb8e2e9dc08db Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Wed, 25 Mar 2026 21:44:33 -0700 Subject: [PATCH 0923/1893] 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 --- pci/src/vfio.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index dbcf59fbe8..f1d22ff63c 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -1562,13 +1562,33 @@ impl VfioPciDevice { let (offset, size) = msix.cap.table_range(); let offset = align_page_size_down(offset); let size = align_page_size_up(size); - inter_ranges.insert(offset, size); + // MSI-X mmap region safety: when a device has a non page + // aligned MSI-X offset, fixup_msix_region() relocates MSI-X + // to the upper half of an enlarged virtual BAR, causing the + // offsets in msix.cap to exceed the physical BAR size. This + // check skips carving a hole, preventing invalid offsets from + // reaching the mmap path. With no holes, + // generate_sparse_areas() returns a single sparse region + // covering the entire physical BAR. The relocated MSI-X in + // the virtual BAR remains trapped because its upper half has + // no mmap backing. Exposing the physical MSI-X region through + // mmap is safe when the kernel advertises + // VFIO_REGION_INFO_CAP_MSIX_MAPPABLE. When MSI-X offsets are + // already page aligned, fixup_msix_region() does not relocate + // and this check is satisfied, so a hole is carved at the + // intended offset as before. + if offset < region_size { + inter_ranges.insert(offset, size); + } } if region_index == msix.cap.pba_bir() { let (offset, size) = msix.cap.pba_range(); let offset = align_page_size_down(offset); let size = align_page_size_up(size); - inter_ranges.insert(offset, size); + // See MSI-X mmap safety comment above. + if offset < region_size { + inter_ranges.insert(offset, size); + } } } From c5dba9ea3b73d21d61e98d293dd41b2f4a2f68cc Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 22:44:43 +0100 Subject: [PATCH 0924/1893] 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 --- vmm/src/seccomp_filters.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index d295761518..97f020e650 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -110,9 +110,10 @@ mod kvm { pub const KVM_SET_NESTED_STATE: u64 = 1082175167; } -// Block device ioctls for sparse support probing (not exported by libc) +// Block device ioctls (not exported by libc) const BLKDISCARD: u64 = 0x1277; // _IO(0x12, 119) const BLKZEROOUT: u64 = 0x127f; // _IO(0x12, 127) +const BLKGETSIZE64: u64 = 0x80081272; // _IOR(0x12, 114, size_t) // MSHV IOCTL code. This is unstable until the kernel code has been declared stable. #[cfg(feature = "mshv")] @@ -265,6 +266,7 @@ fn create_vmm_ioctl_seccomp_rule_common( and![Cond::new(1, ArgLen::Dword, Eq, BLKPBSZGET as _)?], and![Cond::new(1, ArgLen::Dword, Eq, BLKIOMIN as _)?], and![Cond::new(1, ArgLen::Dword, Eq, BLKIOOPT as _)?], + and![Cond::new(1, ArgLen::Dword, Eq, BLKGETSIZE64 as _)?], and![Cond::new(1, ArgLen::Dword, Eq, BLKDISCARD as _)?], and![Cond::new(1, ArgLen::Dword, Eq, BLKZEROOUT as _)?], and![Cond::new(1, ArgLen::Dword, Eq, FIOCLEX as _)?], From 051a6eff5c563e5f4787b37b848212d914226dea Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 22:45:44 +0100 Subject: [PATCH 0925/1893] 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 --- block/src/lib.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 0640611c78..2c07276e23 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -38,6 +38,7 @@ use std::fmt::{self, Debug}; use std::fs::{File, OpenOptions}; use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; use std::os::linux::fs::MetadataExt; +use std::os::unix::fs::FileTypeExt; use std::os::unix::io::AsRawFd; use std::path::Path; use std::str::FromStr; @@ -61,7 +62,7 @@ use vm_memory::{ }; use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; -use vmm_sys_util::{aio, ioctl_io_nr}; +use vmm_sys_util::{aio, ioctl_io_nr, ioctl_ior_nr}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; @@ -1193,6 +1194,36 @@ ioctl_io_nr!(BLKSSZGET, 0x12, 104); ioctl_io_nr!(BLKPBSZGET, 0x12, 123); ioctl_io_nr!(BLKIOMIN, 0x12, 120); ioctl_io_nr!(BLKIOOPT, 0x12, 121); +ioctl_ior_nr!(BLKGETSIZE64, 0x12, 114, u64); + +/// Returns `(logical_size, physical_size)` in bytes for regular files and block devices. +/// +/// For regular files, logical size is `st_size` and physical size is +/// `st_blocks * 512` (actual host allocation). For block devices both +/// values equal the `BLKGETSIZE64` result. +pub fn query_device_size(file: &File) -> io::Result<(u64, u64)> { + let m = file.metadata()?; + if m.is_file() { + // st_blocks is always in 512-byte units on Linux + Ok((m.len(), m.st_blocks() * 512)) + } else if m.file_type().is_block_device() { + let mut size: u64 = 0; + // SAFETY: BLKGETSIZE64 reads the device size into a u64 pointer. + let ret = unsafe { libc::ioctl(file.as_raw_fd(), BLKGETSIZE64() as _, &mut size) }; + if ret != 0 { + return Err(io::Error::last_os_error()); + } + Ok((size, size)) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "disk image must be a regular file or block device, is: {:?}", + m.file_type() + ), + )) + } +} #[derive(Copy, Clone)] enum BlockSize { From f14e2d2b40c23e7d133b120ae5c535d1422120d5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 23:20:57 +0100 Subject: [PATCH 0926/1893] 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 --- block/src/raw_async.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 8544040f50..4efff45e9c 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause use std::fs::File; -use std::io::{Error, Seek, SeekFrom}; +use std::io::Error; use std::os::unix::io::{AsRawFd, RawFd}; use io_uring::{IoUring, opcode, types}; @@ -14,7 +14,9 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::{BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, probe_sparse_support}; +use crate::{ + BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, probe_sparse_support, query_device_size, +}; pub struct RawFileDisk { file: File, @@ -28,16 +30,15 @@ impl RawFileDisk { impl DiskFile for RawFileDisk { fn logical_size(&mut self) -> DiskFileResult { - self.file - .seek(SeekFrom::End(0)) - .map_err(DiskFileError::Size) + Ok(query_device_size(&self.file) + .map_err(DiskFileError::Size)? + .0) } fn physical_size(&mut self) -> DiskFileResult { - self.file - .metadata() - .map(|m| m.len()) - .map_err(DiskFileError::Size) + Ok(query_device_size(&self.file) + .map_err(DiskFileError::Size)? + .1) } fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { From 290f57a6e3aaf470a75a95ebd4f67947b7b406bd Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 23:21:18 +0100 Subject: [PATCH 0927/1893] 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 --- block/src/raw_sync.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index b9f89dde05..c045c5942d 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -4,7 +4,6 @@ use std::collections::VecDeque; use std::fs::File; -use std::io::{Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; @@ -14,7 +13,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support}; +use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support, query_device_size}; pub struct RawFileDiskSync { file: File, @@ -28,16 +27,15 @@ impl RawFileDiskSync { impl DiskFile for RawFileDiskSync { fn logical_size(&mut self) -> DiskFileResult { - self.file - .seek(SeekFrom::End(0)) - .map_err(DiskFileError::Size) + Ok(query_device_size(&self.file) + .map_err(DiskFileError::Size)? + .0) } fn physical_size(&mut self) -> DiskFileResult { - self.file - .metadata() - .map(|m| m.len()) - .map_err(DiskFileError::Size) + Ok(query_device_size(&self.file) + .map_err(DiskFileError::Size)? + .1) } fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { From 1b92af5534e7b8d2207fbb33e536dc5b35a17d32 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 23:21:36 +0100 Subject: [PATCH 0928/1893] 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 --- block/src/raw_async_aio.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index c2e6a174e0..66f7a667bf 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -7,7 +7,6 @@ use std::collections::VecDeque; use std::fs::File; -use std::io::{Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; @@ -18,7 +17,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support}; +use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support, query_device_size}; pub struct RawFileDiskAio { file: File, @@ -32,16 +31,15 @@ impl RawFileDiskAio { impl DiskFile for RawFileDiskAio { fn logical_size(&mut self) -> DiskFileResult { - self.file - .seek(SeekFrom::End(0)) - .map_err(DiskFileError::Size) + Ok(query_device_size(&self.file) + .map_err(DiskFileError::Size)? + .0) } fn physical_size(&mut self) -> DiskFileResult { - self.file - .metadata() - .map(|m| m.len()) - .map_err(DiskFileError::Size) + Ok(query_device_size(&self.file) + .map_err(DiskFileError::Size)? + .1) } fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { From 8b6eb83f2a833cf721edacdf5d286bbc63d9730a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 23:21:45 +0100 Subject: [PATCH 0929/1893] 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 --- block/src/qcow/raw_file.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index 06ec4975f4..56ec797355 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -20,7 +20,7 @@ use vmm_sys_util::file_traits::FileSync; use vmm_sys_util::seek_hole::SeekHole; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; -use crate::BlockBackend; +use crate::{BlockBackend, query_device_size}; #[derive(Debug)] pub struct RawFile { @@ -374,11 +374,15 @@ impl SeekHole for RawFile { impl BlockBackend for RawFile { fn logical_size(&self) -> std::result::Result { - Ok(self.metadata().map_err(crate::Error::RawFileError)?.len()) + Ok(query_device_size(&self.file) + .map_err(crate::Error::RawFileError)? + .0) } fn physical_size(&self) -> std::result::Result { - Ok(self.metadata().map_err(crate::Error::RawFileError)?.len()) + Ok(query_device_size(&self.file) + .map_err(crate::Error::RawFileError)? + .1) } } From a21b9588ecd2b9530892ba94b05a839e14ddf236 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 23:32:25 +0100 Subject: [PATCH 0930/1893] 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 --- virtio-devices/src/block.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 47309dd877..a38a378d1b 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -8,6 +8,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +use std::cmp::max; use std::collections::{BTreeMap, HashMap, VecDeque}; use std::num::Wrapping; use std::ops::Deref; @@ -887,10 +888,16 @@ impl Block { match self.lock_granularity_choice { LockGranularityChoice::Full => LockGranularity::WholeFile, LockGranularityChoice::ByteRange => { - // Byte-range lock covering [0, size) - self.disk_image.physical_size().map_or_else( - // use a safe fallback - |e| { + // Byte range lock covering [0, max(logical, physical)) + // logical > physical for sparse files, physical > logical + // for small dense files due to filesystem block rounding. + let logical = self.disk_image.logical_size(); + let physical = self.disk_image.physical_size(); + match (logical, physical) { + (Ok(l), Ok(p)) => LockGranularity::ByteRange(0, max(l, p)), + (Ok(l), Err(_)) => LockGranularity::ByteRange(0, l), + (Err(_), Ok(p)) => LockGranularity::ByteRange(0, p), + (Err(e), Err(_)) => { let fallback = LockGranularity::WholeFile; warn!( "Can't get disk size for id={},path={}, falling back to {:?}: error: {e}", @@ -899,9 +906,8 @@ impl Block { fallback ); fallback - }, - |size| LockGranularity::ByteRange(0, size), - ) + } + } } } } From c21d6bb85635b0799ec53e69de16bc11d2302fe5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 09:27:38 +0100 Subject: [PATCH 0931/1893] 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 --- block/src/lib.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/block/src/lib.rs b/block/src/lib.rs index 2c07276e23..d62e2717b2 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -1473,4 +1473,68 @@ mod unit_tests { // SAFETY: buf was allocated with this layout via alloc_zeroed. unsafe { dealloc(buf, layout) }; } + + #[test] + fn test_query_device_size_regular_file() { + let temp_file = TempFile::new().unwrap(); + let mut f = temp_file.into_file(); + // 5 sectors + 13 extra bytes - not page aligned, not sectoraligned + f.write_all(&[0xAB; 5 * 512 + 13]).unwrap(); + f.sync_all().unwrap(); + + let (logical, physical) = query_device_size(&f).unwrap(); + assert_eq!(logical, 5 * 512 + 13); + assert!(physical > 0); + } + + #[test] + fn test_query_device_size_sparse_file_punch_hole() { + let temp_file = TempFile::new().unwrap(); + let f = temp_file.as_file(); + // Allocate 1 MiB + let size: i64 = 1 << 20; + f.set_len(size as u64).unwrap(); + // SAFETY: fd is valid, range is within file size. + let ret = unsafe { + libc::fallocate( + f.as_raw_fd(), + 0, // allocate + 0, + size, + ) + }; + assert_eq!(ret, 0, "fallocate failed: {}", io::Error::last_os_error()); + f.sync_all().unwrap(); + + let (log_before, phys_before) = query_device_size(f).unwrap(); + assert_eq!(log_before, size as u64); + assert_eq!(phys_before, size as u64); + + // Punch a hole in the middle 512 KiB + // SAFETY: fd is valid, range is within file size. + let ret = unsafe { + libc::fallocate( + f.as_raw_fd(), + libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE, + size / 4, + size / 2, + ) + }; + assert_eq!(ret, 0, "punch hole failed: {}", io::Error::last_os_error()); + f.sync_all().unwrap(); + + let (logical, physical) = query_device_size(f).unwrap(); + assert_eq!(logical, size as u64, "logical size must not change"); + assert!( + physical < logical, + "physical ({physical}) should be less than logical ({logical}) after punch hole" + ); + } + + #[test] + fn test_query_device_size_rejects_char_device() { + let f = std::fs::File::open("/dev/zero").unwrap(); + let err = query_device_size(&f).unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::InvalidInput); + } } From e1e6d0a25b1a0feb6987af17d9adef6f7b667efb Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Fri, 27 Mar 2026 11:09:33 +0100 Subject: [PATCH 0932/1893] 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 --- vmm/src/vm_config.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index d453ead2d9..88f8af4acf 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -553,6 +553,10 @@ pub fn default_consoleconfig_file() -> Option { impl ApplyLandlock for ConsoleConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + if self.mode == ConsoleOutputMode::Pty { + landlock.add_rule_with_access(Path::new("/dev/pts"), "rw")?; + landlock.add_rule_with_access(Path::new("/dev/ptmx"), "rw")?; + } if let Some(file) = &self.file { landlock.add_rule_with_access(file, "rw")?; } @@ -586,6 +590,10 @@ impl Default for DebugConsoleConfig { #[cfg(target_arch = "x86_64")] impl ApplyLandlock for DebugConsoleConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + if self.mode == ConsoleOutputMode::Pty { + landlock.add_rule_with_access(Path::new("/dev/pts"), "rw")?; + landlock.add_rule_with_access(Path::new("/dev/ptmx"), "rw")?; + } if let Some(file) = &self.file { landlock.add_rule_with_access(file, "rw")?; } From a77f89847ec1e0f172a6e95b7a6d89aa6d4dc958 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 08:48:32 -0700 Subject: [PATCH 0933/1893] 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 --- virtio-devices/src/vhost_user/blk.rs | 13 +++--------- virtio-devices/src/vhost_user/fs.rs | 14 +++---------- .../src/vhost_user/generic_vhost_user.rs | 13 +++--------- virtio-devices/src/vhost_user/mod.rs | 20 +++++++++++++++++-- virtio-devices/src/vhost_user/net.rs | 13 +++--------- 5 files changed, 30 insertions(+), 43 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 9125e79909..66958e32c3 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -9,7 +9,6 @@ use block::VirtioBlockConfig; use event_monitor::event; use log::{error, info}; use seccompiler::SeccompAction; -use serde::{Deserialize, Serialize}; use vhost::vhost_user::message::{ VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures, }; @@ -29,19 +28,12 @@ use super::vu_common_ctrl::{VhostUserConfig, VhostUserHandle}; use super::{DEFAULT_VIRTIO_FEATURES, Error, Result}; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; -use crate::vhost_user::VhostUserCommon; +use crate::vhost_user::{VhostUserCommon, VhostUserState}; use crate::{GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_ACCESS_PLATFORM, VirtioInterrupt}; const DEFAULT_QUEUE_NUMBER: usize = 1; -#[derive(Serialize, Deserialize)] -pub struct State { - pub avail_features: u64, - pub acked_features: u64, - pub config: VirtioBlockConfig, - pub acked_protocol_features: u64, - pub vu_num_queues: usize, -} +pub type State = VhostUserState; struct BackendReqHandler {} impl VhostUserFrontendReqHandler for BackendReqHandler {} @@ -206,6 +198,7 @@ impl Blk { config: self.config, acked_protocol_features: self.vu_common.acked_protocol_features, vu_num_queues: self.vu_common.vu_num_queues, + ..Default::default() } } } diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index fb21105c8a..f9f819c0cc 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -22,7 +22,7 @@ use super::vu_common_ctrl::VhostUserHandle; use super::{DEFAULT_VIRTIO_FEATURES, Error, Result}; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; -use crate::vhost_user::VhostUserCommon; +use crate::vhost_user::{VhostUserCommon, VhostUserState}; use crate::{ ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioSharedMemoryList, @@ -31,15 +31,7 @@ use crate::{ const NUM_QUEUE_OFFSET: usize = 1; const DEFAULT_QUEUE_NUMBER: usize = 2; -#[derive(Serialize, Deserialize)] -pub struct State { - pub avail_features: u64, - pub acked_features: u64, - pub config: VirtioFsConfig, - pub acked_protocol_features: u64, - pub vu_num_queues: usize, - pub backend_req_support: bool, -} +pub type State = VhostUserState; struct BackendReqHandler {} impl VhostUserFrontendReqHandler for BackendReqHandler {} @@ -215,7 +207,7 @@ impl Fs { config: self.config, acked_protocol_features: self.vu_common.acked_protocol_features, vu_num_queues: self.vu_common.vu_num_queues, - backend_req_support: false, + ..Default::default() } } } diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index b90c6c079d..d0880b02e1 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -9,7 +9,6 @@ use std::{result, thread}; use event_monitor::event; use log::{error, info, warn}; use seccompiler::SeccompAction; -use serde::{Deserialize, Serialize}; use vhost::vhost_user::message::{ VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures, }; @@ -24,20 +23,13 @@ use super::vu_common_ctrl::VhostUserHandle; use super::{Error, Result}; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; -use crate::vhost_user::VhostUserCommon; +use crate::vhost_user::{VhostUserCommon, VhostUserState}; use crate::{ ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioSharedMemoryList, }; -#[derive(Serialize, Deserialize)] -pub struct State { - pub avail_features: u64, - pub acked_features: u64, - pub acked_protocol_features: u64, - pub vu_num_queues: usize, - pub backend_req_support: bool, -} +pub type State = VhostUserState<()>; struct BackendReqHandler {} impl VhostUserFrontendReqHandler for BackendReqHandler {} @@ -171,6 +163,7 @@ since the backend only supports {backend_num_queues}\n", acked_protocol_features: self.vu_common.acked_protocol_features, vu_num_queues: self.vu_common.vu_num_queues, backend_req_support: false, + ..Default::default() } } diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index aca9aba113..8632125391 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -295,6 +295,22 @@ impl EpollHelperHandler for VhostUserEpollHandle } } +/// Common snapshot state for all vhost-user device types. +/// +/// Generic over `C` which is the device-specific config type +/// (e.g. VirtioBlockConfig, VirtioFsConfig, VirtioNetConfig). +/// Devices without a config type use `()`. +#[derive(Default, Serialize, Deserialize)] +pub struct VhostUserState { + pub avail_features: u64, + pub acked_features: u64, + pub config: C, + pub acked_protocol_features: u64, + pub vu_num_queues: usize, + #[serde(default)] + pub backend_req_support: bool, +} + #[derive(Default)] pub struct VhostUserCommon { pub vu: Option>>, @@ -441,9 +457,9 @@ impl VhostUserCommon { Ok(()) } - pub fn snapshot<'a, T>(&mut self, state: &T) -> std::result::Result + pub fn snapshot(&mut self, state: &T) -> std::result::Result where - T: Serialize + Deserialize<'a>, + T: Serialize, { let snapshot = Snapshot::new_from_state(state)?; diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 165115ae7e..d6d142ba6d 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -9,7 +9,6 @@ use event_monitor::event; use log::{error, info}; use net_util::{CtrlQueue, MacAddr, VirtioNetConfig, build_net_config_space}; use seccompiler::SeccompAction; -use serde::{Deserialize, Serialize}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; use virtio_bindings::virtio_net::{ @@ -28,7 +27,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::vu_common_ctrl::{VhostUserConfig, VhostUserHandle}; -use crate::vhost_user::{DEFAULT_VIRTIO_FEATURES, Error, Result, VhostUserCommon}; +use crate::vhost_user::{DEFAULT_VIRTIO_FEATURES, Error, Result, VhostUserCommon, VhostUserState}; use crate::{ ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, @@ -36,14 +35,7 @@ use crate::{ const DEFAULT_QUEUE_NUMBER: usize = 2; -#[derive(Serialize, Deserialize)] -pub struct State { - pub avail_features: u64, - pub acked_features: u64, - pub config: VirtioNetConfig, - pub acked_protocol_features: u64, - pub vu_num_queues: usize, -} +pub type State = VhostUserState; struct BackendReqHandler {} impl VhostUserFrontendReqHandler for BackendReqHandler {} @@ -235,6 +227,7 @@ impl Net { config: self.config, acked_protocol_features: self.vu_common.acked_protocol_features, vu_num_queues: self.vu_common.vu_num_queues, + ..Default::default() } } } From bd56214d5453f03ca3d98323a8c41647c0e4c3a1 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 08:48:41 -0700 Subject: [PATCH 0934/1893] 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 --- virtio-devices/src/vhost_user/blk.rs | 9 +-------- virtio-devices/src/vhost_user/fs.rs | 9 +-------- virtio-devices/src/vhost_user/generic_vhost_user.rs | 9 +-------- virtio-devices/src/vhost_user/mod.rs | 11 +++++++++++ virtio-devices/src/vhost_user/net.rs | 9 +-------- 5 files changed, 15 insertions(+), 32 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 66958e32c3..7b574a583e 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -192,14 +192,7 @@ impl Blk { } fn state(&self) -> State { - State { - avail_features: self.common.avail_features, - acked_features: self.common.acked_features, - config: self.config, - acked_protocol_features: self.vu_common.acked_protocol_features, - vu_num_queues: self.vu_common.vu_num_queues, - ..Default::default() - } + self.vu_common.state(&self.common, self.config) } } diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index f9f819c0cc..3ae5262649 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -201,14 +201,7 @@ impl Fs { } fn state(&self) -> State { - State { - avail_features: self.common.avail_features, - acked_features: self.common.acked_features, - config: self.config, - acked_protocol_features: self.vu_common.acked_protocol_features, - vu_num_queues: self.vu_common.vu_num_queues, - ..Default::default() - } + self.vu_common.state(&self.common, self.config) } } diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index d0880b02e1..2110e6842e 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -157,14 +157,7 @@ since the backend only supports {backend_num_queues}\n", } fn state(&self) -> State { - State { - avail_features: self.common.avail_features, - acked_features: self.common.acked_features, - acked_protocol_features: self.vu_common.acked_protocol_features, - vu_num_queues: self.vu_common.vu_num_queues, - backend_req_support: false, - ..Default::default() - } + self.vu_common.state(&self.common, ()) } #[cold] diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 8632125391..811ff92e11 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -457,6 +457,17 @@ impl VhostUserCommon { Ok(()) } + pub fn state(&self, common: &crate::VirtioCommon, config: C) -> VhostUserState { + VhostUserState { + avail_features: common.avail_features, + acked_features: common.acked_features, + config, + acked_protocol_features: self.acked_protocol_features, + vu_num_queues: self.vu_num_queues, + ..Default::default() + } + } + pub fn snapshot(&mut self, state: &T) -> std::result::Result where T: Serialize, diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index d6d142ba6d..2145691a00 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -221,14 +221,7 @@ impl Net { } fn state(&self) -> State { - State { - avail_features: self.common.avail_features, - acked_features: self.common.acked_features, - config: self.config, - acked_protocol_features: self.vu_common.acked_protocol_features, - vu_num_queues: self.vu_common.vu_num_queues, - ..Default::default() - } + self.vu_common.state(&self.common, self.config) } } From 3e233af654e18acc4422e69cd375367d3b49bf38 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 08:49:29 -0700 Subject: [PATCH 0935/1893] 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 --- virtio-devices/src/vhost_user/generic_vhost_user.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 2110e6842e..368f46982b 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -213,7 +213,8 @@ impl VirtioDevice for GenericVhostUser { } fn read_config(&self, offset: u64, data: &mut [u8]) { - if (VhostUserProtocolFeatures::CONFIG.bits() & self.state().acked_protocol_features) == 0 { + if (VhostUserProtocolFeatures::CONFIG.bits() & self.vu_common.acked_protocol_features) == 0 + { self.warn_no_config_access(); data.fill(0xFF); @@ -240,7 +241,8 @@ impl VirtioDevice for GenericVhostUser { } fn write_config(&mut self, offset: u64, data: &[u8]) { - if (VhostUserProtocolFeatures::CONFIG.bits() & self.state().acked_protocol_features) == 0 { + if (VhostUserProtocolFeatures::CONFIG.bits() & self.vu_common.acked_protocol_features) == 0 + { self.warn_no_config_access(); return; } From 8536a2536e95a3a0762c03d426ff8eaaec7f1acf Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 14:14:37 -0700 Subject: [PATCH 0936/1893] 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 --- virtio-devices/src/vhost_user/blk.rs | 4 ++-- virtio-devices/src/vhost_user/fs.rs | 4 ++-- virtio-devices/src/vhost_user/generic_vhost_user.rs | 4 ++-- virtio-devices/src/vhost_user/mod.rs | 12 +++++++++--- virtio-devices/src/vhost_user/net.rs | 4 ++-- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 7b574a583e..8d6003216c 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -191,7 +191,7 @@ impl Blk { }) } - fn state(&self) -> State { + fn state(&self) -> std::result::Result { self.vu_common.state(&self.common, self.config) } } @@ -367,7 +367,7 @@ impl Snapshottable for Blk { } fn snapshot(&mut self) -> std::result::Result { - self.vu_common.snapshot(&self.state()) + self.vu_common.snapshot(&self.state()?) } } impl Transportable for Blk {} diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 3ae5262649..b5bf495baa 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -200,7 +200,7 @@ impl Fs { }) } - fn state(&self) -> State { + fn state(&self) -> std::result::Result { self.vu_common.state(&self.common, self.config) } } @@ -377,7 +377,7 @@ impl Snapshottable for Fs { } fn snapshot(&mut self) -> std::result::Result { - self.vu_common.snapshot(&self.state()) + self.vu_common.snapshot(&self.state()?) } } impl Transportable for Fs {} diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 368f46982b..9b4c8e4990 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -156,7 +156,7 @@ since the backend only supports {backend_num_queues}\n", }) } - fn state(&self) -> State { + fn state(&self) -> std::result::Result { self.vu_common.state(&self.common, ()) } @@ -396,7 +396,7 @@ impl Snapshottable for GenericVhostUser { } fn snapshot(&mut self) -> std::result::Result { - self.vu_common.snapshot(&self.state()) + self.vu_common.snapshot(&self.state()?) } } impl Transportable for GenericVhostUser {} diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 811ff92e11..07df6f0130 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -457,15 +457,21 @@ impl VhostUserCommon { Ok(()) } - pub fn state(&self, common: &crate::VirtioCommon, config: C) -> VhostUserState { - VhostUserState { + pub fn state( + &self, + common: &crate::VirtioCommon, + config: C, + ) -> std::result::Result, MigratableError> { + let state = VhostUserState { avail_features: common.avail_features, acked_features: common.acked_features, config, acked_protocol_features: self.acked_protocol_features, vu_num_queues: self.vu_num_queues, ..Default::default() - } + }; + + Ok(state) } pub fn snapshot(&mut self, state: &T) -> std::result::Result diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 2145691a00..ac3719d9d0 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -220,7 +220,7 @@ impl Net { }) } - fn state(&self) -> State { + fn state(&self) -> std::result::Result { self.vu_common.state(&self.common, self.config) } } @@ -425,7 +425,7 @@ impl Snapshottable for Net { } fn snapshot(&mut self) -> std::result::Result { - self.vu_common.snapshot(&self.state()) + self.vu_common.snapshot(&self.state()?) } } impl Transportable for Net {} From afc3cb8e1f5b93307b58d3ee06983c0b614fadf1 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 14:15:09 -0700 Subject: [PATCH 0937/1893] 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 --- virtio-devices/src/vhost_user/vu_common_ctrl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index b603463fb4..497888ca80 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -150,7 +150,7 @@ impl VhostUserHandle { self.vu.set_hdr_flags(VhostUserHeaderFlag::NEED_REPLY); } - self.update_supports_migration(acked_features, acked_protocol_features.bits()); + self.update_supported_features(acked_features, acked_protocol_features.bits()); Ok((acked_features, acked_protocol_features.bits())) } @@ -334,7 +334,7 @@ impl VhostUserHandle { } } - self.update_supports_migration(acked_features, acked_protocol_features); + self.update_supported_features(acked_features, acked_protocol_features); Ok(()) } @@ -438,7 +438,7 @@ impl VhostUserHandle { Ok(()) } - fn update_supports_migration(&mut self, acked_features: u64, acked_protocol_features: u64) { + fn update_supported_features(&mut self, acked_features: u64, acked_protocol_features: u64) { if (acked_features & u64::from(vhost::vhost_kern::vhost_binding::VHOST_F_LOG_ALL) != 0) && (acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0) { From a6b945540253790777396219bbb24e7cbbfb3233 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 13:34:54 -0700 Subject: [PATCH 0938/1893] 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 --- virtio-devices/src/vhost_user/mod.rs | 9 ++ .../src/vhost_user/vu_common_ctrl.rs | 122 ++++++++++++++++-- 2 files changed, 120 insertions(+), 11 deletions(-) diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 07df6f0130..92e1b73d8b 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -148,6 +148,14 @@ pub enum Error { NewMmapRegion(#[source] MmapRegionError), #[error("Could not find the shm log region")] MissingShmLogRegion, + #[error("Failed setting device state fd")] + VhostUserSetDeviceStateFd(#[source] VhostError), + #[error("Failed checking device state")] + VhostUserCheckDeviceState(#[source] VhostError), + #[error("Failed saving/restoring backend state")] + SaveRestoreBackendState(#[source] io::Error), + #[error("Vring bases count ({0}) does not match queue count ({1})")] + VringBasesCountMismatch(usize, usize), } type Result = std::result::Result; @@ -360,6 +368,7 @@ impl VhostUserCommon { acked_features, &backend_req_handler, inflight.as_mut(), + None, ) .map_err(ActivateError::VhostUserSetup)?; diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 497888ca80..0e59e23990 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -3,8 +3,9 @@ use std::ffi; use std::fs::File; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::os::unix::net::UnixListener; +use std::io::{Read, Write}; +use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd}; +use std::os::unix::net::{UnixListener, UnixStream}; use std::sync::Arc; use std::sync::atomic::Ordering; use std::thread::sleep; @@ -13,7 +14,8 @@ use std::time::{Duration, Instant}; use log::{error, info}; use vhost::vhost_kern::vhost_binding::{VHOST_F_LOG_ALL, VHOST_VRING_F_LOG}; use vhost::vhost_user::message::{ - VhostUserHeaderFlag, VhostUserInflight, VhostUserProtocolFeatures, VhostUserVirtioFeatures, + VhostTransferStateDirection, VhostTransferStatePhase, VhostUserHeaderFlag, VhostUserInflight, + VhostUserProtocolFeatures, VhostUserVirtioFeatures, }; use vhost::vhost_user::{ Frontend, FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler, @@ -54,6 +56,7 @@ pub struct VhostUserHandle { vu: Frontend, ready: bool, supports_migration: bool, + supports_device_state: bool, shm_log: Option>, acked_features: u64, vrings_info: Option>, @@ -164,7 +167,14 @@ impl VhostUserHandle { acked_features: u64, backend_req_handler: &Option>, inflight: Option<&mut Inflight>, + vring_bases: Option<&[u64]>, ) -> Result<()> { + if let Some(bases) = &vring_bases + && bases.len() != queues.len() + { + return Err(Error::VringBasesCountMismatch(bases.len(), queues.len())); + } + self.vu .set_features(acked_features) .map_err(Error::VhostUserSetFeatures)?; @@ -207,7 +217,7 @@ impl VhostUserHandle { } let mut vrings_info = Vec::new(); - for (queue_index, queue, queue_evt) in queues.iter() { + for (i, (queue_index, queue, queue_evt)) in queues.iter().enumerate() { let actual_size: usize = queue.size().into(); let config_data = VringConfigData { @@ -247,14 +257,16 @@ impl VhostUserHandle { self.vu .set_vring_addr(*queue_index, &config_data) .map_err(Error::VhostUserSetVringAddr)?; + let base = if let Some(bases) = vring_bases { + bases[i] as u16 + } else { + queue + .avail_idx(mem, Ordering::Acquire) + .map_err(Error::GetAvailableIndex)? + .0 + }; self.vu - .set_vring_base( - *queue_index, - queue - .avail_idx(mem, Ordering::Acquire) - .map_err(Error::GetAvailableIndex)? - .0, - ) + .set_vring_base(*queue_index, base) .map_err(Error::VhostUserSetVringBase)?; if let Some(eventfd) = @@ -359,6 +371,7 @@ impl VhostUserHandle { acked_features, backend_req_handler, inflight, + None, ) } @@ -382,6 +395,7 @@ impl VhostUserHandle { vu: Frontend::from_stream(stream, num_queues), ready: false, supports_migration: false, + supports_device_state: false, shm_log: None, acked_features: 0, vrings_info: None, @@ -398,6 +412,7 @@ impl VhostUserHandle { vu: m, ready: false, supports_migration: false, + supports_device_state: false, shm_log: None, acked_features: 0, vrings_info: None, @@ -444,6 +459,91 @@ impl VhostUserHandle { { self.supports_migration = true; } + self.supports_device_state = + acked_protocol_features & VhostUserProtocolFeatures::DEVICE_STATE.bits() != 0; + } + + pub fn supports_device_state(&self) -> bool { + self.supports_device_state + } + + /// Save backend device state via the SET_DEVICE_STATE_FD protocol. + /// Returns the opaque state blob and per-queue vring base indices. + pub fn save_backend_state(&mut self) -> Result<(Vec, Vec)> { + // GET_VRING_BASE for each queue to stop the backend and capture indices + let mut vring_bases = Vec::new(); + for queue_index in &self.queue_indexes { + let base = self + .vu + .get_vring_base(*queue_index) + .map_err(Error::VhostUserGetVringBase)?; + vring_bases.push(base as u64); + } + + // The backend considers the vrings stopped after GET_VRING_BASE. + self.ready = false; + + let (local, remote) = UnixStream::pair().map_err(Error::SaveRestoreBackendState)?; + + let mut read_file: File = match self + .vu + .set_device_state_fd( + VhostTransferStateDirection::SAVE, + VhostTransferStatePhase::STOPPED, + remote.into(), + ) + .map_err(Error::VhostUserSetDeviceStateFd)? + { + Some(file) => file, + None => OwnedFd::from(local).into(), + }; + + // Read all state from the socket + let mut state = Vec::new(); + read_file + .read_to_end(&mut state) + .map_err(Error::SaveRestoreBackendState)?; + + // Verify the transfer succeeded + self.vu + .check_device_state() + .map_err(Error::VhostUserCheckDeviceState)?; + + Ok((state, vring_bases)) + } + + /// Restore backend device state via the SET_DEVICE_STATE_FD protocol. + /// Sends the saved opaque state blob to the backend via a socket. + pub fn restore_backend_state(&mut self, state: &[u8]) -> Result<()> { + let (local, remote) = UnixStream::pair().map_err(Error::SaveRestoreBackendState)?; + + // Explicit scope to close the write end and signal EOF to the backend + { + let mut write_file: File = match self + .vu + .set_device_state_fd( + VhostTransferStateDirection::LOAD, + VhostTransferStatePhase::STOPPED, + remote.into(), + ) + .map_err(Error::VhostUserSetDeviceStateFd)? + { + Some(file) => file, + None => OwnedFd::from(local).into(), + }; + + // Write the saved state to the socket + write_file + .write_all(state) + .map_err(Error::SaveRestoreBackendState)?; + } + + // Verify the transfer succeeded + self.vu + .check_device_state() + .map_err(Error::VhostUserCheckDeviceState)?; + + Ok(()) } fn update_log_base(&mut self, last_ram_addr: u64) -> Result>> { From 02688993a0fd5334803077f17f7c4bf2995ee041 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 13:41:38 -0700 Subject: [PATCH 0939/1893] 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 --- virtio-devices/src/vhost_user/mod.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 92e1b73d8b..3858c5c052 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -317,6 +317,10 @@ pub struct VhostUserState { pub vu_num_queues: usize, #[serde(default)] pub backend_req_support: bool, + #[serde(default)] + pub vring_bases: Option>, + #[serde(default)] + pub backend_state: Option>, } #[derive(Default)] @@ -328,6 +332,7 @@ pub struct VhostUserCommon { pub migration_started: bool, pub server: bool, pub interrupt_cb: Option>, + pub vring_bases: Option>, } impl VhostUserCommon { @@ -359,6 +364,7 @@ impl VhostUserCommon { .iter() .map(|(i, q, e)| (*i, vm_virtio::clone_queue(q), e.try_clone().unwrap())) .collect::>(); + let vring_bases = self.vring_bases.take(); vu.lock() .unwrap() .setup_vhost_user( @@ -368,7 +374,7 @@ impl VhostUserCommon { acked_features, &backend_req_handler, inflight.as_mut(), - None, + vring_bases.as_deref(), ) .map_err(ActivateError::VhostUserSetup)?; @@ -471,7 +477,7 @@ impl VhostUserCommon { common: &crate::VirtioCommon, config: C, ) -> std::result::Result, MigratableError> { - let state = VhostUserState { + let mut state = VhostUserState { avail_features: common.avail_features, acked_features: common.acked_features, config, @@ -480,6 +486,17 @@ impl VhostUserCommon { ..Default::default() }; + if let Some(vu) = &self.vu { + let mut vu_locked = vu.lock().unwrap(); + if vu_locked.supports_device_state() { + let (backend_state, vring_bases) = vu_locked.save_backend_state().map_err(|e| { + MigratableError::Snapshot(anyhow!("Failed saving backend state: {e:?}")) + })?; + state.backend_state = Some(backend_state); + state.vring_bases = Some(vring_bases); + } + } + Ok(state) } From 2fbb98e2c3ee6e52164f9c0d3577a14832cde415 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 15:16:58 -0700 Subject: [PATCH 0940/1893] 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 --- virtio-devices/src/vhost_user/mod.rs | 11 +++++++++++ virtio-devices/src/vhost_user/vu_common_ctrl.rs | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 3858c5c052..86a774737a 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -156,6 +156,8 @@ pub enum Error { SaveRestoreBackendState(#[source] io::Error), #[error("Vring bases count ({0}) does not match queue count ({1})")] VringBasesCountMismatch(usize, usize), + #[error("Backend state and vring bases must both be present or both be absent")] + InconsistentBackendState, } type Result = std::result::Result; @@ -323,6 +325,15 @@ pub struct VhostUserState { pub backend_state: Option>, } +impl VhostUserState { + pub fn validate(&self) -> Result<()> { + if self.backend_state.is_some() != self.vring_bases.is_some() { + return Err(Error::InconsistentBackendState); + } + Ok(()) + } +} + #[derive(Default)] pub struct VhostUserCommon { pub vu: Option>>, diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 0e59e23990..b2ee2ae5dc 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -28,7 +28,7 @@ use vm_memory::{Address, FileOffset, GuestAddress, GuestMemory, GuestMemoryRegio use vm_migration::protocol::MemoryRangeTable; use vmm_sys_util::eventfd::EventFd; -use super::{Error, Result}; +use super::{Error, Result, VhostUserState}; use crate::vhost_user::Inflight; use crate::{ GuestMemoryMmap, GuestRegionMmap, MmapRegion, VirtioInterrupt, VirtioInterruptType, @@ -512,6 +512,14 @@ impl VhostUserHandle { Ok((state, vring_bases)) } + pub fn restore_state(&mut self, state: &VhostUserState) -> Result<()> { + state.validate()?; + if let Some(backend_state) = &state.backend_state { + self.restore_backend_state(backend_state)?; + } + Ok(()) + } + /// Restore backend device state via the SET_DEVICE_STATE_FD protocol. /// Sends the saved opaque state blob to the backend via a socket. pub fn restore_backend_state(&mut self, state: &[u8]) -> Result<()> { From cd3334a3c27f9b46eb20d2c7a28d2d0d3e6c4ff8 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 14:22:42 -0700 Subject: [PATCH 0941/1893] 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 --- virtio-devices/src/vhost_user/blk.rs | 9 +- virtio-devices/src/vhost_user/fs.rs | 9 +- .../src/vhost_user/generic_vhost_user.rs | 125 ++++++++++-------- virtio-devices/src/vhost_user/net.rs | 9 +- 4 files changed, 92 insertions(+), 60 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 8d6003216c..203012b8ec 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -72,6 +72,7 @@ impl Blk { vu_num_queues, config, paused, + vring_bases, ) = if let Some(state) = state { info!("Restoring vhost-user-block {id}"); @@ -80,6 +81,8 @@ impl Blk { state.acked_protocol_features, )?; + vu.restore_state(&state)?; + ( state.avail_features, state.acked_features, @@ -87,6 +90,7 @@ impl Blk { state.vu_num_queues, state.config, true, + state.vring_bases, ) } else { // Filling device and vring features VMM supports. @@ -111,7 +115,8 @@ impl Blk { | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS | VhostUserProtocolFeatures::REPLY_ACK | VhostUserProtocolFeatures::INFLIGHT_SHMFD - | VhostUserProtocolFeatures::LOG_SHMFD; + | VhostUserProtocolFeatures::LOG_SHMFD + | VhostUserProtocolFeatures::DEVICE_STATE; let (acked_features, acked_protocol_features) = vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; @@ -160,6 +165,7 @@ impl Blk { num_queues, config, false, + None, ) }; @@ -179,6 +185,7 @@ impl Blk { acked_protocol_features, socket_path: vu_cfg.socket, vu_num_queues, + vring_bases, ..Default::default() }, id, diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index b5bf495baa..1b8edfe6bd 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -101,6 +101,7 @@ impl Fs { vu_num_queues, config, paused, + vring_bases, ) = if let Some(state) = state { info!("Restoring vhost-user-fs {id}"); @@ -109,6 +110,8 @@ impl Fs { state.acked_protocol_features, )?; + vu.restore_state(&state)?; + ( state.avail_features, state.acked_features, @@ -116,6 +119,7 @@ impl Fs { state.vu_num_queues, state.config, true, + state.vring_bases, ) } else { // Filling device and vring features VMM supports. @@ -125,7 +129,8 @@ impl Fs { | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS | VhostUserProtocolFeatures::REPLY_ACK | VhostUserProtocolFeatures::INFLIGHT_SHMFD - | VhostUserProtocolFeatures::LOG_SHMFD; + | VhostUserProtocolFeatures::LOG_SHMFD + | VhostUserProtocolFeatures::DEVICE_STATE; let (acked_features, acked_protocol_features) = vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; @@ -168,6 +173,7 @@ impl Fs { num_queues, config, false, + None, ) }; @@ -187,6 +193,7 @@ impl Fs { acked_protocol_features, socket_path: path.to_string(), vu_num_queues, + vring_bases, ..Default::default() }, id, diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 9b4c8e4990..da8fe53a88 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -68,64 +68,74 @@ impl GenericVhostUser { // Connect to the vhost-user socket. let mut vu = VhostUserHandle::connect_vhost_user(false, path, num_queues as u64, false)?; - let (avail_features, acked_features, acked_protocol_features, vu_num_queues, paused) = - if let Some(state) = state { - info!("Restoring generic vhost-user {id}"); - vu.set_protocol_features_vhost_user( - state.acked_features, - state.acked_protocol_features, - )?; - - ( - state.avail_features, - state.acked_features, - state.acked_protocol_features, - state.vu_num_queues, - true, - ) - } else { - let avail_protocol_features = VhostUserProtocolFeatures::CONFIG - | VhostUserProtocolFeatures::MQ - | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS - | VhostUserProtocolFeatures::REPLY_ACK - | VhostUserProtocolFeatures::INFLIGHT_SHMFD - | VhostUserProtocolFeatures::LOG_SHMFD; - - let avail_features = super::DEFAULT_VIRTIO_FEATURES; - - let (acked_features, acked_protocol_features) = - vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; - - let backend_num_queues = - if acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0 { - vu.socket_handle() - .get_queue_num() - .map_err(Error::VhostUserGetQueueMaxNum)? - as usize - } else { - num_queues - }; - - if num_queues > backend_num_queues { - error!( - "generic vhost-user requested too many queues ({num_queues}) \ + let ( + avail_features, + acked_features, + acked_protocol_features, + vu_num_queues, + paused, + vring_bases, + ) = if let Some(state) = state { + info!("Restoring generic vhost-user {id}"); + vu.set_protocol_features_vhost_user( + state.acked_features, + state.acked_protocol_features, + )?; + + vu.restore_state(&state)?; + + ( + state.avail_features, + state.acked_features, + state.acked_protocol_features, + state.vu_num_queues, + true, + state.vring_bases, + ) + } else { + let avail_protocol_features = VhostUserProtocolFeatures::CONFIG + | VhostUserProtocolFeatures::MQ + | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS + | VhostUserProtocolFeatures::REPLY_ACK + | VhostUserProtocolFeatures::INFLIGHT_SHMFD + | VhostUserProtocolFeatures::LOG_SHMFD + | VhostUserProtocolFeatures::DEVICE_STATE; + + let avail_features = super::DEFAULT_VIRTIO_FEATURES; + + let (acked_features, acked_protocol_features) = + vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; + + let backend_num_queues = + if acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0 { + vu.socket_handle() + .get_queue_num() + .map_err(Error::VhostUserGetQueueMaxNum)? as usize + } else { + num_queues + }; + + if num_queues > backend_num_queues { + error!( + "generic vhost-user requested too many queues ({num_queues}) \ since the backend only supports {backend_num_queues}\n", - ); - return Err(Error::BadQueueNum); - } - - ( - acked_features, - // If part of the available features that have been acked, the - // PROTOCOL_FEATURES bit must be already set through the VIRTIO - // acked features as we know the guest would never ack it, thus - // the feature would be lost. - acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(), - acked_protocol_features, - num_queues, - false, - ) - }; + ); + return Err(Error::BadQueueNum); + } + + ( + acked_features, + // If part of the available features that have been acked, the + // PROTOCOL_FEATURES bit must be already set through the VIRTIO + // acked features as we know the guest would never ack it, thus + // the feature would be lost. + acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(), + acked_protocol_features, + num_queues, + false, + None, + ) + }; Ok(GenericVhostUser { common: VirtioCommon { @@ -143,6 +153,7 @@ since the backend only supports {backend_num_queues}\n", acked_protocol_features, socket_path: path.to_string(), vu_num_queues, + vring_bases, ..Default::default() }, id, diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index ac3719d9d0..d05626901a 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -82,6 +82,7 @@ impl Net { vu_num_queues, config, paused, + vring_bases, ) = if let Some(state) = state { info!("Restoring vhost-user-net {id}"); @@ -95,6 +96,8 @@ impl Net { state.acked_protocol_features, )?; + vu.restore_state(&state)?; + // If the control queue feature has been negotiated, let's // increase the number of queues. if state.acked_features & (1 << VIRTIO_NET_F_CTRL_VQ) != 0 { @@ -108,6 +111,7 @@ impl Net { state.vu_num_queues, state.config, true, + state.vring_bases, ) } else { // Filling device and vring features VMM supports. @@ -144,7 +148,8 @@ impl Net { | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS | VhostUserProtocolFeatures::REPLY_ACK | VhostUserProtocolFeatures::INFLIGHT_SHMFD - | VhostUserProtocolFeatures::LOG_SHMFD; + | VhostUserProtocolFeatures::LOG_SHMFD + | VhostUserProtocolFeatures::DEVICE_STATE; let (mut acked_features, acked_protocol_features) = vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; @@ -187,6 +192,7 @@ impl Net { vu_num_queues, config, false, + None, ) }; @@ -208,6 +214,7 @@ impl Net { socket_path: vu_cfg.socket, vu_num_queues, server, + vring_bases, ..Default::default() }, config, From 0bc3ca1103142be52a52bf820f09c93305deb81c Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 26 Mar 2026 09:01:57 -0700 Subject: [PATCH 0942/1893] 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 --- cloud-hypervisor/tests/integration.rs | 152 ++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index a9acca630d..8fbe6dd73b 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7077,6 +7077,158 @@ mod common_sequential { handle_child_output(r, &output); } + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_virtio_fs() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); + + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + let mut shared_dir = workload_path; + shared_dir.push("shared_dir"); + + let (mut daemon_child, virtiofsd_socket_path) = + prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); + + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_source]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=512M,shared=on"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .default_disks() + .default_net() + .args([ + "--fs", + format!("socket={virtiofsd_socket_path},tag=myfs,num_queues=1,queue_size=1024") + .as_str(), + ]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .capture_output() + .spawn() + .unwrap(); + + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Mount virtiofs and write a test file + guest + .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") + .unwrap(); + + // Verify the shared directory is accessible + assert_eq!( + guest.ssh_command("cat mount_dir/file1").unwrap().trim(), + "foo" + ); + + // Write a file from the guest + guest + .ssh_command( + "sudo bash -c 'echo snapshot_test_data > mount_dir/snapshot_test_file'", + ) + .unwrap(); + + snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); + }); + + // Shutdown the source VM + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + // Kill the old virtiofsd + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); + + // Start a fresh virtiofsd (reusing the same socket path) + let (mut daemon_child, _) = prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); + + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + + // Restore the VM from the snapshot + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_restored]) + .args([ + "--event-monitor", + format!("path={event_path_restored}").as_str(), + ]) + .args([ + "--restore", + format!("source_url=file://{snapshot_dir}").as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + // Wait for the VM to be restored + thread::sleep(std::time::Duration::new(20, 0)); + + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(check_latest_events_exact( + &latest_events, + &event_path_restored + )); + + // Remove the snapshot dir + let _ = remove_dir_all(snapshot_dir.as_str()); + + let r = std::panic::catch_unwind(|| { + // Resume the VM + assert!(remote_command(&api_socket_restored, "resume", None)); + thread::sleep(std::time::Duration::new(5, 0)); + + // Verify virtiofs still works after restore + // Read the file written before snapshot + assert_eq!( + guest + .ssh_command("cat mount_dir/snapshot_test_file") + .unwrap() + .trim(), + "snapshot_test_data" + ); + + // Read the pre-existing shared file + assert_eq!( + guest.ssh_command("cat mount_dir/file1").unwrap().trim(), + "foo" + ); + + // Write a new file after restore + guest + .ssh_command("sudo bash -c 'echo post_restore_data > mount_dir/post_restore_file'") + .unwrap(); + + // Verify the new file exists on the host + let post_restore_content = + std::fs::read_to_string(shared_dir.join("post_restore_file")).unwrap(); + assert_eq!(post_restore_content.trim(), "post_restore_data"); + }); + + // Shutdown the target VM + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + // Clean up virtiofsd and test files + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); + let _ = std::fs::remove_file(shared_dir.join("snapshot_test_file")); + let _ = std::fs::remove_file(shared_dir.join("post_restore_file")); + } + #[test] fn test_virtio_pmem_persist_writes() { test_virtio_pmem(false, false); From c06f6c32932e3241676ba658a8fa9b2852d9e305 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 27 Mar 2026 15:47:26 +0100 Subject: [PATCH 0943/1893] 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 --- cloud-hypervisor/tests/integration.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 8fbe6dd73b..bac62af116 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -9949,7 +9949,7 @@ mod live_migration { // Start a memory stressor in the background to keep pages dirty, // ensuring the precopy loop cannot converge within the 1s timeout. guest - .ssh_command("nohup stress --vm 2 --vm-bytes 200M --vm-keep &>/dev/null &") + .ssh_command("nohup stress --vm 2 --vm-bytes 220M --vm-keep &>/dev/null &") .unwrap(); // Give stress a moment to actually start dirtying memory thread::sleep(Duration::from_secs(3)); @@ -9971,14 +9971,14 @@ mod live_migration { thread::sleep(Duration::from_secs(1)); - // Use a tight downtime budget (50ms) combined with a 1s timeout so the - // migration cannot converge regardless of strategy. + // Use a tight downtime budget (1ms) combined with a 1s timeout so the + // migration practically cannot converge regardless of strategy. let mut send_migration = Command::new(clh_command("ch-remote")) .args([ &format!("--api-socket={src_api_socket}"), "send-migration", &format!( - "destination_url=tcp:{host_ip}:{migration_port},downtime_ms=50,timeout_s=1,timeout_strategy={timeout_strategy:?}" + "destination_url=tcp:{host_ip}:{migration_port},downtime_ms=1,timeout_s=1,timeout_strategy={timeout_strategy:?}" ), ]) .stdin(Stdio::null()) From baacaea35e5bcd2440f8a8078345f08d6c6b38d4 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 27 Mar 2026 16:45:59 -0700 Subject: [PATCH 0944/1893] 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 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- fuzz/Cargo.lock | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad3fe2b934..c1ce72b0b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2286,9 +2286,9 @@ dependencies = [ [[package]] name = "vfio-ioctls" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c80c6d52f8e592e31a8f7eb45e882a9617aa61ec2479981a175e9f0a79f2434e" +checksum = "d4b1d98dff7f0d219278e406323e7eda4d426447bd203c7828189baf0d8c07b7" dependencies = [ "byteorder", "kvm-bindings", diff --git a/Cargo.toml b/Cargo.toml index cc2bd3c175..e22ace8382 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ mshv-bindings = "0.6.7" mshv-ioctls = "0.6.7" seccompiler = "0.5.0" vfio-bindings = { version = "0.6.2", default-features = false } -vfio-ioctls = { version = "0.5.3", default-features = false } +vfio-ioctls = { version = "0.6.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 } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 559fd35ce6..488476e9e9 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1282,9 +1282,9 @@ dependencies = [ [[package]] name = "vfio-ioctls" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c80c6d52f8e592e31a8f7eb45e882a9617aa61ec2479981a175e9f0a79f2434e" +checksum = "d4b1d98dff7f0d219278e406323e7eda4d426447bd203c7828189baf0d8c07b7" dependencies = [ "byteorder", "kvm-bindings", From ce3b3fa1b248e86ae1bccacc273e87427b4b18d2 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Fri, 27 Mar 2026 18:41:24 +0000 Subject: [PATCH 0945/1893] build: Enable the vfio CI worker This reverts commit 8aaf3734aadbf550ad7df06328f5c7964fc38220. Fixes: #7751 Signed-off-by: Saravanan D Signed-off-by: Bo Chen --- .github/workflows/integration-vfio.yaml | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/integration-vfio.yaml diff --git a/.github/workflows/integration-vfio.yaml b/.github/workflows/integration-vfio.yaml new file mode 100644 index 0000000000..218e897270 --- /dev/null +++ b/.github/workflows/integration-vfio.yaml @@ -0,0 +1,33 @@ +name: Cloud Hypervisor Tests (VFIO) +on: [merge_group, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} + 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" From ea0a0393b50ac56e3a06217dab0f3f2a0d9e2a1b Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Fri, 27 Mar 2026 18:53:31 +0000 Subject: [PATCH 0946/1893] 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 --- .github/workflows/integration-vfio.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-vfio.yaml b/.github/workflows/integration-vfio.yaml index 218e897270..19d5cadba6 100644 --- a/.github/workflows/integration-vfio.yaml +++ b/.github/workflows/integration-vfio.yaml @@ -13,7 +13,7 @@ jobs: steps: - name: Fix workspace permissions if: ${{ github.event_name != 'pull_request' }} - run: sudo chown -R runner:runner ${GITHUB_WORKSPACE} + run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE} - name: Code checkout if: ${{ github.event_name != 'pull_request' }} uses: actions/checkout@v6 From 9b9950335fcec2d8c555ebcf04b361d4d6cc8936 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 28 Mar 2026 12:55:09 -0400 Subject: [PATCH 0947/1893] 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 --- .github/workflows/mshv-integration.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index 261b84d546..ad544249eb 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -1,5 +1,6 @@ name: Cloud Hypervisor Tests (MSHV) (x86_64) on: [pull_request_target, merge_group] +permissions: {} jobs: infra-setup: From cbe7018f84d4ac52c5cab1385d9a5d754cf5b788 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Mon, 30 Mar 2026 13:49:08 +0000 Subject: [PATCH 0948/1893] 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 --- vmm/src/cpu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index e855204566..f8fff2b299 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -2762,7 +2762,7 @@ impl Debuggable for CpuManager { ]; // GDB exposes 32-bit eflags instead of 64-bit rflags. - // https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/64bit-core.xml + // https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/features/i386/64bit-core.xml let eflags = gregs.get_rflags() as u32; let rip = gregs.get_rip(); From 823ab99e027a280685c398aabc29d78254b04d3c Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 28 Mar 2026 14:46:01 +0100 Subject: [PATCH 0949/1893] 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 --- virtio-devices/src/block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index a38a378d1b..8f919e3e04 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -340,7 +340,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese Ok(_) => VIRTIO_BLK_S_OK, Err(e) => { warn!("Request failed: {request:x?} {e:?}"); - VIRTIO_BLK_S_IOERR + e.status() as u32 } }; From 6261cad0d8da1124e4460cbded8a259cecbb993f Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 18:22:56 -0700 Subject: [PATCH 0950/1893] 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 --- block/src/async_io.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index fe3349e6a3..2d8ea37878 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -24,6 +24,8 @@ pub enum DiskFileError { /// Resize failed #[error("Resize failed")] ResizeError(#[source] std::io::Error), + #[error("Failed cloning disk file")] + Clone(#[source] std::io::Error), } pub type DiskFileResult = std::result::Result; From 4586ca133a171ebfae8d0aa558827e0f5c69e9bf Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:17:33 -0700 Subject: [PATCH 0951/1893] 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 --- block/src/raw_async.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 4efff45e9c..b5ae8f50a3 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -18,6 +18,7 @@ use crate::{ BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, probe_sparse_support, query_device_size, }; +#[derive(Debug)] pub struct RawFileDisk { file: File, } From 064720221590e0a94a68ef4bab78d8ddc3055feb Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:24:02 -0700 Subject: [PATCH 0952/1893] 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 --- block/src/raw_async.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index b5ae8f50a3..7dcda5bbdb 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -14,8 +14,10 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; +use crate::error::{BlockError, BlockErrorKind, BlockResult}; use crate::{ - BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, probe_sparse_support, query_device_size, + BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, disk_file, probe_sparse_support, + query_device_size, }; #[derive(Debug)] @@ -72,6 +74,14 @@ impl DiskFile for RawFileDisk { } } +impl disk_file::DiskSize for RawFileDisk { + fn logical_size(&self) -> BlockResult { + query_device_size(&self.file) + .map(|(logical_size, _)| logical_size) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e))) + } +} + pub struct RawFileAsync { fd: RawFd, io_uring: IoUring, From aa6a7aea0e6af7c84f9b9e72378e252f1ec20f57 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:24:25 -0700 Subject: [PATCH 0953/1893] 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 --- block/src/raw_async.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 7dcda5bbdb..477077effd 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -82,6 +82,14 @@ impl disk_file::DiskSize for RawFileDisk { } } +impl disk_file::PhysicalSize for RawFileDisk { + fn physical_size(&self) -> BlockResult { + query_device_size(&self.file) + .map(|(_, physical_size)| physical_size) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e))) + } +} + pub struct RawFileAsync { fd: RawFd, io_uring: IoUring, From ca1f48a7179c86132a630fbdaadafe7f7e832903 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:24:42 -0700 Subject: [PATCH 0954/1893] 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 --- block/src/raw_async.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 477077effd..c08f501c4b 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -90,6 +90,12 @@ impl disk_file::PhysicalSize for RawFileDisk { } } +impl disk_file::DiskFd for RawFileDisk { + fn fd(&self) -> BorrowedDiskFd<'_> { + BorrowedDiskFd::new(self.file.as_raw_fd()) + } +} + pub struct RawFileAsync { fd: RawFd, io_uring: IoUring, From 7e514d8d0e8b099c2dab2ede0427ac38609475d3 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:24:54 -0700 Subject: [PATCH 0955/1893] 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 --- block/src/raw_async.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index c08f501c4b..e5ba96de31 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -96,6 +96,15 @@ impl disk_file::DiskFd for RawFileDisk { } } +impl disk_file::Geometry for RawFileDisk { + fn topology(&self) -> DiskTopology { + DiskTopology::probe(&self.file).unwrap_or_else(|_| { + warn!("Unable to get device topology. Using default topology"); + DiskTopology::default() + }) + } +} + pub struct RawFileAsync { fd: RawFd, io_uring: IoUring, From 899dec4c0433f98f039c30409efc7036b382c4d4 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:25:05 -0700 Subject: [PATCH 0956/1893] 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 --- block/src/raw_async.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index e5ba96de31..86f23b1fa9 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -105,6 +105,12 @@ impl disk_file::Geometry for RawFileDisk { } } +impl disk_file::SparseCapable for RawFileDisk { + fn supports_sparse_operations(&self) -> bool { + probe_sparse_support(&self.file) + } +} + pub struct RawFileAsync { fd: RawFd, io_uring: IoUring, From 64b65caddd78262252d186f7a23841e05fb1f180 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:25:17 -0700 Subject: [PATCH 0957/1893] 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 --- block/src/raw_async.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 86f23b1fa9..0dc0bff2f1 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -111,6 +111,14 @@ impl disk_file::SparseCapable for RawFileDisk { } } +impl disk_file::Resizable for RawFileDisk { + fn resize(&mut self, size: u64) -> BlockResult<()> { + self.file + .set_len(size) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e))) + } +} + pub struct RawFileAsync { fd: RawFd, io_uring: IoUring, From 607a2c769d97edde98da0ddd882dd3289591032a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:25:29 -0700 Subject: [PATCH 0958/1893] 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 --- block/src/raw_async.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 0dc0bff2f1..153a0a94f1 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -119,6 +119,8 @@ impl disk_file::Resizable for RawFileDisk { } } +impl disk_file::DiskFile for RawFileDisk {} + pub struct RawFileAsync { fd: RawFd, io_uring: IoUring, From 491c5493c5c8dc72c8f972770940df3f7ddb1247 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:25:44 -0700 Subject: [PATCH 0959/1893] 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 --- block/src/raw_async.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 153a0a94f1..42309edda7 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -121,6 +121,24 @@ impl disk_file::Resizable for RawFileDisk { impl disk_file::DiskFile for RawFileDisk {} +impl disk_file::AsyncDiskFile for RawFileDisk { + fn try_clone(&self) -> BlockResult> { + let file = self + .file + .try_clone() + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?; + Ok(Box::new(RawFileDisk { file })) + } + + fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)))?; + raw.alignment = + DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); + Ok(Box::new(raw) as Box) + } +} + pub struct RawFileAsync { fd: RawFd, io_uring: IoUring, From 573f3af77d1f049f382f694328ba0f15ace72434 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 18:15:00 -0700 Subject: [PATCH 0960/1893] 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 --- block/src/raw_async.rs | 47 +-------------------------------------- vmm/src/device_manager.rs | 4 +--- 2 files changed, 2 insertions(+), 49 deletions(-) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 42309edda7..90332aa4b8 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -11,9 +11,7 @@ use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; use log::warn; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{ - AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, -}; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::error::{BlockError, BlockErrorKind, BlockResult}; use crate::{ BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, disk_file, probe_sparse_support, @@ -31,49 +29,6 @@ impl RawFileDisk { } } -impl DiskFile for RawFileDisk { - fn logical_size(&mut self) -> DiskFileResult { - Ok(query_device_size(&self.file) - .map_err(DiskFileError::Size)? - .0) - } - - fn physical_size(&mut self) -> DiskFileResult { - Ok(query_device_size(&self.file) - .map_err(DiskFileError::Size)? - .1) - } - - fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { - let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth) - .map_err(DiskFileError::NewAsyncIo)?; - raw.alignment = - DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); - Ok(Box::new(raw) as Box) - } - - fn topology(&mut self) -> DiskTopology { - if let Ok(topology) = DiskTopology::probe(&self.file) { - topology - } else { - warn!("Unable to get device topology. Using default topology"); - DiskTopology::default() - } - } - - fn resize(&mut self, size: u64) -> DiskFileResult<()> { - self.file.set_len(size).map_err(DiskFileError::ResizeError) - } - - fn supports_sparse_operations(&self) -> bool { - probe_sparse_support(&self.file) - } - - fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.file.as_raw_fd()) - } -} - impl disk_file::DiskSize for RawFileDisk { fn logical_size(&self) -> BlockResult { query_device_size(&self.file) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 95814cc92d..080fe9a3d6 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2756,9 +2756,7 @@ impl DeviceManager { unreachable!("Checked in if statement above"); #[cfg(feature = "io_uring")] { - DiskBackend::Legacy( - Box::new(RawFileDisk::new(file)) as Box - ) + DiskBackend::Next(Box::new(RawFileDisk::new(file))) } } else if !disk_cfg.disable_aio && self.aio_is_supported() { info!("Using asynchronous RAW disk file (aio)"); From 98fedac77f4c66caf5cf5f7b109741f3b6ff3acc Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Mon, 23 Mar 2026 13:31:50 -0700 Subject: [PATCH 0961/1893] 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 --- block/src/raw_sync.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index c045c5942d..0051c1b677 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -15,6 +15,7 @@ use crate::async_io::{ }; use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support, query_device_size}; +#[derive(Debug)] pub struct RawFileDiskSync { file: File, } From 00a355d273cf8edd767dabb51d5d4bfa684c424a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:34:57 -0700 Subject: [PATCH 0962/1893] 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 --- block/src/raw_sync.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 0051c1b677..23f2d10e54 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -13,7 +13,8 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support, query_device_size}; +use crate::error::{BlockError, BlockErrorKind, BlockResult}; +use crate::{DiskTopology, SECTOR_SIZE, disk_file, probe_sparse_support, query_device_size}; #[derive(Debug)] pub struct RawFileDiskSync { @@ -64,6 +65,14 @@ impl DiskFile for RawFileDiskSync { } } +impl disk_file::DiskSize for RawFileDiskSync { + fn logical_size(&self) -> BlockResult { + query_device_size(&self.file) + .map(|(logical_size, _)| logical_size) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e))) + } +} + pub struct RawFileSync { fd: RawFd, eventfd: EventFd, From 744c365faab91fb7ea1c6cd83b6b63703b2b5491 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:35:15 -0700 Subject: [PATCH 0963/1893] 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 --- block/src/raw_sync.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 23f2d10e54..35eb2fbf44 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -73,6 +73,14 @@ impl disk_file::DiskSize for RawFileDiskSync { } } +impl disk_file::PhysicalSize for RawFileDiskSync { + fn physical_size(&self) -> BlockResult { + query_device_size(&self.file) + .map(|(_, physical_size)| physical_size) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e))) + } +} + pub struct RawFileSync { fd: RawFd, eventfd: EventFd, From bd026ce4cb8cc5b6a75d9683fe98cea6760dd50d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:35:27 -0700 Subject: [PATCH 0964/1893] 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 --- block/src/raw_sync.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 35eb2fbf44..46488ff79f 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -81,6 +81,12 @@ impl disk_file::PhysicalSize for RawFileDiskSync { } } +impl disk_file::DiskFd for RawFileDiskSync { + fn fd(&self) -> BorrowedDiskFd<'_> { + BorrowedDiskFd::new(self.file.as_raw_fd()) + } +} + pub struct RawFileSync { fd: RawFd, eventfd: EventFd, From aa5aa6fe89fe38597c85084c4529ce5c6d6b782c Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:35:39 -0700 Subject: [PATCH 0965/1893] 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 --- block/src/raw_sync.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 46488ff79f..c6f30f53e8 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -87,6 +87,15 @@ impl disk_file::DiskFd for RawFileDiskSync { } } +impl disk_file::Geometry for RawFileDiskSync { + fn topology(&self) -> DiskTopology { + DiskTopology::probe(&self.file).unwrap_or_else(|_| { + warn!("Unable to get device topology. Using default topology"); + DiskTopology::default() + }) + } +} + pub struct RawFileSync { fd: RawFd, eventfd: EventFd, From fe39929a2ad751a7985828bbb240c4a9d8b7105e Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:35:52 -0700 Subject: [PATCH 0966/1893] 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 --- block/src/raw_sync.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index c6f30f53e8..5a6a65b361 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -96,6 +96,12 @@ impl disk_file::Geometry for RawFileDiskSync { } } +impl disk_file::SparseCapable for RawFileDiskSync { + fn supports_sparse_operations(&self) -> bool { + probe_sparse_support(&self.file) + } +} + pub struct RawFileSync { fd: RawFd, eventfd: EventFd, From 6b14d27f305ab4ba7aef6be082d90e14a8983d17 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:36:03 -0700 Subject: [PATCH 0967/1893] 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 --- block/src/raw_sync.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 5a6a65b361..59d842debc 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -102,6 +102,14 @@ impl disk_file::SparseCapable for RawFileDiskSync { } } +impl disk_file::Resizable for RawFileDiskSync { + fn resize(&mut self, size: u64) -> BlockResult<()> { + self.file + .set_len(size) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e))) + } +} + pub struct RawFileSync { fd: RawFd, eventfd: EventFd, From da72a3abfb0e3125aa51cb0584dd4d1023b75be5 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:36:15 -0700 Subject: [PATCH 0968/1893] 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 --- block/src/raw_sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 59d842debc..d25709d800 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -110,6 +110,8 @@ impl disk_file::Resizable for RawFileDiskSync { } } +impl disk_file::DiskFile for RawFileDiskSync {} + pub struct RawFileSync { fd: RawFd, eventfd: EventFd, From 662d350cf9c086f043d103a4a72a52c24b26ee86 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:36:39 -0700 Subject: [PATCH 0969/1893] 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 --- block/src/raw_sync.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index d25709d800..0e95fa8e2a 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -112,6 +112,23 @@ impl disk_file::Resizable for RawFileDiskSync { impl disk_file::DiskFile for RawFileDiskSync {} +impl disk_file::AsyncDiskFile for RawFileDiskSync { + fn try_clone(&self) -> BlockResult> { + let file = self + .file + .try_clone() + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?; + Ok(Box::new(RawFileDiskSync { file })) + } + + fn new_async_io(&self, _ring_depth: u32) -> BlockResult> { + let mut raw = RawFileSync::new(self.file.as_raw_fd()); + raw.alignment = + DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); + Ok(Box::new(raw) as Box) + } +} + pub struct RawFileSync { fd: RawFd, eventfd: EventFd, From afd018f9d1e4a769dc73f3333d82eb3b822b8630 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:32:30 -0700 Subject: [PATCH 0970/1893] 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 --- fuzz/fuzz_targets/block.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index 35d59c9850..abddc27b41 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -15,7 +15,6 @@ use std::path::PathBuf; use std::sync::Arc; use std::{ffi, io}; -use block::async_io::DiskFile; use block::disk_file::DiskBackend; use block::fcntl::LockGranularityChoice; use block::raw_sync::RawFileDiskSync; @@ -53,11 +52,10 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { // Create a virtio-block device backed by a synchronous raw file let shm = memfd_create(&ffi::CString::new("fuzz").unwrap(), 0).unwrap(); let disk_file: File = unsafe { File::from_raw_fd(shm) }; - let qcow_disk = Box::new(RawFileDiskSync::new(disk_file)) as Box; let queue_affinity = BTreeMap::new(); let mut block = Block::new( "tmp".to_owned(), - DiskBackend::Legacy(qcow_disk), + DiskBackend::Next(Box::new(RawFileDiskSync::new(disk_file))), PathBuf::from(""), false, false, From 4f44cd9ed3c06a345f0f87bd969fb755b5ca19bb Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:32:39 -0700 Subject: [PATCH 0971/1893] 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 --- vmm/src/device_manager.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 080fe9a3d6..342f99b373 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2763,9 +2763,7 @@ impl DeviceManager { DiskBackend::Legacy(Box::new(RawFileDiskAio::new(file)) as Box) } else { info!("Using synchronous RAW disk file"); - DiskBackend::Legacy( - Box::new(RawFileDiskSync::new(file)) as Box - ) + DiskBackend::Next(Box::new(RawFileDiskSync::new(file))) } } ImageType::Qcow2 => { From 38eb10d209544156e67e03f52346211359f0d49a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Tue, 24 Mar 2026 17:32:20 -0700 Subject: [PATCH 0972/1893] 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 --- block/src/raw_sync.rs | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 0e95fa8e2a..491ef0563d 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -10,9 +10,7 @@ use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; use log::warn; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{ - AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, -}; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::error::{BlockError, BlockErrorKind, BlockResult}; use crate::{DiskTopology, SECTOR_SIZE, disk_file, probe_sparse_support, query_device_size}; @@ -27,44 +25,6 @@ impl RawFileDiskSync { } } -impl DiskFile for RawFileDiskSync { - fn logical_size(&mut self) -> DiskFileResult { - Ok(query_device_size(&self.file) - .map_err(DiskFileError::Size)? - .0) - } - - fn physical_size(&mut self) -> DiskFileResult { - Ok(query_device_size(&self.file) - .map_err(DiskFileError::Size)? - .1) - } - - fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { - let mut raw = RawFileSync::new(self.file.as_raw_fd()); - raw.alignment = - DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); - Ok(Box::new(raw) as Box) - } - - fn topology(&mut self) -> DiskTopology { - if let Ok(topology) = DiskTopology::probe(&self.file) { - topology - } else { - warn!("Unable to get device topology. Using default topology"); - DiskTopology::default() - } - } - - fn supports_sparse_operations(&self) -> bool { - probe_sparse_support(&self.file) - } - - fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.file.as_raw_fd()) - } -} - impl disk_file::DiskSize for RawFileDiskSync { fn logical_size(&self) -> BlockResult { query_device_size(&self.file) From 15073edf081c03949344526ba1a15a8f7c6f16c9 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:01:55 +0100 Subject: [PATCH 0973/1893] 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 --- block/src/fixed_vhd_async.rs | 7 +++++-- vmm/src/device_manager.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index 6e858f74a5..141435420c 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -10,6 +10,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; +use crate::error::{BlockError, BlockResult, ErrorOp}; use crate::fixed_vhd::FixedVhd; use crate::raw_async::RawFileAsync; use crate::{BatchRequest, BlockBackend}; @@ -17,8 +18,10 @@ use crate::{BatchRequest, BlockBackend}; pub struct FixedVhdDiskAsync(FixedVhd); impl FixedVhdDiskAsync { - pub fn new(file: File) -> std::io::Result { - Ok(Self(FixedVhd::new(file)?)) + pub fn new(file: File) -> BlockResult { + Ok(Self( + FixedVhd::new(file).map_err(|e| BlockError::from(e).with_op(ErrorOp::Open))?, + )) } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 342f99b373..26bc24dbc0 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -569,7 +569,7 @@ pub enum DeviceManagerError { /// Failed to create FixedVhdDiskAsync #[error("Failed to create FixedVhdDiskAsync")] - CreateFixedVhdDiskAsync(#[source] io::Error), + CreateFixedVhdDiskAsync(#[source] BlockError), /// Failed to create FixedVhdDiskSync #[error("Failed to create FixedVhdDiskSync")] From d82b1101d9099867a1dad0778ff20e9677c9cb7b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:04:53 +0100 Subject: [PATCH 0974/1893] block: vhd: impl DiskSize for FixedVhdDiskAsync Delegate to infallible FixedVhd::logical_size(). Signed-off-by: Anatol Belski --- block/src/fixed_vhd_async.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index 141435420c..5f4c6d842d 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -13,8 +13,9 @@ use crate::async_io::{ use crate::error::{BlockError, BlockResult, ErrorOp}; use crate::fixed_vhd::FixedVhd; use crate::raw_async::RawFileAsync; -use crate::{BatchRequest, BlockBackend}; +use crate::{BatchRequest, BlockBackend, disk_file}; +#[derive(Debug)] pub struct FixedVhdDiskAsync(FixedVhd); impl FixedVhdDiskAsync { @@ -50,6 +51,12 @@ impl DiskFile for FixedVhdDiskAsync { } } +impl disk_file::DiskSize for FixedVhdDiskAsync { + fn logical_size(&self) -> BlockResult { + Ok(self.0.logical_size().unwrap()) + } +} + pub struct FixedVhdAsync { raw_file_async: RawFileAsync, size: u64, From 42af5913d95d60152ea53bd1b2db2610f58d01bb Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:05:38 +0100 Subject: [PATCH 0975/1893] block: vhd: impl PhysicalSize for FixedVhdDiskAsync Fix .unwrap() bug: use explicit GetFileMetadata match. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_async.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index 5f4c6d842d..5b15d52076 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -10,7 +10,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::error::{BlockError, BlockResult, ErrorOp}; +use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::fixed_vhd::FixedVhd; use crate::raw_async::RawFileAsync; use crate::{BatchRequest, BlockBackend, disk_file}; @@ -57,6 +57,17 @@ impl disk_file::DiskSize for FixedVhdDiskAsync { } } +impl disk_file::PhysicalSize for FixedVhdDiskAsync { + fn physical_size(&self) -> BlockResult { + self.0.physical_size().map_err(|e| match e { + crate::Error::GetFileMetadata(io) => { + BlockError::new(BlockErrorKind::Io, crate::Error::GetFileMetadata(io)) + } + _ => BlockError::new(BlockErrorKind::Io, e), + }) + } +} + pub struct FixedVhdAsync { raw_file_async: RawFileAsync, size: u64, From 2973987c811240b1e78844da8863099fe33e2936 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:06:38 +0100 Subject: [PATCH 0976/1893] block: vhd: impl DiskFd for FixedVhdDiskAsync Delegate to FixedVhd::as_raw_fd(). Signed-off-by: Anatol Belski --- block/src/fixed_vhd_async.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index 5b15d52076..b5b622e7ee 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -68,6 +68,12 @@ impl disk_file::PhysicalSize for FixedVhdDiskAsync { } } +impl disk_file::DiskFd for FixedVhdDiskAsync { + fn fd(&self) -> BorrowedDiskFd<'_> { + BorrowedDiskFd::new(self.0.as_raw_fd()) + } +} + pub struct FixedVhdAsync { raw_file_async: RawFileAsync, size: u64, From 1db64f511efb8e06e6734860e6716408317cb865 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:09:05 +0100 Subject: [PATCH 0977/1893] block: vhd: impl Geometry for FixedVhdDiskAsync Use default DiskTopology with 512 byte sectors. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_async.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index b5b622e7ee..3ce44e8dbd 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -74,6 +74,8 @@ impl disk_file::DiskFd for FixedVhdDiskAsync { } } +impl disk_file::Geometry for FixedVhdDiskAsync {} + pub struct FixedVhdAsync { raw_file_async: RawFileAsync, size: u64, From 0dece29fa8b33008ecf6bce916bc73f99bd4815a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:09:47 +0100 Subject: [PATCH 0978/1893] block: vhd: impl SparseCapable for FixedVhdDiskAsync Fixed VHD does not support sparse operations. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_async.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index 3ce44e8dbd..bba4f07de2 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -76,6 +76,8 @@ impl disk_file::DiskFd for FixedVhdDiskAsync { impl disk_file::Geometry for FixedVhdDiskAsync {} +impl disk_file::SparseCapable for FixedVhdDiskAsync {} + pub struct FixedVhdAsync { raw_file_async: RawFileAsync, size: u64, From df5d2d64b228c9a52a23713e10968d86463b3bf8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:10:08 +0100 Subject: [PATCH 0979/1893] block: vhd: impl Resizable for FixedVhdDiskAsync Fixed VHD does not support resize, return UnsupportedFeature. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_async.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index bba4f07de2..c265636c8d 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -78,6 +78,16 @@ impl disk_file::Geometry for FixedVhdDiskAsync {} impl disk_file::SparseCapable for FixedVhdDiskAsync {} +impl disk_file::Resizable for FixedVhdDiskAsync { + fn resize(&mut self, _size: u64) -> BlockResult<()> { + Err(BlockError::new( + BlockErrorKind::UnsupportedFeature, + DiskFileError::ResizeError(std::io::Error::other("resize not supported for fixed VHD")), + ) + .with_op(ErrorOp::Resize)) + } +} + pub struct FixedVhdAsync { raw_file_async: RawFileAsync, size: u64, From be6ce5b87858337d47afcf7449430514b023d967 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:10:21 +0100 Subject: [PATCH 0980/1893] block: vhd: impl DiskFile for FixedVhdDiskAsync Marker impl bundling DiskSize and Geometry supertraits. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_async.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index c265636c8d..8f6b37db19 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -88,6 +88,8 @@ impl disk_file::Resizable for FixedVhdDiskAsync { } } +impl disk_file::DiskFile for FixedVhdDiskAsync {} + pub struct FixedVhdAsync { raw_file_async: RawFileAsync, size: u64, From c0db1f61ac82ed7f16e5bdf11f3cca2b860e4497 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:11:02 +0100 Subject: [PATCH 0981/1893] 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 --- block/src/fixed_vhd_async.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index 8f6b37db19..9b768f80c6 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -90,6 +90,26 @@ impl disk_file::Resizable for FixedVhdDiskAsync { impl disk_file::DiskFile for FixedVhdDiskAsync {} +impl disk_file::AsyncDiskFile for FixedVhdDiskAsync { + fn try_clone(&self) -> BlockResult> { + Ok(Box::new(FixedVhdDiskAsync(self.0.clone()))) + } + + fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + Ok(Box::new( + FixedVhdAsync::new( + self.0.as_raw_fd(), + ring_depth, + self.0.logical_size().unwrap(), + ) + .map_err(|e| { + BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)) + .with_op(ErrorOp::Open) + })?, + )) + } +} + pub struct FixedVhdAsync { raw_file_async: RawFileAsync, size: u64, From b69bd219fa6284aefc341cf97b184097abdde875 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:11:36 +0100 Subject: [PATCH 0982/1893] vmm: Switch fixed VHD async to DiskBackend::Next Wire FixedVhdDiskAsync through the new composable trait system. Signed-off-by: Anatol Belski --- vmm/src/device_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 26bc24dbc0..4699d7231d 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2722,10 +2722,10 @@ impl DeviceManager { unreachable!("Checked in if statement above"); #[cfg(feature = "io_uring")] { - DiskBackend::Legacy(Box::new( + DiskBackend::Next(Box::new( FixedVhdDiskAsync::new(file) .map_err(DeviceManagerError::CreateFixedVhdDiskAsync)?, - ) as Box) + )) } } else { info!("Using synchronous fixed VHD disk file"); From 019aa528304ca70c979db343e45b43fce4f08cc4 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 17:12:14 +0100 Subject: [PATCH 0983/1893] block: vhd: Remove legacy async_io::DiskFile impl from FixedVhdDiskAsync No remaining consumers after switching to DiskBackend::Next. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_async.rs | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index 9b768f80c6..699fb2a494 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -7,9 +7,7 @@ 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::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::fixed_vhd::FixedVhd; use crate::raw_async::RawFileAsync; @@ -26,31 +24,6 @@ impl FixedVhdDiskAsync { } } -impl DiskFile for FixedVhdDiskAsync { - fn logical_size(&mut self) -> DiskFileResult { - Ok(self.0.logical_size().unwrap()) - } - - fn physical_size(&mut self) -> DiskFileResult { - Ok(self.0.physical_size().unwrap()) - } - - fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { - Ok(Box::new( - FixedVhdAsync::new( - self.0.as_raw_fd(), - ring_depth, - self.0.logical_size().unwrap(), - ) - .map_err(DiskFileError::NewAsyncIo)?, - ) as Box) - } - - fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.0.as_raw_fd()) - } -} - impl disk_file::DiskSize for FixedVhdDiskAsync { fn logical_size(&self) -> BlockResult { Ok(self.0.logical_size().unwrap()) From b8b32f59275b8123e2ff70afd1efd91fbc9526c0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 15:19:19 +0100 Subject: [PATCH 0984/1893] 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 --- block/src/vhdx_sync.rs | 9 ++++++--- vmm/src/device_manager.rs | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 0a0dc47bc2..a99f4f6f9c 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -12,7 +12,8 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::vhdx::{Result as VhdxResult, Vhdx}; +use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; +use crate::vhdx::Vhdx; use crate::{AsyncAdaptor, BlockBackend, Error}; pub struct VhdxDiskSync { @@ -28,9 +29,11 @@ pub struct VhdxDiskSync { } impl VhdxDiskSync { - pub fn new(f: File) -> VhdxResult { + pub fn new(f: File) -> BlockResult { Ok(VhdxDiskSync { - vhdx_file: Arc::new(Mutex::new(Vhdx::new(f)?)), + vhdx_file: Arc::new(Mutex::new(Vhdx::new(f).map_err(|e| { + BlockError::new(BlockErrorKind::Io, e).with_op(ErrorOp::Open) + })?)), }) } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 4699d7231d..4437cc8e94 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -42,7 +42,7 @@ use block::raw_sync::RawFileDiskSync; use block::vhdx_sync::VhdxDiskSync; use block::{ ImageType, block_aio_is_supported, block_io_uring_is_supported, detect_image_type, - open_disk_image, preallocate_disk, vhdx, + open_disk_image, preallocate_disk, }; #[cfg(feature = "io_uring")] use block::{fixed_vhd_async::FixedVhdDiskAsync, raw_async::RawFileDisk}; @@ -581,7 +581,7 @@ pub enum DeviceManagerError { /// Failed to create FixedVhdxDiskSync #[error("Failed to create FixedVhdxDiskSync")] - CreateFixedVhdxDiskSync(#[source] vhdx::VhdxError), + CreateFixedVhdxDiskSync(#[source] BlockError), /// Failed to add DMA mapping handler to virtio-mem device. #[error("Failed to add DMA mapping handler to virtio-mem device")] From 3164b66cb5b231e82ec7adc69f64ca09b1aa7cf0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:36:15 +0100 Subject: [PATCH 0985/1893] block: vhdx: impl DiskSize for VhdxDiskSync Delegates to Vhdx::virtual_disk_size() which is infallible. Signed-off-by: Anatol Belski --- block/src/vhdx_sync.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index a99f4f6f9c..7a863a8640 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -14,8 +14,9 @@ use crate::async_io::{ }; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::vhdx::Vhdx; -use crate::{AsyncAdaptor, BlockBackend, Error}; +use crate::{AsyncAdaptor, BlockBackend, Error, disk_file}; +#[derive(Debug)] pub struct VhdxDiskSync { // FIXME: The Mutex serializes all VHDX I/O operations across queues, which // is necessary for correctness but eliminates any parallelism benefit from @@ -38,6 +39,12 @@ impl VhdxDiskSync { } } +impl disk_file::DiskSize for VhdxDiskSync { + fn logical_size(&self) -> BlockResult { + Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) + } +} + impl DiskFile for VhdxDiskSync { fn logical_size(&mut self) -> DiskFileResult { Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) From 5578f329cfa813326e0ee369afa50f475bdde3e0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:37:26 +0100 Subject: [PATCH 0986/1893] block: vhdx: impl PhysicalSize for VhdxDiskSync Explicitly matches GetFileMetadata to preserve the original error type. Signed-off-by: Anatol Belski --- block/src/vhdx_sync.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 7a863a8640..d52e0667e4 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -45,6 +45,21 @@ impl disk_file::DiskSize for VhdxDiskSync { } } +impl disk_file::PhysicalSize for VhdxDiskSync { + fn physical_size(&self) -> BlockResult { + self.vhdx_file + .lock() + .unwrap() + .physical_size() + .map_err(|e| match e { + Error::GetFileMetadata(io) => { + BlockError::new(BlockErrorKind::Io, Error::GetFileMetadata(io)) + } + _ => BlockError::new(BlockErrorKind::Io, e), + }) + } +} + impl DiskFile for VhdxDiskSync { fn logical_size(&mut self) -> DiskFileResult { Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) From eca9e14ecbc18e9715910fdd844ae9d11c770592 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:39:53 +0100 Subject: [PATCH 0987/1893] block: vhdx: impl DiskFd for VhdxDiskSync Returns the raw fd from the inner Vhdx via mutex lock. Signed-off-by: Anatol Belski --- block/src/vhdx_sync.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index d52e0667e4..7a009bfdff 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -60,6 +60,12 @@ impl disk_file::PhysicalSize for VhdxDiskSync { } } +impl disk_file::DiskFd for VhdxDiskSync { + fn fd(&self) -> BorrowedDiskFd<'_> { + BorrowedDiskFd::new(self.vhdx_file.lock().unwrap().as_raw_fd()) + } +} + impl DiskFile for VhdxDiskSync { fn logical_size(&mut self) -> DiskFileResult { Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) From 62244f94da6383b628385bdd7e9716d01be9146b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:41:01 +0100 Subject: [PATCH 0988/1893] block: vhdx: impl Geometry for VhdxDiskSync Uses default topology. Signed-off-by: Anatol Belski --- block/src/vhdx_sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 7a009bfdff..9f1e952c23 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -66,6 +66,8 @@ impl disk_file::DiskFd for VhdxDiskSync { } } +impl disk_file::Geometry for VhdxDiskSync {} + impl DiskFile for VhdxDiskSync { fn logical_size(&mut self) -> DiskFileResult { Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) From 3e904d7a279095b48c905165678424bdccf4fef0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:41:37 +0100 Subject: [PATCH 0989/1893] block: vhdx: impl SparseCapable for VhdxDiskSync VHDX does not support sparse operations. Signed-off-by: Anatol Belski --- block/src/vhdx_sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 9f1e952c23..c3b04c9d1f 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -68,6 +68,8 @@ impl disk_file::DiskFd for VhdxDiskSync { impl disk_file::Geometry for VhdxDiskSync {} +impl disk_file::SparseCapable for VhdxDiskSync {} + impl DiskFile for VhdxDiskSync { fn logical_size(&mut self) -> DiskFileResult { Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) From ffdc8c49d19911fa471964de36dd2fb055682320 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:42:26 +0100 Subject: [PATCH 0990/1893] block: vhdx: impl Resizable for VhdxDiskSync Returns UnsupportedFeature, VHDX resize is not supported. Signed-off-by: Anatol Belski --- block/src/vhdx_sync.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index c3b04c9d1f..3eb8a91be3 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -70,6 +70,16 @@ impl disk_file::Geometry for VhdxDiskSync {} impl disk_file::SparseCapable for VhdxDiskSync {} +impl disk_file::Resizable for VhdxDiskSync { + fn resize(&mut self, _size: u64) -> BlockResult<()> { + Err(BlockError::new( + BlockErrorKind::UnsupportedFeature, + DiskFileError::ResizeError(std::io::Error::other("resize not supported for VHDX")), + ) + .with_op(ErrorOp::Resize)) + } +} + impl DiskFile for VhdxDiskSync { fn logical_size(&mut self) -> DiskFileResult { Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) From e99ad15939a835c1286e1270b14b2811a32e36a8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:42:48 +0100 Subject: [PATCH 0991/1893] block: vhdx: impl DiskFile for VhdxDiskSync Supertrait marker, all component traits already implemented. Signed-off-by: Anatol Belski --- block/src/vhdx_sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 3eb8a91be3..48d25714e3 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -80,6 +80,8 @@ impl disk_file::Resizable for VhdxDiskSync { } } +impl disk_file::DiskFile for VhdxDiskSync {} + impl DiskFile for VhdxDiskSync { fn logical_size(&mut self) -> DiskFileResult { Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) From cf9496376f84b9bb42c2afa16929d43af87d60a5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:43:37 +0100 Subject: [PATCH 0992/1893] block: vhdx: impl AsyncDiskFile for VhdxDiskSync try_clone() shares the Arc>. new_async_io() creates VhdxSync with a cloned Arc (no error path, infallible). Signed-off-by: Anatol Belski --- block/src/vhdx_sync.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 48d25714e3..8679978255 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -82,6 +82,18 @@ impl disk_file::Resizable for VhdxDiskSync { impl disk_file::DiskFile for VhdxDiskSync {} +impl disk_file::AsyncDiskFile for VhdxDiskSync { + fn try_clone(&self) -> BlockResult> { + Ok(Box::new(VhdxDiskSync { + vhdx_file: Arc::clone(&self.vhdx_file), + })) + } + + fn new_async_io(&self, _ring_depth: u32) -> BlockResult> { + Ok(Box::new(VhdxSync::new(Arc::clone(&self.vhdx_file)))) + } +} + impl DiskFile for VhdxDiskSync { fn logical_size(&mut self) -> DiskFileResult { Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) From b703043f7718864884eb1865b10c9db314463f7e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:44:44 +0100 Subject: [PATCH 0993/1893] vmm: Switch VHDX to DiskBackend::Next Wire VhdxDiskSync through DiskBackend::Next instead of Legacy. Signed-off-by: Anatol Belski --- vmm/src/device_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 4437cc8e94..ea66cda9da 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2784,10 +2784,10 @@ impl DeviceManager { } ImageType::Vhdx => { info!("Using synchronous VHDX disk file"); - DiskBackend::Legacy(Box::new( + DiskBackend::Next(Box::new( VhdxDiskSync::new(file) .map_err(DeviceManagerError::CreateFixedVhdxDiskSync)?, - ) as Box) + )) } ImageType::Unknown => unreachable!(), }; From 6e36932e90735c4cf1fc38cb314ce50e9d22f517 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 25 Mar 2026 16:46:46 +0100 Subject: [PATCH 0994/1893] block: vhdx: Remove legacy async_io::DiskFile impl from VhdxDiskSync All functionality now provided by composable disk_file traits. Signed-off-by: Anatol Belski --- block/src/vhdx_sync.rs | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 8679978255..0405554c1b 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -9,9 +9,7 @@ use std::sync::{Arc, Mutex}; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{ - AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, -}; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::vhdx::Vhdx; use crate::{AsyncAdaptor, BlockBackend, Error, disk_file}; @@ -94,30 +92,6 @@ impl disk_file::AsyncDiskFile for VhdxDiskSync { } } -impl DiskFile for VhdxDiskSync { - fn logical_size(&mut self) -> DiskFileResult { - Ok(self.vhdx_file.lock().unwrap().virtual_disk_size()) - } - - fn physical_size(&mut self) -> DiskFileResult { - self.vhdx_file.lock().unwrap().physical_size().map_err(|e| { - let io_inner = match e { - Error::GetFileMetadata(e) => e, - _ => unreachable!(), - }; - DiskFileError::Size(io_inner) - }) - } - - fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult> { - Ok(Box::new(VhdxSync::new(Arc::clone(&self.vhdx_file))) as Box) - } - - fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.vhdx_file.lock().unwrap().as_raw_fd()) - } -} - pub struct VhdxSync { vhdx_file: Arc>, eventfd: EventFd, From 24e90492806631670347afc13aa83d7462841e1d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 23 Mar 2026 23:30:08 +0100 Subject: [PATCH 0995/1893] 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 --- block/src/fixed_vhd_sync.rs | 7 +++++-- vmm/src/device_manager.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index ecb5e83ad0..bf6c431fe3 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -11,14 +11,17 @@ use crate::BlockBackend; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; +use crate::error::{BlockError, BlockResult, ErrorOp}; use crate::fixed_vhd::FixedVhd; use crate::raw_sync::RawFileSync; pub struct FixedVhdDiskSync(FixedVhd); impl FixedVhdDiskSync { - pub fn new(file: File) -> std::io::Result { - Ok(Self(FixedVhd::new(file)?)) + pub fn new(file: File) -> BlockResult { + Ok(Self( + FixedVhd::new(file).map_err(|e| BlockError::from(e).with_op(ErrorOp::Open))?, + )) } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index ea66cda9da..bdcd33602c 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -573,7 +573,7 @@ pub enum DeviceManagerError { /// Failed to create FixedVhdDiskSync #[error("Failed to create FixedVhdDiskSync")] - CreateFixedVhdDiskSync(#[source] io::Error), + CreateFixedVhdDiskSync(#[source] BlockError), /// Failed to create QcowDiskSync #[error("Failed to create QcowDiskSync")] From 713674454966eae112f28b943387514da750dfae Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 23 Mar 2026 23:54:02 +0100 Subject: [PATCH 0996/1893] 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 --- block/src/fixed_vhd_sync.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index bf6c431fe3..9ea036b90a 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -7,14 +7,15 @@ 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::error::{BlockError, BlockResult, ErrorOp}; use crate::fixed_vhd::FixedVhd; use crate::raw_sync::RawFileSync; +use crate::{BlockBackend, disk_file}; +#[derive(Debug)] pub struct FixedVhdDiskSync(FixedVhd); impl FixedVhdDiskSync { @@ -52,6 +53,12 @@ impl DiskFile for FixedVhdDiskSync { } } +impl disk_file::DiskSize for FixedVhdDiskSync { + fn logical_size(&self) -> BlockResult { + Ok(self.0.logical_size().unwrap()) + } +} + pub struct FixedVhdSync { raw_file_sync: RawFileSync, size: u64, From 266c5fc241116d5d196af63470970365937421ca Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 09:14:11 +0100 Subject: [PATCH 0997/1893] 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 --- block/src/fixed_vhd_sync.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index 9ea036b90a..15624eada0 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -10,7 +10,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::error::{BlockError, BlockResult, ErrorOp}; +use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::fixed_vhd::FixedVhd; use crate::raw_sync::RawFileSync; use crate::{BlockBackend, disk_file}; @@ -59,6 +59,17 @@ impl disk_file::DiskSize for FixedVhdDiskSync { } } +impl disk_file::PhysicalSize for FixedVhdDiskSync { + fn physical_size(&self) -> BlockResult { + self.0.physical_size().map_err(|e| match e { + crate::Error::GetFileMetadata(io) => { + BlockError::new(BlockErrorKind::Io, crate::Error::GetFileMetadata(io)) + } + _ => BlockError::new(BlockErrorKind::Io, e), + }) + } +} + pub struct FixedVhdSync { raw_file_sync: RawFileSync, size: u64, From dfa9a25fd8a32469122b27958907c57162dae5ea Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 09:33:16 +0100 Subject: [PATCH 0998/1893] block: vhd: impl DiskFd for FixedVhdDiskSync Delegate to FixedVhd::as_raw_fd() for the backing file descriptor. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_sync.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index 15624eada0..778ab16531 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -70,6 +70,12 @@ impl disk_file::PhysicalSize for FixedVhdDiskSync { } } +impl disk_file::DiskFd for FixedVhdDiskSync { + fn fd(&self) -> BorrowedDiskFd<'_> { + BorrowedDiskFd::new(self.0.as_raw_fd()) + } +} + pub struct FixedVhdSync { raw_file_sync: RawFileSync, size: u64, From 1a56cb3d0e7fdd896a80baedf1149ae92f7b4f50 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 13:53:34 +0100 Subject: [PATCH 0999/1893] block: vhd: impl Geometry for FixedVhdDiskSync Use default DiskTopology with 512byte sectors. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index 778ab16531..056c79818a 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -76,6 +76,8 @@ impl disk_file::DiskFd for FixedVhdDiskSync { } } +impl disk_file::Geometry for FixedVhdDiskSync {} + pub struct FixedVhdSync { raw_file_sync: RawFileSync, size: u64, From 74b0613c04d4fc7199b0474ff18e9bccf17a8d1c Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 13:59:55 +0100 Subject: [PATCH 1000/1893] block: vhd: impl SparseCapable for FixedVhdDiskSync Fixed VHD does not support sparse operations. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index 056c79818a..c66a945cb8 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -78,6 +78,8 @@ impl disk_file::DiskFd for FixedVhdDiskSync { impl disk_file::Geometry for FixedVhdDiskSync {} +impl disk_file::SparseCapable for FixedVhdDiskSync {} + pub struct FixedVhdSync { raw_file_sync: RawFileSync, size: u64, From 60af47c99ba877e8725e632f98a91647e228c11f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 14:01:34 +0100 Subject: [PATCH 1001/1893] block: vhd: impl Resizable for FixedVhdDiskSync Fixed VHD does not support resize, return UnsupportedFeature. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_sync.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index c66a945cb8..10836853a9 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -80,6 +80,16 @@ impl disk_file::Geometry for FixedVhdDiskSync {} impl disk_file::SparseCapable for FixedVhdDiskSync {} +impl disk_file::Resizable for FixedVhdDiskSync { + fn resize(&mut self, _size: u64) -> BlockResult<()> { + Err(BlockError::new( + BlockErrorKind::UnsupportedFeature, + DiskFileError::ResizeError(std::io::Error::other("resize not supported for fixed VHD")), + ) + .with_op(ErrorOp::Resize)) + } +} + pub struct FixedVhdSync { raw_file_sync: RawFileSync, size: u64, From ce1592d1df8fd9c36e850bd1dda314a36af318bf Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 14:57:14 +0100 Subject: [PATCH 1002/1893] block: vhd: impl DiskFile for FixedVhdDiskSync Marker impl bundling DiskSize and Geometry supertraits. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_sync.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index 10836853a9..92250fc8e7 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -90,6 +90,8 @@ impl disk_file::Resizable for FixedVhdDiskSync { } } +impl disk_file::DiskFile for FixedVhdDiskSync {} + pub struct FixedVhdSync { raw_file_sync: RawFileSync, size: u64, From cc2f878094255b7a1dabd077be66dc722edee371 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 15:11:10 +0100 Subject: [PATCH 1003/1893] 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 --- block/src/fixed_vhd_sync.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index 92250fc8e7..b28ce38689 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -92,6 +92,21 @@ impl disk_file::Resizable for FixedVhdDiskSync { impl disk_file::DiskFile for FixedVhdDiskSync {} +impl disk_file::AsyncDiskFile for FixedVhdDiskSync { + fn try_clone(&self) -> BlockResult> { + Ok(Box::new(FixedVhdDiskSync(self.0.clone()))) + } + + fn new_async_io(&self, _ring_depth: u32) -> BlockResult> { + Ok(Box::new( + FixedVhdSync::new(self.0.as_raw_fd(), self.0.logical_size().unwrap()).map_err(|e| { + BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)) + .with_op(ErrorOp::Open) + })?, + )) + } +} + pub struct FixedVhdSync { raw_file_sync: RawFileSync, size: u64, From c90f5a9e47f37f431f7793c633b16538a058bbc6 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 15:21:08 +0100 Subject: [PATCH 1004/1893] vmm: Switch fixed VHD sync to DiskBackend::Next Wire FixedVhdDiskSync through the new composable trait system. Signed-off-by: Anatol Belski --- vmm/src/device_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index bdcd33602c..72ce17b26a 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2729,10 +2729,10 @@ impl DeviceManager { } } else { info!("Using synchronous fixed VHD disk file"); - DiskBackend::Legacy(Box::new( + DiskBackend::Next(Box::new( FixedVhdDiskSync::new(file) .map_err(DeviceManagerError::CreateFixedVhdDiskSync)?, - ) as Box) + )) } } ImageType::Raw => { From 5ca6ff869fd467dda2013c514a5d7d00f4653bc1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 24 Mar 2026 16:37:17 +0100 Subject: [PATCH 1005/1893] block: vhd: Remove legacy async_io::DiskFile impl from FixedVhdDiskSync No remaining consumers after switching to DiskBackend::Next. Signed-off-by: Anatol Belski --- block/src/fixed_vhd_sync.rs | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index b28ce38689..14685522b3 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -7,9 +7,7 @@ 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::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::fixed_vhd::FixedVhd; use crate::raw_sync::RawFileSync; @@ -26,33 +24,6 @@ impl FixedVhdDiskSync { } } -impl DiskFile for FixedVhdDiskSync { - fn logical_size(&mut self) -> DiskFileResult { - Ok(self.0.logical_size().unwrap()) - } - - fn physical_size(&mut self) -> DiskFileResult { - 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> { - Ok(Box::new( - FixedVhdSync::new(self.0.as_raw_fd(), self.0.logical_size().unwrap()) - .map_err(DiskFileError::NewAsyncIo)?, - ) as Box) - } - - fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.0.as_raw_fd()) - } -} - impl disk_file::DiskSize for FixedVhdDiskSync { fn logical_size(&self) -> BlockResult { Ok(self.0.logical_size().unwrap()) From c943103f7530553afe689187035be32c4c5fd881 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 00:05:58 +0000 Subject: [PATCH 1006/1893] 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] --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 4 ++-- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 24 ++++++++++++------------ 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1ce72b0b6..ac44003adb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,9 +370,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "jobserver", @@ -1225,9 +1225,9 @@ checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "libc", ] @@ -2021,9 +2021,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "slab" @@ -2258,9 +2258,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -2885,18 +2885,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e22ace8382..67d5398f01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,9 +95,9 @@ libc = "0.2.183" log = "0.4.29" signal-hook = "0.4.3" thiserror = "2.0.18" -uuid = { version = "1.22.0" } +uuid = { version = "1.23.0" } wait-timeout = "0.2.1" -zerocopy = { version = "0.8.42", default-features = false } +zerocopy = { version = "0.8.48", default-features = false } [workspace.lints.clippy] # Any clippy lint (group) in alphabetical order: diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 516f41e834..afdc3403bf 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -35,7 +35,7 @@ vm-memory = { workspace = true, features = [ ] } vm-migration = { path = "../vm-migration" } vmm-sys-util = { workspace = true } -zerocopy = { version = "0.8.42", features = [ +zerocopy = { version = "0.8.48", features = [ "alloc", "derive", ], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 488476e9e9..8741ab8591 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -173,9 +173,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "jobserver", @@ -1134,9 +1134,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "smallvec" @@ -1261,9 +1261,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -1640,9 +1640,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] @@ -1737,18 +1737,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", From c52e15143950c67c2ecbe99f5e0aded159b3a74f Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 31 Mar 2026 06:05:14 +0000 Subject: [PATCH 1007/1893] 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 --- .github/dependabot.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 781912648b..2f3865fb1a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -37,6 +37,11 @@ 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: "kvm-bindings" From ff20f183647c602bf5e5028d99959e5547a7dd84 Mon Sep 17 00:00:00 2001 From: CMGS Date: Tue, 31 Mar 2026 06:12:21 +0000 Subject: [PATCH 1008/1893] 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 --- vmm/src/vm.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 430c9bdc74..c1e21a5ac9 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -3111,7 +3111,8 @@ impl Pausable for Vm { .valid_transition(new_state) .map_err(|e| MigratableError::Resume(anyhow!("Invalid transition: {e:?}")))?; - self.cpu_manager.lock().unwrap().resume()?; + // Restore KVM clock BEFORE vCPUs start running, so they see correct + // TSC/kvmclock from the first instruction after resume. #[cfg(target_arch = "x86_64")] { if let Some(clock) = &self.saved_clock { @@ -3128,6 +3129,7 @@ impl Pausable for Vm { } self.device_manager.lock().unwrap().resume()?; + self.cpu_manager.lock().unwrap().resume()?; // And we're back to the Running state. self.state = new_state; From 7832401816c5902b013630b05de6e5638b463e68 Mon Sep 17 00:00:00 2001 From: Souradeep Chakrabarti Date: Tue, 31 Mar 2026 06:18:27 +0000 Subject: [PATCH 1009/1893] 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 --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 --- hypervisor/src/mshv/mod.rs | 101 +++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 00cc4a6844..8623531c5b 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -13,6 +13,8 @@ use anyhow::anyhow; #[cfg(target_arch = "x86_64")] use arc_swap::ArcSwap; #[cfg(feature = "sev_snp")] +use log::error; +#[cfg(feature = "sev_snp")] use log::info; use log::{debug, warn}; use mshv_bindings::*; @@ -85,6 +87,12 @@ use crate::{CpuState, IoEventAddress, IrqRoutingEntry, MpState}; pub const PAGE_SHIFT: usize = 12; +// SVM exit codes not yet defined in mshv-bindings (AMD APM Vol 2, Table 15-7) +#[cfg(feature = "sev_snp")] +const SVM_EXITCODE_CPUID: u32 = 0x72; +#[cfg(feature = "sev_snp")] +const SVM_EXITCODE_MSR: u32 = 0x7c; + #[cfg(target_arch = "x86_64")] impl From for ClockData { fn from(d: MshvClockData) -> Self { @@ -1199,11 +1207,104 @@ impl cpu::Vcpu for MshvVcpu { // Clear the SW_EXIT_INFO1 register to indicate no error self.clear_swexit_info1()?; } + SVM_EXITCODE_CPUID => { + // SAFETY: Accessing fields from the mapped GHCB page + let cpuid_fn = unsafe { (*ghcb).rax } as u32; + // SAFETY: Accessing fields from the mapped GHCB page + let cpuid_idx = unsafe { (*ghcb).rcx } as u32; + // SAFETY: Accessing fields from the mapped GHCB page + let xcr0 = unsafe { (*ghcb).xfem }; + // SAFETY: Accessing fields from the mapped GHCB page + let xss = unsafe { (*ghcb).xss }; + debug!("GHCB CPUID: fn=0x{cpuid_fn:x} idx=0x{cpuid_idx:x}"); + + let cpuid_result = self + .fd + .get_cpuid_values(cpuid_fn, cpuid_idx, xcr0, xss) + .unwrap_or([0u32; 4]); + + set_svm_field_u64_ptr!(ghcb, rax, cpuid_result[0] as u64); + set_svm_field_u64_ptr!(ghcb, rbx, cpuid_result[1] as u64); + set_svm_field_u64_ptr!(ghcb, rcx, cpuid_result[2] as u64); + set_svm_field_u64_ptr!(ghcb, rdx, cpuid_result[3] as u64); + + self.clear_swexit_info1()?; + } + SVM_EXITCODE_MSR => { + let exit_info1 = + info.__bindgen_anon_2.__bindgen_anon_1.sw_exit_info1; + // SAFETY: Accessing fields from the mapped GHCB page + let msr_index = unsafe { (*ghcb).rcx } as u32; + let is_write = exit_info1 & 1 != 0; + + if is_write { + // SAFETY: Accessing fields from the mapped GHCB page + let msr_lo = unsafe { (*ghcb).rax } as u32; + // SAFETY: Accessing fields from the mapped GHCB page + let msr_hi = unsafe { (*ghcb).rdx } as u32; + let msr_val = ((msr_hi as u64) << 32) | (msr_lo as u64); + debug!( + "GHCB MSR WRITE: index=0x{msr_index:x} val=0x{msr_val:x}" + ); + let entry = msr_entry { + index: msr_index, + data: msr_val, + ..Default::default() + }; + let msr_entries = MsrEntries::from_entries(&[entry]) + .map_err(|e| { + cpu::HypervisorCpuError::RunVcpu(e.into()) + })?; + self.fd.set_msrs(&msr_entries).map_err(|e| { + cpu::HypervisorCpuError::RunVcpu(e.into()) + })?; + } else { + let entry = msr_entry { + index: msr_index, + ..Default::default() + }; + let mut msr_entries = MsrEntries::from_entries(&[entry]) + .map_err(|e| { + cpu::HypervisorCpuError::RunVcpu(e.into()) + })?; + self.fd.get_msrs(&mut msr_entries).map_err(|e| { + cpu::HypervisorCpuError::RunVcpu(e.into()) + })?; + let msr_slice = msr_entries.as_slice(); + if msr_slice.is_empty() { + return Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( + "get_msrs returned no entries for index 0x{msr_index:x}" + ))); + } + let msr_val = msr_slice[0].data; + debug!( + "GHCB MSR READ: index=0x{msr_index:x} val=0x{msr_val:x}" + ); + set_svm_field_u64_ptr!(ghcb, rax, msr_val & 0xFFFFFFFF); + set_svm_field_u64_ptr!(ghcb, rdx, msr_val >> 32); + } + + self.clear_swexit_info1()?; + } _ => { panic!("GHCB_INFO_NORMAL: Unhandled exit code: {exit_code:0x}") } } } + GHCB_INFO_SHUTDOWN_REQUEST => { + let ghcb_msr_val = { info.ghcb_msr }; + let reason_set = (ghcb_msr_val >> 12) & 0xf; + let reason_val = (ghcb_msr_val >> 16) & 0xff; + error!( + "GHCB_MSR_TERM_REQ: Guest terminated! \ + ghcb_msr=0x{ghcb_msr_val:x}, \ + reason_set={reason_set}, reason_val={reason_val}" + ); + return Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( + "Guest requested termination via GHCB_MSR_TERM_REQ \ + (reason_set={reason_set}, reason_val={reason_val})" + ))); + } _ => panic!("Unsupported VMGEXIT operation: {ghcb_op:0x}"), } From 082fdc4d076086e84ab0392389354681eb603c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=A4sser?= Date: Sat, 28 Mar 2026 12:35:34 +0000 Subject: [PATCH 1010/1893] virtio-devices: fix guest-triggerable panic via OOB queue_select MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src/transport/pci_common_config.rs | 50 ++++++++++++++++++- virtio-devices/src/transport/pci_device.rs | 2 +- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index dcd65f7bc1..c59c454b77 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -16,6 +16,7 @@ use virtio_queue::{Queue, QueueT}; use vm_migration::{MigratableError, Pausable, Snapshot, Snapshottable}; use vm_virtio::AccessPlatform; +use super::pci_device::VIRTQ_MSI_NO_VECTOR; use crate::VirtioDevice; pub const VIRTIO_PCI_COMMON_CONFIG_ID: &str = "virtio_pci_common_config"; @@ -249,7 +250,13 @@ impl VirtioPciCommonConfig { 0x12 => queues.len() as u16, // num_queues 0x16 => self.queue_select, 0x18 => self.with_queue(queues, |q| q.size()).unwrap_or(0), - 0x1a => self.msix_queues.lock().unwrap()[self.queue_select as usize], + 0x1a => self + .msix_queues + .lock() + .unwrap() + .get(usize::from(self.queue_select)) + .copied() + .unwrap_or(VIRTQ_MSI_NO_VECTOR), 0x1c => u16::from(self.with_queue(queues, |q| q.ready()).unwrap_or(false)), 0x1e => self.queue_select, // notify_off _ => { @@ -265,7 +272,16 @@ impl VirtioPciCommonConfig { 0x10 => self.msix_config.store(value, Ordering::Release), 0x16 => self.queue_select = value, 0x18 => self.with_queue_mut(queues, |q| q.set_size(value)), - 0x1a => self.msix_queues.lock().unwrap()[self.queue_select as usize] = value, + 0x1a => { + if let Some(entry) = self + .msix_queues + .lock() + .unwrap() + .get_mut(usize::from(self.queue_select)) + { + *entry = value; + } + } 0x1c => self.with_queue_mut(queues, |q| { let ready = value == 1; q.set_ready(ready); @@ -484,4 +500,34 @@ mod unit_tests { assert_eq!(read_back[0], 0xaa); assert_eq!(read_back[1], 0x55); } + + #[test] + fn oob_queue_select_does_not_panic() { + // Regression test: reading/writing queue_msix_vector (offset 0x1a) + // with an out-of-bounds queue_select must not panic. + let mut regs = VirtioPciCommonConfig { + access_platform: None, + driver_status: Arc::new(AtomicU8::new(0)), + config_generation: 0, + device_feature_select: 0, + driver_feature_select: 0, + queue_select: 0, + msix_config: Arc::new(AtomicU16::new(0)), + msix_queues: Arc::new(Mutex::new(vec![0; 1])), // only 1 queue + }; + + let dev = Arc::new(Mutex::new(DummyDevice(0))); + let mut queues = vec![Queue::new(256).unwrap()]; + + // Set queue_select to an out-of-bounds value. + regs.write(0x16, &[0xFF, 0xFF], &mut queues, dev.clone()); + + // Read queue_msix_vector — must not panic, should return VIRTQ_MSI_NO_VECTOR. + let mut read_back = vec![0x00, 0x00]; + regs.read(0x1a, &mut read_back, &queues, dev.clone()); + assert_eq!(LittleEndian::read_u16(&read_back), VIRTQ_MSI_NO_VECTOR); + + // Write queue_msix_vector — must not panic. + regs.write(0x1a, &[0xAB, 0xCD], &mut queues, dev); + } } diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 1eb1cc03f7..dd54472d39 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -45,7 +45,7 @@ use crate::{ }; /// Vector value used to disable MSI for a queue. -const VIRTQ_MSI_NO_VECTOR: u16 = 0xffff; +pub(super) const VIRTQ_MSI_NO_VECTOR: u16 = 0xffff; enum PciCapabilityType { Common = 1, From ca58685f4cbe634e1d1c55da16205d7666e137f8 Mon Sep 17 00:00:00 2001 From: CMGS Date: Tue, 31 Mar 2026 06:13:58 +0000 Subject: [PATCH 1011/1893] 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 --- vmm/src/cpu.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index f8fff2b299..4b15cffc31 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -2637,16 +2637,16 @@ impl Pausable for CpuManager { self.signal_vcpus() .map_err(|e| MigratableError::Pause(anyhow!("Error signalling vCPUs: {e}")))?; + // Notify all guests (including Hyper-V / Windows) that the clock was + // paused. KVM_KVMCLOCK_CTRL updates internal KVM state that affects + // both pvclock (Linux) and the Hyper-V TSC reference page, so it must + // be called unconditionally. #[cfg(all(feature = "kvm", target_arch = "x86_64"))] for vcpu in self.vcpus.iter() { let vcpu = vcpu.lock().unwrap(); - if !self.config.kvm_hyperv { - vcpu.vcpu.notify_guest_clock_paused().map_err(|e| { - MigratableError::Pause(anyhow!( - "Could not notify guest it has been paused {e:?}" - )) - })?; - } + vcpu.vcpu.notify_guest_clock_paused().map_err(|e| { + MigratableError::Pause(anyhow!("Could not notify guest it has been paused {e:?}")) + })?; } // The vCPU thread will change its paused state before parking, wait here for each From af1d69a9ddff03db3bdee85b299c53c1ea62a9ed Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 28 Mar 2026 14:11:55 +0100 Subject: [PATCH 1012/1893] 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 --- block/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/block/src/lib.rs b/block/src/lib.rs index d62e2717b2..912cf44011 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -610,6 +610,7 @@ impl Request { let mut discard_sector = [0u8; 8]; let mut discard_num_sectors = [0u8; 4]; + let mut discard_flags = [0u8; 4]; let sector_addr = data_addr.checked_add(DISCARD_WZ_SECTOR_OFFSET).unwrap(); mem.read_slice(&mut discard_sector, sector_addr) @@ -621,6 +622,17 @@ impl Request { mem.read_slice(&mut discard_num_sectors, num_sectors_addr) .map_err(ExecuteError::Read)?; + let flags_addr = data_addr.checked_add(DISCARD_WZ_FLAGS_OFFSET).unwrap(); + mem.read_slice(&mut discard_flags, flags_addr) + .map_err(ExecuteError::Read)?; + + let discard_flags = u32::from_le_bytes(discard_flags); + // Per virtio spec v1.2 reject discard if any flag is set, including unmap. + if discard_flags != 0 { + warn!("Unsupported flags {discard_flags:#x} in discard request"); + return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_DISCARD)); + } + let discard_sector = u64::from_le_bytes(discard_sector); if discard_sector == 0 && disable_sector0_writes { From 8ca5210603d9c7ad67ccdc31ab66b2c22fce80d1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 28 Mar 2026 14:36:15 +0100 Subject: [PATCH 1013/1893] 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 --- block/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/lib.rs b/block/src/lib.rs index 912cf44011..d02ab5469d 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -691,7 +691,13 @@ impl Request { let wz_sector = u64::from_le_bytes(wz_sector); let wz_num_sectors = u32::from_le_bytes(wz_num_sectors); + let wz_flags = u32::from_le_bytes(wz_flags); + // Per virtio spec v1.2 reject write zeroes if any unknown flag is set. + if (wz_flags & !VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP) != 0 { + warn!("Unsupported flags {wz_flags:#x} in write zeroes request"); + return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_WRITE_ZEROES)); + } let wz_offset = wz_sector * SECTOR_SIZE; if wz_offset == 0 && disable_sector0_writes { From e4e2a37afabc764a9071b01f3d60ef7ced49cd7a Mon Sep 17 00:00:00 2001 From: CMGS Date: Tue, 31 Mar 2026 08:50:04 +0000 Subject: [PATCH 1014/1893] 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 --- cloud-hypervisor/tests/integration.rs | 61 +++++++++++++-------------- virtio-devices/src/block.rs | 9 ++-- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index bac62af116..5ae554981c 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -4564,7 +4564,6 @@ mod common_parallel { #[test] fn test_virtio_block_sparse_off_qcow2() { const TEST_DISK_SIZE: &str = "2G"; - const CLUSTER_SIZE_BYTES: u64 = 64 * 1024; let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); @@ -4618,37 +4617,36 @@ mod common_parallel { 1 ); - let mut current_offset_kb = 1024; - - for &size_kb in BLOCK_DISCARD_TEST_SIZES_KB.iter() { - guest - .ssh_command(&format!( - "sudo dd if=/dev/urandom of=/dev/vdc bs=1K count={size_kb} seek={current_offset_kb} oflag=direct" - )) - .unwrap(); - - guest.ssh_command("sync").unwrap(); - - guest - .ssh_command(&format!( - "sudo blkdiscard -o {} -l {} /dev/vdc", - current_offset_kb * 1024, - size_kb * 1024 - )) - .unwrap(); - - guest.ssh_command("sync").unwrap(); + // With sparse=off, DISCARD should NOT be advertised. + // blkdiscard is expected to fail. + let discard_result = + guest.ssh_command("sudo blkdiscard -o 1048576 -l 1048576 /dev/vdc 2>&1; echo $?"); + let exit_code = discard_result + .unwrap() + .trim() + .lines() + .last() + .unwrap_or("1") + .parse::() + .unwrap_or(1); + assert_ne!( + exit_code, 0, + "blkdiscard should fail with sparse=off (DISCARD not advertised)" + ); - // Verify VM sees zeros in discarded region - assert_guest_disk_region_is_zero( - &guest, - "/dev/vdc", - current_offset_kb * 1024, - size_kb * 1024, - ); + // WRITE_ZEROES should still work via blkdiscard --zeroout + guest + .ssh_command( + "sudo dd if=/dev/urandom of=/dev/vdc bs=1K count=64 seek=1024 oflag=direct", + ) + .unwrap(); + guest.ssh_command("sync").unwrap(); + guest + .ssh_command("sudo blkdiscard -z -o 1048576 -l 65536 /dev/vdc") + .unwrap(); + guest.ssh_command("sync").unwrap(); - current_offset_kb += size_kb + 64; - } + assert_guest_disk_region_is_zero(&guest, "/dev/vdc", 1048576, 65536); }); kill_child(&mut child); @@ -4659,9 +4657,10 @@ mod common_parallel { handle_child_output(r, &output); + // WRITE_ZEROES should still produce zero-flagged regions assert!( zero_regions_after > zero_regions_before, - "Expected zero-flagged regions to increase with sparse=off: before={zero_regions_before}, after={zero_regions_after}" + "Expected zero-flagged regions to increase via WRITE_ZEROES: before={zero_regions_before}, after={zero_regions_after}" ); disk_check_consistency(test_disk_path, None); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 8f919e3e04..1080b71bcb 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -778,13 +778,14 @@ impl Block { | (1u64 << VIRTIO_RING_F_INDIRECT_DESC); // When backend supports sparse operations: - // - Always advertise WRITE_ZEROES - // - Advertise DISCARD only if sparse=true OR format supports marking - // clusters as zero without deallocating + // - Always advertise WRITE_ZEROES (safe for all drivers) + // - Advertise DISCARD only when sparse=true, since DISCARD + // deallocates space via punch_hole and should require + // explicit user opt in. let mut discard_supported = false; if disk_image.supports_sparse_operations() { avail_features |= 1u64 << VIRTIO_BLK_F_WRITE_ZEROES; - if sparse || disk_image.supports_zero_flag() { + if sparse { avail_features |= 1u64 << VIRTIO_BLK_F_DISCARD; discard_supported = true; } From 9f980969fced6479ad5b2de94ca1699c3f409b87 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 31 Mar 2026 22:50:25 +0200 Subject: [PATCH 1015/1893] 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 --- block/src/lib.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index d02ab5469d..6d093daca4 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -169,6 +169,8 @@ pub enum ExecuteError { WriteAll(#[source] io::Error), #[error("Unsupported request: {0}")] Unsupported(u32), + #[error("Unsupported flags {flags:#x} for request type {request_type}")] + UnsupportedFlags { request_type: u32, flags: u32 }, #[error("Failed to submit io uring")] SubmitIoUring(#[source] io::Error), #[error("Failed to get guest address")] @@ -199,6 +201,7 @@ impl ExecuteError { ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR, ExecuteError::WriteAll(_) => VIRTIO_BLK_S_IOERR, ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP, + ExecuteError::UnsupportedFlags { .. } => VIRTIO_BLK_S_UNSUPP, ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR, ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR, ExecuteError::AsyncRead(_) => VIRTIO_BLK_S_IOERR, @@ -630,7 +633,10 @@ impl Request { // Per virtio spec v1.2 reject discard if any flag is set, including unmap. if discard_flags != 0 { warn!("Unsupported flags {discard_flags:#x} in discard request"); - return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_DISCARD)); + return Err(ExecuteError::UnsupportedFlags { + request_type: VIRTIO_BLK_T_DISCARD, + flags: discard_flags, + }); } let discard_sector = u64::from_le_bytes(discard_sector); @@ -696,7 +702,10 @@ impl Request { // Per virtio spec v1.2 reject write zeroes if any unknown flag is set. if (wz_flags & !VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP) != 0 { warn!("Unsupported flags {wz_flags:#x} in write zeroes request"); - return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_WRITE_ZEROES)); + return Err(ExecuteError::UnsupportedFlags { + request_type: VIRTIO_BLK_T_WRITE_ZEROES, + flags: wz_flags, + }); } let wz_offset = wz_sector * SECTOR_SIZE; From 820140930a5ccbd59d9bfeb9e310d4ef5ae49141 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Mon, 16 Mar 2026 17:55:52 +0000 Subject: [PATCH 1016/1893] vmm: interrupt: Reduce visibility of internal types and methods Signed-off-by: Bo Chen --- vmm/src/interrupt.rs | 52 ++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/vmm/src/interrupt.rs b/vmm/src/interrupt.rs index 0995d83567..80ddd46ae8 100644 --- a/vmm/src/interrupt.rs +++ b/vmm/src/interrupt.rs @@ -17,7 +17,7 @@ use vm_device::interrupt::{ use vmm_sys_util::eventfd::EventFd; /// Reuse std::io::Result to simplify interoperability among crates. -pub type Result = std::io::Result; +type Result = std::io::Result; struct InterruptRoute { gsi: u32, @@ -26,11 +26,11 @@ struct InterruptRoute { } impl InterruptRoute { - pub fn new(allocator: &mut SystemAllocator) -> Result { + fn new(allocator: &mut SystemAllocator) -> Result { Self::new_with_fd(allocator, Some(EventFd::new(libc::EFD_NONBLOCK)?)) } - pub fn new_with_fd(allocator: &mut SystemAllocator, irq_fd: Option) -> Result { + fn new_with_fd(allocator: &mut SystemAllocator, irq_fd: Option) -> Result { let gsi = allocator .allocate_gsi() .ok_or_else(|| io::Error::other("Failed allocating new GSI"))?; @@ -42,7 +42,7 @@ impl InterruptRoute { }) } - pub fn enable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { + fn enable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { if !self.registered { if let Some(ref irq_fd) = self.irq_fd { vm.register_irqfd(irq_fd, self.gsi) @@ -56,7 +56,7 @@ impl InterruptRoute { Ok(()) } - pub fn disable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { + fn disable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { if self.registered { if let Some(ref irq_fd) = self.irq_fd { vm.unregister_irqfd(irq_fd, self.gsi) @@ -70,14 +70,14 @@ impl InterruptRoute { Ok(()) } - pub fn trigger(&mut self) -> Result<()> { + fn trigger(&mut self) -> Result<()> { match self.irq_fd { Some(ref fd) => fd.write(1), None => Ok(()), } } - pub fn notifier(&mut self) -> Option { + fn notifier(&mut self) -> Option { Some( self.irq_fd .as_ref()? @@ -90,11 +90,7 @@ impl InterruptRoute { // will use it. Use #[allow(dead_code)] to suppress a compiler // warning. #[allow(dead_code)] - pub fn set_notifier( - &mut self, - eventfd: Option, - vm: &dyn hypervisor::Vm, - ) -> Result<()> { + fn set_notifier(&mut self, eventfd: Option, vm: &dyn hypervisor::Vm) -> Result<()> { let old_irqfd = core::mem::replace(&mut self.irq_fd, eventfd); if self.registered { if let Some(ref irq_fd) = self.irq_fd { @@ -114,18 +110,30 @@ impl InterruptRoute { } } -pub struct RoutingEntry { +struct RoutingEntry { route: IrqRoutingEntry, masked: bool, } -pub struct MsiInterruptGroup { +struct MsiInterruptGroup { vm: Arc, gsi_msi_routes: Arc>>, irq_routes: HashMap>, } impl MsiInterruptGroup { + fn new( + vm: Arc, + gsi_msi_routes: Arc>>, + irq_routes: HashMap>, + ) -> Self { + MsiInterruptGroup { + vm, + gsi_msi_routes, + irq_routes, + } + } + fn set_gsi_routes(&self, routes: &HashMap) -> Result<()> { let mut entry_vec: Vec = Vec::new(); for (_, entry) in routes.iter() { @@ -142,20 +150,6 @@ impl MsiInterruptGroup { } } -impl MsiInterruptGroup { - fn new( - vm: Arc, - gsi_msi_routes: Arc>>, - irq_routes: HashMap>, - ) -> Self { - MsiInterruptGroup { - vm, - gsi_msi_routes, - irq_routes, - } - } -} - impl InterruptSourceGroup for MsiInterruptGroup { fn enable(&self) -> Result<()> { for (_, route) in self.irq_routes.iter() { @@ -253,7 +247,7 @@ impl InterruptSourceGroup for MsiInterruptGroup { } } -pub struct LegacyUserspaceInterruptGroup { +struct LegacyUserspaceInterruptGroup { ioapic: Arc>, irq: u32, } From 0686045290204c59661f46f987e2cd60c5f40ee7 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Fri, 27 Mar 2026 23:34:16 +0000 Subject: [PATCH 1017/1893] 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 --- vmm/src/interrupt.rs | 79 +++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/vmm/src/interrupt.rs b/vmm/src/interrupt.rs index 80ddd46ae8..f08aaab7fa 100644 --- a/vmm/src/interrupt.rs +++ b/vmm/src/interrupt.rs @@ -20,32 +20,49 @@ use vmm_sys_util::eventfd::EventFd; type Result = std::io::Result; struct InterruptRoute { - gsi: u32, + gsi: Option, irq_fd: Option, registered: bool, } impl InterruptRoute { - fn new(allocator: &mut SystemAllocator) -> Result { - Self::new_with_fd(allocator, Some(EventFd::new(libc::EFD_NONBLOCK)?)) + fn new() -> Result { + // The irq_fd must be created eagerly because external components + // (say, VFIO) need the fd at device initialization time via notifier(). + Self::new_with_fd(Some(EventFd::new(libc::EFD_NONBLOCK)?)) } - fn new_with_fd(allocator: &mut SystemAllocator, irq_fd: Option) -> Result { - let gsi = allocator - .allocate_gsi() - .ok_or_else(|| io::Error::other("Failed allocating new GSI"))?; - + fn new_with_fd(irq_fd: Option) -> Result { Ok(InterruptRoute { - gsi, + gsi: None, irq_fd, registered: false, }) } + fn allocate_gsi(&mut self, allocator: &mut SystemAllocator) -> Result { + match self.gsi { + Some(existing) => Ok(existing), + None => { + let new_gsi = allocator + .allocate_gsi() + .ok_or_else(|| io::Error::other("Failed allocating new GSI"))?; + self.gsi = Some(new_gsi); + Ok(new_gsi) + } + } + } + fn enable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { + let gsi = match self.gsi { + Some(gsi) => gsi, + // Do nothing if no GSI was ever allocated for this route, which means the interrupt is still masked. + None => return Ok(()), + }; + if !self.registered { if let Some(ref irq_fd) = self.irq_fd { - vm.register_irqfd(irq_fd, self.gsi) + vm.register_irqfd(irq_fd, gsi) .map_err(|e| io::Error::other(format!("Failed registering irq_fd: {e}")))?; } @@ -57,9 +74,15 @@ impl InterruptRoute { } fn disable(&mut self, vm: &dyn hypervisor::Vm) -> Result<()> { + let gsi = match self.gsi { + Some(gsi) => gsi, + // Do nothing if no GSI was ever allocated for this route, which means the interrupt is still masked. + None => return Ok(()), + }; + if self.registered { if let Some(ref irq_fd) = self.irq_fd { - vm.unregister_irqfd(irq_fd, self.gsi) + vm.unregister_irqfd(irq_fd, gsi) .map_err(|e| io::Error::other(format!("Failed unregistering irq_fd: {e}")))?; } @@ -93,14 +116,17 @@ impl InterruptRoute { fn set_notifier(&mut self, eventfd: Option, vm: &dyn hypervisor::Vm) -> Result<()> { let old_irqfd = core::mem::replace(&mut self.irq_fd, eventfd); if self.registered { + // A registered route must have a GSI allocated, since enable() + // only sets registered=true after using a valid GSI. + let gsi = self.gsi.expect("registered route has no GSI allocated"); if let Some(ref irq_fd) = self.irq_fd { - vm.register_irqfd(irq_fd, self.gsi) + vm.register_irqfd(irq_fd, gsi) .map_err(|e| io::Error::other(format!("Failed registering irq_fd: {e}")))?; } // If the irqfd cannot be unregistered, what to do? Spin? // Returning an error isn't helpful as the new irqfd is already registered. if let Some(old_irq_fd) = old_irqfd { - match vm.unregister_irqfd(&old_irq_fd, self.gsi) { + match vm.unregister_irqfd(&old_irq_fd, gsi) { Ok(()) => {} Err(e) => log::warn!("Failed unregistering old irqfd: {e}"), } @@ -119,6 +145,7 @@ struct MsiInterruptGroup { vm: Arc, gsi_msi_routes: Arc>>, irq_routes: HashMap>, + allocator: Arc>, } impl MsiInterruptGroup { @@ -126,11 +153,13 @@ impl MsiInterruptGroup { vm: Arc, gsi_msi_routes: Arc>>, irq_routes: HashMap>, + allocator: Arc>, ) -> Self { MsiInterruptGroup { vm, gsi_msi_routes, irq_routes, + allocator, } } @@ -194,8 +223,20 @@ impl InterruptSourceGroup for MsiInterruptGroup { ) -> Result<()> { if let Some(route) = self.irq_routes.get(&index) { let mut route = route.lock().unwrap(); + let gsi = if masked { + match route.gsi { + Some(gsi) => gsi, + // No update needed if masked and no GSI was ever allocated + None => return Ok(()), + } + } else { + // Allocate a GSI when the interrupt vector is first unmasked + let mut allocator = self.allocator.lock().unwrap(); + route.allocate_gsi(&mut allocator)? + }; + let entry = RoutingEntry { - route: self.vm.make_routing_entry(route.gsi, &config), + route: self.vm.make_routing_entry(gsi, &config), masked, }; @@ -208,7 +249,7 @@ impl InterruptSourceGroup for MsiInterruptGroup { } let mut routes = self.gsi_msi_routes.lock().unwrap(); - routes.insert(route.gsi, entry); + routes.insert(gsi, entry); if set_gsi { self.set_gsi_routes(&routes)?; } @@ -338,17 +379,17 @@ impl MsiInterruptManager { &self, config: ::GroupConfig, ) -> Result { - let mut allocator = self.allocator.lock().unwrap(); let mut irq_routes: HashMap> = HashMap::with_capacity(config.count as usize); for i in config.base..config.base + config.count { - irq_routes.insert(i, Mutex::new(InterruptRoute::new(&mut allocator)?)); + irq_routes.insert(i, Mutex::new(InterruptRoute::new()?)); } Ok(MsiInterruptGroup::new( self.vm.clone(), self.gsi_msi_routes.clone(), irq_routes, + self.allocator.clone(), )) } } @@ -357,17 +398,17 @@ impl InterruptManager for MsiInterruptManager { type GroupConfig = MsiIrqGroupConfig; fn create_group(&self, config: Self::GroupConfig) -> Result> { - let mut allocator = self.allocator.lock().unwrap(); let mut irq_routes: HashMap> = HashMap::with_capacity(config.count as usize); for i in config.base..config.base + config.count { - irq_routes.insert(i, Mutex::new(InterruptRoute::new(&mut allocator)?)); + irq_routes.insert(i, Mutex::new(InterruptRoute::new()?)); } Ok(Arc::new(MsiInterruptGroup::new( self.vm.clone(), self.gsi_msi_routes.clone(), irq_routes, + self.allocator.clone(), ))) } From db93c6fdc7dfc3786128d2aaf2086fe49bc0d858 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Tue, 31 Mar 2026 12:03:20 +0200 Subject: [PATCH 1018/1893] 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 Co-authored-by: Julian Schindel On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler --- pci/src/configuration.rs | 9 ++++++++- pci/src/device.rs | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index 4dbd04f122..f506017b26 100644 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -422,6 +422,9 @@ pub struct PciConfigurationState { rom_bar_used: bool, last_capability: Option<(usize, usize)>, msix_cap_reg_idx: Option, + // Preserve deferred BAR moves across snapshot and restore. + #[serde(default)] + pending_bar_reprogram: Vec, } /// Contains the configuration space of a PCI node. @@ -557,6 +560,7 @@ impl PciConfiguration { rom_bar_used, last_capability, msix_cap_reg_idx, + pending_bar_reprogram, ) = if let Some(state) = state { ( state.registers.try_into().unwrap(), @@ -567,6 +571,7 @@ impl PciConfiguration { state.rom_bar_used, state.last_capability, state.msix_cap_reg_idx, + state.pending_bar_reprogram, ) } else { let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS]; @@ -606,6 +611,7 @@ impl PciConfiguration { false, None, None, + Vec::new(), ) }; @@ -619,7 +625,7 @@ impl PciConfiguration { last_capability, msix_cap_reg_idx, msix_config, - pending_bar_reprogram: Vec::new(), + pending_bar_reprogram, } } @@ -633,6 +639,7 @@ impl PciConfiguration { rom_bar_used: self.rom_bar_used, last_capability: self.last_capability, msix_cap_reg_idx: self.msix_cap_reg_idx, + pending_bar_reprogram: self.pending_bar_reprogram.clone(), } } diff --git a/pci/src/device.rs b/pci/src/device.rs index 3c5b3315f8..3a23ea7772 100644 --- a/pci/src/device.rs +++ b/pci/src/device.rs @@ -8,6 +8,7 @@ use std::any::Any; use std::sync::{Arc, Barrier, Mutex}; use std::{io, result}; +use serde::{Deserialize, Serialize}; use thiserror::Error; use vm_allocator::{AddressAllocator, SystemAllocator}; use vm_device::Resource; @@ -35,7 +36,7 @@ pub enum Error { } pub type Result = std::result::Result; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct BarReprogrammingParams { pub old_base: u64, pub new_base: u64, From a0bbef3a761bd0824c447d06d02b79dff4ab534c Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 31 Mar 2026 04:31:35 -0700 Subject: [PATCH 1019/1893] 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 --- virtio-devices/src/vhost_user/blk.rs | 62 +++++++------- virtio-devices/src/vhost_user/fs.rs | 62 +++++++------- .../src/vhost_user/generic_vhost_user.rs | 62 +++++++------- virtio-devices/src/vhost_user/mod.rs | 15 ++-- virtio-devices/src/vhost_user/net.rs | 83 ++++++++++--------- 5 files changed, 145 insertions(+), 139 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 203012b8ec..f6e9a46232 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -39,7 +39,6 @@ struct BackendReqHandler {} impl VhostUserFrontendReqHandler for BackendReqHandler {} pub struct Blk { - common: VirtioCommon, vu_common: VhostUserCommon, id: String, config: VirtioBlockConfig, @@ -170,17 +169,17 @@ impl Blk { }; Ok(Blk { - common: VirtioCommon { - device_type: VirtioDeviceType::Block as u32, - queue_sizes: vec![vu_cfg.queue_size; num_queues], - avail_features, - acked_features, - paused_sync: Some(Arc::new(Barrier::new(2))), - min_queues: DEFAULT_QUEUE_NUMBER as u16, - paused: Arc::new(AtomicBool::new(paused)), - ..Default::default() - }, vu_common: VhostUserCommon { + virtio_common: VirtioCommon { + device_type: VirtioDeviceType::Block as u32, + queue_sizes: vec![vu_cfg.queue_size; num_queues], + avail_features, + acked_features, + paused_sync: Some(Arc::new(Barrier::new(2))), + min_queues: DEFAULT_QUEUE_NUMBER as u16, + paused: Arc::new(AtomicBool::new(paused)), + ..Default::default() + }, vu: Some(Arc::new(Mutex::new(vu))), acked_protocol_features, socket_path: vu_cfg.socket, @@ -199,18 +198,18 @@ impl Blk { } fn state(&self) -> std::result::Result { - self.vu_common.state(&self.common, self.config) + self.vu_common.state(self.config) } } impl Drop for Blk { fn drop(&mut self) { - if let Some(kill_evt) = self.common.kill_evt.take() + if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() && let Err(e) = kill_evt.write(1) { error!("failed to kill vhost-user-blk: {e:?}"); } - self.common.wait_for_epoll_threads(); + self.vu_common.virtio_common.wait_for_epoll_threads(); if let Some(thread) = self.epoll_thread.take() && let Err(e) = thread.join() { @@ -221,15 +220,15 @@ impl Drop for Blk { impl VirtioDevice for Blk { fn device_type(&self) -> u32 { - self.common.device_type + self.vu_common.virtio_common.device_type } fn queue_max_sizes(&self) -> &[u16] { - &self.common.queue_sizes + &self.vu_common.virtio_common.queue_sizes } fn features(&self) -> u64 { - let mut features = self.common.avail_features; + let mut features = self.vu_common.virtio_common.avail_features; if self.iommu { features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } @@ -237,7 +236,7 @@ impl VirtioDevice for Blk { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value); + self.vu_common.virtio_common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -278,27 +277,29 @@ impl VirtioDevice for Blk { queues, .. } = context; - self.common.activate(&queues, interrupt_cb.clone())?; + self.vu_common + .virtio_common + .activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); let backend_req_handler: Option> = None; // Run a dedicated thread for handling potential reconnections with // the backend. - let (kill_evt, pause_evt) = self.common.dup_eventfds(); + let (kill_evt, pause_evt) = self.vu_common.virtio_common.dup_eventfds(); let mut handler = self.vu_common.activate( mem, &queues, interrupt_cb, - self.common.acked_features, + self.vu_common.virtio_common.acked_features, backend_req_handler, kill_evt, pause_evt, )?; - let paused = self.common.paused.clone(); - let paused_sync = self.common.paused_sync.clone(); + let paused = self.vu_common.virtio_common.paused.clone(); + let paused_sync = self.vu_common.virtio_common.paused_sync.clone(); let mut epoll_threads = Vec::new(); @@ -317,8 +318,8 @@ impl VirtioDevice for Blk { fn reset(&mut self) -> Option> { // We first must resume the virtio thread if it was paused. - if self.common.pause_evt.take().is_some() { - self.common.resume().ok()?; + if self.vu_common.virtio_common.pause_evt.take().is_some() { + self.vu_common.virtio_common.resume().ok()?; } if let Some(vu) = &self.vu_common.vu @@ -328,7 +329,7 @@ impl VirtioDevice for Blk { return None; } - if let Some(kill_evt) = self.common.kill_evt.take() { + if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { // Ignore the result because there is nothing we can do about it. let _ = kill_evt.write(1); } @@ -336,7 +337,7 @@ impl VirtioDevice for Blk { event!("virtio-device", "reset", "id", &self.id); // Return the interrupt - Some(self.common.interrupt_cb.take().unwrap()) + Some(self.vu_common.virtio_common.interrupt_cb.take().unwrap()) } fn shutdown(&mut self) { @@ -354,11 +355,11 @@ impl VirtioDevice for Blk { impl Pausable for Blk { fn pause(&mut self) -> result::Result<(), MigratableError> { self.vu_common.pause()?; - self.common.pause() + self.vu_common.virtio_common.pause() } fn resume(&mut self) -> result::Result<(), MigratableError> { - self.common.resume()?; + self.vu_common.virtio_common.resume()?; if let Some(epoll_thread) = &self.epoll_thread { epoll_thread.thread().unpark(); @@ -397,7 +398,6 @@ impl Migratable for Blk { } fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> { - self.vu_common - .complete_migration(self.common.kill_evt.take()) + self.vu_common.complete_migration() } } diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 1b8edfe6bd..3f982ff299 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -59,7 +59,6 @@ impl Default for VirtioFsConfig { unsafe impl ByteValued for VirtioFsConfig {} pub struct Fs { - common: VirtioCommon, vu_common: VhostUserCommon, id: String, config: VirtioFsConfig, @@ -178,17 +177,17 @@ impl Fs { }; Ok(Fs { - common: VirtioCommon { - device_type: VirtioDeviceType::Fs as u32, - avail_features, - acked_features, - queue_sizes: vec![queue_size; num_queues], - paused_sync: Some(Arc::new(Barrier::new(2))), - min_queues: 1, - paused: Arc::new(AtomicBool::new(paused)), - ..Default::default() - }, vu_common: VhostUserCommon { + virtio_common: VirtioCommon { + device_type: VirtioDeviceType::Fs as u32, + avail_features, + acked_features, + queue_sizes: vec![queue_size; num_queues], + paused_sync: Some(Arc::new(Barrier::new(2))), + min_queues: 1, + paused: Arc::new(AtomicBool::new(paused)), + ..Default::default() + }, vu: Some(Arc::new(Mutex::new(vu))), acked_protocol_features, socket_path: path.to_string(), @@ -208,17 +207,17 @@ impl Fs { } fn state(&self) -> std::result::Result { - self.vu_common.state(&self.common, self.config) + self.vu_common.state(self.config) } } impl Drop for Fs { fn drop(&mut self) { - if let Some(kill_evt) = self.common.kill_evt.take() { + if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { // Ignore the result because there is nothing we can do about it. let _ = kill_evt.write(1); } - self.common.wait_for_epoll_threads(); + self.vu_common.virtio_common.wait_for_epoll_threads(); if let Some(thread) = self.epoll_thread.take() && let Err(e) = thread.join() { @@ -229,15 +228,15 @@ impl Drop for Fs { impl VirtioDevice for Fs { fn device_type(&self) -> u32 { - self.common.device_type + self.vu_common.virtio_common.device_type } fn queue_max_sizes(&self) -> &[u16] { - &self.common.queue_sizes + &self.vu_common.virtio_common.queue_sizes } fn features(&self) -> u64 { - let mut features = self.common.avail_features; + let mut features = self.vu_common.virtio_common.avail_features; if self.iommu { features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } @@ -245,7 +244,7 @@ impl VirtioDevice for Fs { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value); + self.vu_common.virtio_common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -259,26 +258,28 @@ impl VirtioDevice for Fs { queues, .. } = context; - self.common.activate(&queues, interrupt_cb.clone())?; + self.vu_common + .virtio_common + .activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); let backend_req_handler: Option> = None; // Run a dedicated thread for handling potential reconnections with // the backend. - let (kill_evt, pause_evt) = self.common.dup_eventfds(); + let (kill_evt, pause_evt) = self.vu_common.virtio_common.dup_eventfds(); let mut handler = self.vu_common.activate( mem, &queues, interrupt_cb, - self.common.acked_features, + self.vu_common.virtio_common.acked_features, backend_req_handler, kill_evt, pause_evt, )?; - let paused = self.common.paused.clone(); - let paused_sync = self.common.paused_sync.clone(); + let paused = self.vu_common.virtio_common.paused.clone(); + let paused_sync = self.vu_common.virtio_common.paused_sync.clone(); let mut epoll_threads = Vec::new(); spawn_virtio_thread( @@ -297,8 +298,8 @@ impl VirtioDevice for Fs { fn reset(&mut self) -> Option> { // We first must resume the virtio thread if it was paused. - if self.common.pause_evt.take().is_some() { - self.common.resume().ok()?; + if self.vu_common.virtio_common.pause_evt.take().is_some() { + self.vu_common.virtio_common.resume().ok()?; } if let Some(vu) = &self.vu_common.vu @@ -308,7 +309,7 @@ impl VirtioDevice for Fs { return None; } - if let Some(kill_evt) = self.common.kill_evt.take() { + if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { // Ignore the result because there is nothing we can do about it. let _ = kill_evt.write(1); } @@ -316,7 +317,7 @@ impl VirtioDevice for Fs { event!("virtio-device", "reset", "id", &self.id); // Return the interrupt - Some(self.common.interrupt_cb.take().unwrap()) + Some(self.vu_common.virtio_common.interrupt_cb.take().unwrap()) } fn shutdown(&mut self) { @@ -364,11 +365,11 @@ impl VirtioDevice for Fs { impl Pausable for Fs { fn pause(&mut self) -> result::Result<(), MigratableError> { self.vu_common.pause()?; - self.common.pause() + self.vu_common.virtio_common.pause() } fn resume(&mut self) -> result::Result<(), MigratableError> { - self.common.resume()?; + self.vu_common.virtio_common.resume()?; if let Some(epoll_thread) = &self.epoll_thread { epoll_thread.thread().unpark(); @@ -407,7 +408,6 @@ impl Migratable for Fs { } fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> { - self.vu_common - .complete_migration(self.common.kill_evt.take()) + self.vu_common.complete_migration() } } diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index da8fe53a88..0554973f82 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -34,7 +34,6 @@ pub type State = VhostUserState<()>; struct BackendReqHandler {} impl VhostUserFrontendReqHandler for BackendReqHandler {} pub struct GenericVhostUser { - common: VirtioCommon, vu_common: VhostUserCommon, id: String, // Hold ownership of the memory that is allocated for the device @@ -138,17 +137,17 @@ since the backend only supports {backend_num_queues}\n", }; Ok(GenericVhostUser { - common: VirtioCommon { - device_type, - avail_features, - acked_features, - queue_sizes: request_queue_sizes, - paused_sync: Some(Arc::new(Barrier::new(2))), - min_queues: 1, - paused: Arc::new(AtomicBool::new(paused)), - ..Default::default() - }, vu_common: VhostUserCommon { + virtio_common: VirtioCommon { + device_type, + avail_features, + acked_features, + queue_sizes: request_queue_sizes, + paused_sync: Some(Arc::new(Barrier::new(2))), + min_queues: 1, + paused: Arc::new(AtomicBool::new(paused)), + ..Default::default() + }, vu: Some(Arc::new(Mutex::new(vu))), acked_protocol_features, socket_path: path.to_string(), @@ -168,7 +167,7 @@ since the backend only supports {backend_num_queues}\n", } fn state(&self) -> std::result::Result { - self.vu_common.state(&self.common, ()) + self.vu_common.state(()) } #[cold] @@ -189,11 +188,11 @@ space access. Reads will return 0xFF and writes will be ignored." impl Drop for GenericVhostUser { fn drop(&mut self) { - if let Some(kill_evt) = self.common.kill_evt.take() { + if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { // Ignore the result because there is nothing we can do about it. let _ = kill_evt.write(1); } - self.common.wait_for_epoll_threads(); + self.vu_common.virtio_common.wait_for_epoll_threads(); if let Some(thread) = self.epoll_thread.take() && let Err(e) = thread.join() { @@ -204,15 +203,15 @@ impl Drop for GenericVhostUser { impl VirtioDevice for GenericVhostUser { fn device_type(&self) -> u32 { - self.common.device_type + self.vu_common.virtio_common.device_type } fn queue_max_sizes(&self) -> &[u16] { - &self.common.queue_sizes + &self.vu_common.virtio_common.queue_sizes } fn features(&self) -> u64 { - let mut features = self.common.avail_features; + let mut features = self.vu_common.virtio_common.avail_features; if self.iommu { features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } @@ -220,7 +219,7 @@ impl VirtioDevice for GenericVhostUser { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value); + self.vu_common.virtio_common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -282,26 +281,28 @@ impl VirtioDevice for GenericVhostUser { queues, .. } = context; - self.common.activate(&queues, interrupt_cb.clone())?; + self.vu_common + .virtio_common + .activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); let backend_req_handler: Option> = None; // Run a dedicated thread for handling potential reconnections with // the backend. - let (kill_evt, pause_evt) = self.common.dup_eventfds(); + let (kill_evt, pause_evt) = self.vu_common.virtio_common.dup_eventfds(); let mut handler = self.vu_common.activate( mem, &queues, interrupt_cb, - self.common.acked_features, + self.vu_common.virtio_common.acked_features, backend_req_handler, kill_evt, pause_evt, )?; - let paused = self.common.paused.clone(); - let paused_sync = self.common.paused_sync.clone(); + let paused = self.vu_common.virtio_common.paused.clone(); + let paused_sync = self.vu_common.virtio_common.paused_sync.clone(); let mut epoll_threads = Vec::new(); spawn_virtio_thread( @@ -320,8 +321,8 @@ impl VirtioDevice for GenericVhostUser { fn reset(&mut self) -> Option> { // We first must resume the virtio thread if it was paused. - if self.common.pause_evt.take().is_some() { - self.common.resume().ok()?; + if self.vu_common.virtio_common.pause_evt.take().is_some() { + self.vu_common.virtio_common.resume().ok()?; } if let Some(vu) = &self.vu_common.vu @@ -331,7 +332,7 @@ impl VirtioDevice for GenericVhostUser { return None; } - if let Some(kill_evt) = self.common.kill_evt.take() { + if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { // Ignore the result because there is nothing we can do about it. let _ = kill_evt.write(1); } @@ -339,7 +340,7 @@ impl VirtioDevice for GenericVhostUser { event!("virtio-device", "reset", "id", &self.id); // Return the interrupt - Some(self.common.interrupt_cb.take().unwrap()) + Some(self.vu_common.virtio_common.interrupt_cb.take().unwrap()) } fn shutdown(&mut self) { @@ -387,11 +388,11 @@ impl VirtioDevice for GenericVhostUser { impl Pausable for GenericVhostUser { fn pause(&mut self) -> result::Result<(), MigratableError> { self.vu_common.pause()?; - self.common.pause() + self.vu_common.virtio_common.pause() } fn resume(&mut self) -> result::Result<(), MigratableError> { - self.common.resume()?; + self.vu_common.virtio_common.resume()?; if let Some(epoll_thread) = &self.epoll_thread { epoll_thread.thread().unpark(); @@ -430,7 +431,6 @@ impl Migratable for GenericVhostUser { } fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> { - self.vu_common - .complete_migration(self.common.kill_evt.take()) + self.vu_common.complete_migration() } } diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 86a774737a..a80c5aa237 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -29,7 +29,7 @@ use crate::{ ActivateError, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_IN_ORDER, VIRTIO_F_NOTIFICATION_DATA, VIRTIO_F_ORDER_PLATFORM, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC, - VIRTIO_F_VERSION_1, VirtioInterrupt, + VIRTIO_F_VERSION_1, VirtioCommon, VirtioInterrupt, }; pub mod blk; @@ -336,6 +336,7 @@ impl VhostUserState { #[derive(Default)] pub struct VhostUserCommon { + pub virtio_common: VirtioCommon, pub vu: Option>>, pub acked_protocol_features: u64, pub socket_path: String, @@ -485,12 +486,11 @@ impl VhostUserCommon { pub fn state( &self, - common: &crate::VirtioCommon, config: C, ) -> std::result::Result, MigratableError> { let mut state = VhostUserState { - avail_features: common.avail_features, - acked_features: common.acked_features, + avail_features: self.virtio_common.avail_features, + acked_features: self.virtio_common.acked_features, config, acked_protocol_features: self.acked_protocol_features, vu_num_queues: self.vu_num_queues, @@ -586,15 +586,12 @@ impl VhostUserCommon { Ok(()) } - pub fn complete_migration( - &mut self, - kill_evt: Option, - ) -> std::result::Result<(), MigratableError> { + pub fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> { self.migration_started = false; // Make sure the device thread is killed in order to prevent from // reconnections to the socket. - if let Some(kill_evt) = kill_evt { + if let Some(kill_evt) = self.virtio_common.kill_evt.take() { kill_evt.write(1).map_err(|e| { MigratableError::CompleteMigration(anyhow!( "Error killing vhost-user thread: {e:?}" diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index d05626901a..a270ed5414 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -41,7 +41,6 @@ struct BackendReqHandler {} impl VhostUserFrontendReqHandler for BackendReqHandler {} pub struct Net { - common: VirtioCommon, vu_common: VhostUserCommon, id: String, config: VirtioNetConfig, @@ -198,17 +197,17 @@ impl Net { Ok(Net { id, - common: VirtioCommon { - device_type: VirtioDeviceType::Net as u32, - queue_sizes: vec![vu_cfg.queue_size; num_queues], - avail_features, - acked_features, - paused_sync: Some(Arc::new(Barrier::new(2))), - min_queues: DEFAULT_QUEUE_NUMBER as u16, - paused: Arc::new(AtomicBool::new(paused)), - ..Default::default() - }, vu_common: VhostUserCommon { + virtio_common: VirtioCommon { + device_type: VirtioDeviceType::Net as u32, + queue_sizes: vec![vu_cfg.queue_size; num_queues], + avail_features, + acked_features, + paused_sync: Some(Arc::new(Barrier::new(2))), + min_queues: DEFAULT_QUEUE_NUMBER as u16, + paused: Arc::new(AtomicBool::new(paused)), + ..Default::default() + }, vu: Some(Arc::new(Mutex::new(vu))), acked_protocol_features, socket_path: vu_cfg.socket, @@ -228,19 +227,19 @@ impl Net { } fn state(&self) -> std::result::Result { - self.vu_common.state(&self.common, self.config) + self.vu_common.state(self.config) } } impl Drop for Net { fn drop(&mut self) { - if let Some(kill_evt) = self.common.kill_evt.take() + if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() && let Err(e) = kill_evt.write(1) { error!("failed to kill vhost-user-net: {e:?}"); } - self.common.wait_for_epoll_threads(); + self.vu_common.virtio_common.wait_for_epoll_threads(); if let Some(thread) = self.epoll_thread.take() && let Err(e) = thread.join() @@ -258,15 +257,15 @@ impl Drop for Net { impl VirtioDevice for Net { fn device_type(&self) -> u32 { - self.common.device_type + self.vu_common.virtio_common.device_type } fn queue_max_sizes(&self) -> &[u16] { - &self.common.queue_sizes + &self.vu_common.virtio_common.queue_sizes } fn features(&self) -> u64 { - let mut features = self.common.avail_features; + let mut features = self.vu_common.virtio_common.avail_features; if self.iommu { features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } @@ -274,7 +273,7 @@ impl VirtioDevice for Net { } fn ack_features(&mut self, value: u64) { - self.common.ack_features(value); + self.vu_common.virtio_common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { @@ -288,18 +287,28 @@ impl VirtioDevice for Net { mut queues, .. } = context; - self.common.activate(&queues, interrupt_cb.clone())?; + self.vu_common + .virtio_common + .activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); let num_queues = queues.len(); - let event_idx = self.common.feature_acked(VIRTIO_RING_F_EVENT_IDX.into()); - if self.common.feature_acked(VIRTIO_NET_F_CTRL_VQ.into()) && !num_queues.is_multiple_of(2) { + let event_idx = self + .vu_common + .virtio_common + .feature_acked(VIRTIO_RING_F_EVENT_IDX.into()); + if self + .vu_common + .virtio_common + .feature_acked(VIRTIO_NET_F_CTRL_VQ.into()) + && !num_queues.is_multiple_of(2) + { let ctrl_queue_index = num_queues - 1; let (_, mut ctrl_queue, ctrl_queue_evt) = queues.remove(ctrl_queue_index); ctrl_queue.set_event_idx(event_idx); - let (kill_evt, pause_evt) = self.common.dup_eventfds(); + let (kill_evt, pause_evt) = self.vu_common.virtio_common.dup_eventfds(); let mut ctrl_handler = NetCtrlEpollHandler { mem: mem.clone(), @@ -313,12 +322,12 @@ impl VirtioDevice for Net { queue_index: ctrl_queue_index as u16, }; - let paused = self.common.paused.clone(); + let paused = self.vu_common.virtio_common.paused.clone(); // Let's update the barrier as we need 1 for the control queue // thread + 1 for the common vhost-user thread + 1 for the main // thread signalling the pause. - self.common.paused_sync = Some(Arc::new(Barrier::new(3))); - let paused_sync = self.common.paused_sync.clone(); + self.vu_common.virtio_common.paused_sync = Some(Arc::new(Barrier::new(3))); + let paused_sync = self.vu_common.virtio_common.paused_sync.clone(); let mut epoll_threads = Vec::new(); spawn_virtio_thread( @@ -336,11 +345,12 @@ impl VirtioDevice for Net { // The backend acknowledged features must not contain VIRTIO_NET_F_MAC // since we don't expect the backend to handle it. - let backend_acked_features = self.common.acked_features & !(1 << VIRTIO_NET_F_MAC); + let backend_acked_features = + self.vu_common.virtio_common.acked_features & !(1 << VIRTIO_NET_F_MAC); // Run a dedicated thread for handling potential reconnections with // the backend. - let (kill_evt, pause_evt) = self.common.dup_eventfds(); + let (kill_evt, pause_evt) = self.vu_common.virtio_common.dup_eventfds(); let mut handler = self.vu_common.activate( mem, @@ -352,8 +362,8 @@ impl VirtioDevice for Net { pause_evt, )?; - let paused = self.common.paused.clone(); - let paused_sync = self.common.paused_sync.clone(); + let paused = self.vu_common.virtio_common.paused.clone(); + let paused_sync = self.vu_common.virtio_common.paused_sync.clone(); let mut epoll_threads = Vec::new(); spawn_virtio_thread( @@ -371,8 +381,8 @@ impl VirtioDevice for Net { fn reset(&mut self) -> Option> { // We first must resume the virtio thread if it was paused. - if self.common.pause_evt.take().is_some() { - self.common.resume().ok()?; + if self.vu_common.virtio_common.pause_evt.take().is_some() { + self.vu_common.virtio_common.resume().ok()?; } if let Some(vu) = &self.vu_common.vu @@ -382,7 +392,7 @@ impl VirtioDevice for Net { return None; } - if let Some(kill_evt) = self.common.kill_evt.take() { + if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { // Ignore the result because there is nothing we can do about it. let _ = kill_evt.write(1); } @@ -390,7 +400,7 @@ impl VirtioDevice for Net { event!("virtio-device", "reset", "id", &self.id); // Return the interrupt - Some(self.common.interrupt_cb.take().unwrap()) + Some(self.vu_common.virtio_common.interrupt_cb.take().unwrap()) } fn shutdown(&mut self) { @@ -408,11 +418,11 @@ impl VirtioDevice for Net { impl Pausable for Net { fn pause(&mut self) -> result::Result<(), MigratableError> { self.vu_common.pause()?; - self.common.pause() + self.vu_common.virtio_common.pause() } fn resume(&mut self) -> result::Result<(), MigratableError> { - self.common.resume()?; + self.vu_common.virtio_common.resume()?; if let Some(epoll_thread) = &self.epoll_thread { epoll_thread.thread().unpark(); @@ -455,7 +465,6 @@ impl Migratable for Net { } fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> { - self.vu_common - .complete_migration(self.common.kill_evt.take()) + self.vu_common.complete_migration() } } From 63aeb597ef3c7a24e32d4c7cd2935eacc0e38db3 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 31 Mar 2026 04:41:51 -0700 Subject: [PATCH 1020/1893] 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 --- virtio-devices/src/vhost_user/blk.rs | 10 ++++------ virtio-devices/src/vhost_user/fs.rs | 10 ++++------ virtio-devices/src/vhost_user/generic_vhost_user.rs | 10 ++++------ virtio-devices/src/vhost_user/mod.rs | 3 ++- virtio-devices/src/vhost_user/net.rs | 8 +++----- 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index f6e9a46232..529eb517d5 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -3,7 +3,7 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier, Mutex}; -use std::{mem, result, thread}; +use std::{mem, result}; use block::VirtioBlockConfig; use event_monitor::event; @@ -43,7 +43,6 @@ pub struct Blk { id: String, config: VirtioBlockConfig, guest_memory: Option>, - epoll_thread: Option>, seccomp_action: SeccompAction, exit_evt: EventFd, iommu: bool, @@ -190,7 +189,6 @@ impl Blk { id, config, guest_memory: None, - epoll_thread: None, seccomp_action, exit_evt, iommu, @@ -210,7 +208,7 @@ impl Drop for Blk { error!("failed to kill vhost-user-blk: {e:?}"); } self.vu_common.virtio_common.wait_for_epoll_threads(); - if let Some(thread) = self.epoll_thread.take() + if let Some(thread) = self.vu_common.epoll_thread.take() && let Err(e) = thread.join() { error!("Error joining thread: {e:?}"); @@ -311,7 +309,7 @@ impl VirtioDevice for Blk { &self.exit_evt, move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; - self.epoll_thread = Some(epoll_threads.remove(0)); + self.vu_common.epoll_thread = Some(epoll_threads.remove(0)); Ok(()) } @@ -361,7 +359,7 @@ impl Pausable for Blk { fn resume(&mut self) -> result::Result<(), MigratableError> { self.vu_common.virtio_common.resume()?; - if let Some(epoll_thread) = &self.epoll_thread { + if let Some(epoll_thread) = &self.vu_common.epoll_thread { epoll_thread.thread().unpark(); } diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 3f982ff299..71859365ed 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -1,9 +1,9 @@ // Copyright 2019 Intel Corporation. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +use std::result; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier, Mutex}; -use std::{result, thread}; use event_monitor::event; use log::{error, info}; @@ -67,7 +67,6 @@ pub struct Fs { cache: Option<(VirtioSharedMemoryList, MmapRegion)>, seccomp_action: SeccompAction, guest_memory: Option>, - epoll_thread: Option>, exit_evt: EventFd, iommu: bool, } @@ -200,7 +199,6 @@ impl Fs { cache, seccomp_action, guest_memory: None, - epoll_thread: None, exit_evt, iommu, }) @@ -218,7 +216,7 @@ impl Drop for Fs { let _ = kill_evt.write(1); } self.vu_common.virtio_common.wait_for_epoll_threads(); - if let Some(thread) = self.epoll_thread.take() + if let Some(thread) = self.vu_common.epoll_thread.take() && let Err(e) = thread.join() { error!("Error joining thread: {e:?}"); @@ -290,7 +288,7 @@ impl VirtioDevice for Fs { &self.exit_evt, move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; - self.epoll_thread = Some(epoll_threads.remove(0)); + self.vu_common.epoll_thread = Some(epoll_threads.remove(0)); event!("virtio-device", "activated", "id", &self.id); Ok(()) @@ -371,7 +369,7 @@ impl Pausable for Fs { fn resume(&mut self) -> result::Result<(), MigratableError> { self.vu_common.virtio_common.resume()?; - if let Some(epoll_thread) = &self.epoll_thread { + if let Some(epoll_thread) = &self.vu_common.epoll_thread { epoll_thread.thread().unpark(); } diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 0554973f82..83f3fe465d 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -2,9 +2,9 @@ // Copyright 2025 Demi Marie Obenour. // SPDX-License-Identifier: Apache-2.0 +use std::result; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Barrier, Mutex}; -use std::{result, thread}; use event_monitor::event; use log::{error, info, warn}; @@ -41,7 +41,6 @@ pub struct GenericVhostUser { cache: Option<(VirtioSharedMemoryList, MmapRegion)>, seccomp_action: SeccompAction, guest_memory: Option>, - epoll_thread: Option>, exit_evt: EventFd, iommu: bool, cfg_warning: AtomicBool, @@ -159,7 +158,6 @@ since the backend only supports {backend_num_queues}\n", cache, seccomp_action, guest_memory: None, - epoll_thread: None, exit_evt, iommu, cfg_warning: AtomicBool::new(false), @@ -193,7 +191,7 @@ impl Drop for GenericVhostUser { let _ = kill_evt.write(1); } self.vu_common.virtio_common.wait_for_epoll_threads(); - if let Some(thread) = self.epoll_thread.take() + if let Some(thread) = self.vu_common.epoll_thread.take() && let Err(e) = thread.join() { error!("Error joining thread: {e:?}"); @@ -313,7 +311,7 @@ impl VirtioDevice for GenericVhostUser { &self.exit_evt, move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; - self.epoll_thread = Some(epoll_threads.remove(0)); + self.vu_common.epoll_thread = Some(epoll_threads.remove(0)); event!("virtio-device", "activated", "id", &self.id); Ok(()) @@ -394,7 +392,7 @@ impl Pausable for GenericVhostUser { fn resume(&mut self) -> result::Result<(), MigratableError> { self.vu_common.virtio_common.resume()?; - if let Some(epoll_thread) = &self.epoll_thread { + if let Some(epoll_thread) = &self.vu_common.epoll_thread { epoll_thread.thread().unpark(); } diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index a80c5aa237..3ececc663c 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -1,11 +1,11 @@ // Copyright 2019 Intel Corporation. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use std::io; use std::ops::Deref; use std::os::unix::io::AsRawFd; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier, Mutex}; +use std::{io, thread}; use anyhow::anyhow; use log::error; @@ -345,6 +345,7 @@ pub struct VhostUserCommon { pub server: bool, pub interrupt_cb: Option>, pub vring_bases: Option>, + pub epoll_thread: Option>, } impl VhostUserCommon { diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index a270ed5414..7cd4e42070 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -46,7 +46,6 @@ pub struct Net { config: VirtioNetConfig, guest_memory: Option>, ctrl_queue_epoll_thread: Option>, - epoll_thread: Option>, seccomp_action: SeccompAction, exit_evt: EventFd, iommu: bool, @@ -219,7 +218,6 @@ impl Net { config, guest_memory: None, ctrl_queue_epoll_thread: None, - epoll_thread: None, seccomp_action, exit_evt, iommu, @@ -241,7 +239,7 @@ impl Drop for Net { self.vu_common.virtio_common.wait_for_epoll_threads(); - if let Some(thread) = self.epoll_thread.take() + if let Some(thread) = self.vu_common.epoll_thread.take() && let Err(e) = thread.join() { error!("Error joining thread: {e:?}"); @@ -374,7 +372,7 @@ impl VirtioDevice for Net { &self.exit_evt, move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; - self.epoll_thread = Some(epoll_threads.remove(0)); + self.vu_common.epoll_thread = Some(epoll_threads.remove(0)); Ok(()) } @@ -424,7 +422,7 @@ impl Pausable for Net { fn resume(&mut self) -> result::Result<(), MigratableError> { self.vu_common.virtio_common.resume()?; - if let Some(epoll_thread) = &self.epoll_thread { + if let Some(epoll_thread) = &self.vu_common.epoll_thread { epoll_thread.thread().unpark(); } From 68691db37bc6b133e0cf068137223daa75dad71a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 31 Mar 2026 05:54:12 -0700 Subject: [PATCH 1021/1893] 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 --- virtio-devices/src/vhost_user/mod.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 3ececc663c..95e819e18c 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use std::os::unix::io::AsRawFd; -use std::sync::atomic::AtomicBool; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Barrier, Mutex}; use std::{io, thread}; @@ -425,6 +425,22 @@ impl VhostUserCommon { } pub fn shutdown(&mut self) { + // Signal the epoll thread to exit, unpause it (it may be parked + // if the VM was paused for migration), then wait for it to finish. + // This ensures the thread drops its Arc, fully + // closing the vhost-user socket so the backend can accept a new + // connection from the destination. + if let Some(kill_evt) = self.virtio_common.kill_evt.take() { + let _ = kill_evt.write(1); + } + self.virtio_common.paused.store(false, Ordering::SeqCst); + if let Some(t) = self.epoll_thread.as_ref() { + t.thread().unpark(); + } + if let Some(t) = self.epoll_thread.take() { + let _ = t.join(); + } + // Remove socket path if needed if self.server { let _ = std::fs::remove_file(&self.socket_path); From cc1735c3996d9873bfb8f8cc787200905578a159 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 31 Mar 2026 06:50:24 -0700 Subject: [PATCH 1022/1893] 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 --- virtio-devices/src/vhost_user/vu_common_ctrl.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index b2ee2ae5dc..1e356f6485 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -12,7 +12,7 @@ use std::thread::sleep; use std::time::{Duration, Instant}; use log::{error, info}; -use vhost::vhost_kern::vhost_binding::{VHOST_F_LOG_ALL, VHOST_VRING_F_LOG}; +use vhost::vhost_kern::vhost_binding::VHOST_VRING_F_LOG; use vhost::vhost_user::message::{ VhostTransferStateDirection, VhostTransferStatePhase, VhostUserHeaderFlag, VhostUserInflight, VhostUserProtocolFeatures, VhostUserVirtioFeatures, @@ -454,11 +454,8 @@ impl VhostUserHandle { } fn update_supported_features(&mut self, acked_features: u64, acked_protocol_features: u64) { - if (acked_features & u64::from(vhost::vhost_kern::vhost_binding::VHOST_F_LOG_ALL) != 0) - && (acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0) - { - self.supports_migration = true; - } + self.supports_migration = acked_features & VhostUserVirtioFeatures::LOG_ALL.bits() != 0 + && acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0; self.supports_device_state = acked_protocol_features & VhostUserProtocolFeatures::DEVICE_STATE.bits() != 0; } @@ -643,7 +640,7 @@ impl VhostUserHandle { self.update_log_base(last_ram_addr)?; // Enable VHOST_F_LOG_ALL feature - let features = self.acked_features | (1 << VHOST_F_LOG_ALL); + let features = self.acked_features | VhostUserVirtioFeatures::LOG_ALL.bits(); self.vu .set_features(features) .map_err(Error::VhostUserSetFeatures)?; From 070f3bbea15f8e8ffea31afbfc6e50bbc7672625 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 31 Mar 2026 06:52:10 -0700 Subject: [PATCH 1023/1893] virtio-devices: Reject dirty logging if backend does not support it Signed-off-by: Rob Bradford --- virtio-devices/src/vhost_user/vu_common_ctrl.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 1e356f6485..23e06a3c0a 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -670,6 +670,10 @@ impl VhostUserHandle { } pub fn dirty_log(&mut self, last_ram_addr: u64) -> Result { + if !self.supports_migration { + return Err(Error::MigrationNotSupported); + } + // The log region is updated by creating a new region that is sent to // the backend. This ensures the backend stops logging to the previous // region. The previous region is returned and processed to create the From 266ad8aa0ebca38e863171236a8b7fdfe966bc37 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 31 Mar 2026 06:52:45 -0700 Subject: [PATCH 1024/1893] 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 --- virtio-devices/src/vhost_user/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 95e819e18c..01a75f0575 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -167,7 +167,8 @@ pub const DEFAULT_VIRTIO_FEATURES: u64 = (1 << VIRTIO_F_RING_INDIRECT_DESC) | (1 << VIRTIO_F_IN_ORDER) | (1 << VIRTIO_F_ORDER_PLATFORM) | (1 << VIRTIO_F_NOTIFICATION_DATA) - | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() + | VhostUserVirtioFeatures::LOG_ALL.bits(); const HUP_CONNECTION_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1; const BACKEND_REQ_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 2; From fa29dbd0c5ceaf80b851d1614c063ab6ac2b4576 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 31 Mar 2026 07:28:38 -0700 Subject: [PATCH 1025/1893] 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 --- virtio-devices/src/vhost_user/blk.rs | 12 +----------- virtio-devices/src/vhost_user/fs.rs | 11 +---------- .../src/vhost_user/generic_vhost_user.rs | 11 +---------- virtio-devices/src/vhost_user/net.rs | 14 +------------- 4 files changed, 4 insertions(+), 44 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 529eb517d5..8221b7b501 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -202,17 +202,7 @@ impl Blk { impl Drop for Blk { fn drop(&mut self) { - if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() - && let Err(e) = kill_evt.write(1) - { - error!("failed to kill vhost-user-blk: {e:?}"); - } - self.vu_common.virtio_common.wait_for_epoll_threads(); - if let Some(thread) = self.vu_common.epoll_thread.take() - && let Err(e) = thread.join() - { - error!("Error joining thread: {e:?}"); - } + self.vu_common.shutdown(); } } diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 71859365ed..967fdecf99 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -211,16 +211,7 @@ impl Fs { impl Drop for Fs { fn drop(&mut self) { - if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { - // Ignore the result because there is nothing we can do about it. - let _ = kill_evt.write(1); - } - self.vu_common.virtio_common.wait_for_epoll_threads(); - if let Some(thread) = self.vu_common.epoll_thread.take() - && let Err(e) = thread.join() - { - error!("Error joining thread: {e:?}"); - } + self.vu_common.shutdown(); } } diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 83f3fe465d..1778834431 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -186,16 +186,7 @@ space access. Reads will return 0xFF and writes will be ignored." impl Drop for GenericVhostUser { fn drop(&mut self) { - if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { - // Ignore the result because there is nothing we can do about it. - let _ = kill_evt.write(1); - } - self.vu_common.virtio_common.wait_for_epoll_threads(); - if let Some(thread) = self.vu_common.epoll_thread.take() - && let Err(e) = thread.join() - { - error!("Error joining thread: {e:?}"); - } + self.vu_common.shutdown(); } } diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 7cd4e42070..1c85b5f38e 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -231,19 +231,7 @@ impl Net { impl Drop for Net { fn drop(&mut self) { - if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() - && let Err(e) = kill_evt.write(1) - { - error!("failed to kill vhost-user-net: {e:?}"); - } - - self.vu_common.virtio_common.wait_for_epoll_threads(); - - if let Some(thread) = self.vu_common.epoll_thread.take() - && let Err(e) = thread.join() - { - error!("Error joining thread: {e:?}"); - } + self.vu_common.shutdown(); if let Some(thread) = self.ctrl_queue_epoll_thread.take() && let Err(e) = thread.join() From a99c40dbbb5565a432776344b221332fea6af9df Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 30 Mar 2026 02:21:52 -0700 Subject: [PATCH 1026/1893] tests: Add live migration test for virtio-fs Signed-off-by: Rob Bradford --- cloud-hypervisor/tests/common/utils.rs | 14 +- cloud-hypervisor/tests/integration.rs | 185 ++++++++++++++++++++++++- 2 files changed, 197 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs index ac48641826..aff1e94ef5 100644 --- a/cloud-hypervisor/tests/common/utils.rs +++ b/cloud-hypervisor/tests/common/utils.rs @@ -9,6 +9,7 @@ use std::process::{Child, Command}; use std::string::String; use std::sync::mpsc; use std::sync::mpsc::Receiver; +use std::time::{Duration, Instant}; use std::{cmp, fs, io, thread}; use test_infra::*; @@ -93,6 +94,17 @@ pub(crate) fn temp_api_path(tmp_dir: &TempDir) -> String { ) } +pub(crate) fn wait_for_virtiofsd_socket(socket: &str) { + // Wait for virtiofds to start + let deadline = Instant::now() + Duration::from_secs(10); + while !Path::new(socket).exists() { + if Instant::now() > deadline { + panic!("virtiofsd socket did not appear within 10s"); + } + thread::sleep(Duration::from_millis(50)); + } +} + pub(crate) fn prepare_virtiofsd( tmp_dir: &TempDir, shared_dir: &str, @@ -116,7 +128,7 @@ pub(crate) fn prepare_virtiofsd( .spawn() .unwrap(); - thread::sleep(std::time::Duration::new(10, 0)); + wait_for_virtiofsd_socket(virtiofsd_socket_path.as_str()); (child, virtiofsd_socket_path) } diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5ae554981c..a763ccba4c 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -10062,6 +10062,177 @@ mod live_migration { handle_child_output(r, &src_output); } + fn _test_live_migration_virtio_fs(local: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + let mut shared_dir = workload_path; + shared_dir.push("shared_dir"); + + let (daemon_child, virtiofsd_socket_path) = + prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); + + let src_api_socket = temp_api_path(&guest.tmp_dir); + + // Start the source VM + let mut src_child = GuestCommand::new(&guest) + .args(["--api-socket", &src_api_socket]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=512M,shared=on"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .args([ + "--fs", + format!("socket={virtiofsd_socket_path},tag=myfs,num_queues=1,queue_size=1024") + .as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + // Start the destination VM + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) + .capture_output() + .spawn() + .unwrap(); + + // Spawn a thread that waits for the old virtiofsd to exit then + // starts a replacement. During migration the source saves + // DEVICE_STATE then disconnects, causing virtiofsd to exit. + // The destination needs a fresh virtiofsd to load DEVICE_STATE. + // We remove the socket file first so the destination cannot + // accidentally connect to the old instance. + let virtiofsd_socket_clone = virtiofsd_socket_path.clone(); + let shared_dir_str = shared_dir.to_str().unwrap().to_string(); + let (restart_tx, restart_rx) = std::sync::mpsc::channel(); + let _monitor = thread::spawn(move || { + let mut child = daemon_child; + let _ = child.wait(); + let mut path = dirs::home_dir().unwrap(); + path.push("workloads"); + path.push("virtiofsd"); + let new_child = Command::new(path) + .args(["--shared-dir", &shared_dir_str]) + .args(["--socket-path", &virtiofsd_socket_clone]) + .args(["--cache", "never"]) + .args(["--tag", "myfs"]) + .spawn() + .unwrap(); + wait_for_virtiofsd_socket(&virtiofsd_socket_clone); + let _ = restart_tx.send(new_child); + }); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Mount virtiofs and verify it works + guest + .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") + .unwrap(); + + // Write a test file through virtiofs before migration + guest + .ssh_command( + "sudo bash -c 'echo pre_migration_data > mount_dir/migration_test_file'", + ) + .unwrap(); + + // Verify the file is accessible + assert_eq!( + guest + .ssh_command("cat mount_dir/migration_test_file") + .unwrap() + .trim(), + "pre_migration_data" + ); + + let migration_socket = String::from( + guest + .tmp_dir + .as_path() + .join("live-migration.sock") + .to_str() + .unwrap(), + ); + + // Remove the socket so the destination cannot connect to + // the old virtiofsd (which is still running). The source's + // existing connection uses an already-accepted fd. + let _ = std::fs::remove_file(&virtiofsd_socket_path); + + assert!( + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), + "Unsuccessful command: 'send-migration' or 'receive-migration'." + ); + }); + + // Check and report any errors occurred during the live-migration + if r.is_err() { + print_and_panic( + src_child, + dest_child, + None, + "Error occurred during live-migration with virtio-fs", + ); + } + + // Check the source vm has been terminated successfully (give it '3s' to settle) + thread::sleep(Duration::from_secs(3)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + print_and_panic( + src_child, + dest_child, + None, + "source VM was not terminated successfully.", + ); + } + + // Post live-migration checks + let r = std::panic::catch_unwind(|| { + // Verify virtiofs still works after migration + // Read the file written before migration + assert_eq!( + guest + .ssh_command("cat mount_dir/migration_test_file") + .unwrap() + .trim(), + "pre_migration_data" + ); + + // Write a new file after migration + guest + .ssh_command( + "sudo bash -c 'echo post_migration_data > mount_dir/post_migration_file'", + ) + .unwrap(); + + // Verify the new file exists on the host + let post_content = + std::fs::read_to_string(shared_dir.join("post_migration_file")).unwrap(); + assert_eq!(post_content.trim(), "post_migration_data"); + }); + + // Clean up + let _ = dest_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + if let Ok(mut new_daemon) = restart_rx.try_recv() { + let _ = new_daemon.kill(); + let _ = new_daemon.wait(); + } + let _ = std::fs::remove_file(shared_dir.join("migration_test_file")); + let _ = std::fs::remove_file(shared_dir.join("post_migration_file")); + + handle_child_output(r, &dest_output); + } + mod live_migration_parallel { use vmm::api::TimeoutStrategy; @@ -10134,7 +10305,19 @@ mod live_migration { mod live_migration_sequential { use super::*; - // NUMA & balloon live migration tests are large so run sequentially + // NUMA, balloon, and virtio-fs live migration tests run sequentially + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_virtio_fs() { + _test_live_migration_virtio_fs(false); + } + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_virtio_fs_local() { + _test_live_migration_virtio_fs(true); + } #[test] fn test_live_migration_balloon() { From a8904a93a64a08eacabfae420268605cb464a452 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 1 Apr 2026 04:53:45 -0700 Subject: [PATCH 1027/1893] 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 --- .github/workflows/integration-arm64.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-arm64.yaml b/.github/workflows/integration-arm64.yaml index 0678dbc839..d0d6966482 100644 --- a/.github/workflows/integration-arm64.yaml +++ b/.github/workflows/integration-arm64.yaml @@ -8,19 +8,24 @@ jobs: build: timeout-minutes: 120 name: Tests (ARM64) - runs-on: bookworm-arm64 + runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'bookworm-arm64' }} 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 unit tests (musl) + if: ${{ github.event_name != 'pull_request' }} run: scripts/dev_cli.sh tests --unit --libc musl - name: Load openvswitch module + if: ${{ github.event_name != 'pull_request' }} run: sudo modprobe openvswitch - name: Run integration tests (musl) + if: ${{ github.event_name != 'pull_request' }} timeout-minutes: 60 run: scripts/dev_cli.sh tests --integration --libc musl - name: Install Azure CLI @@ -52,3 +57,6 @@ jobs: if: ${{ github.event_name != 'pull_request' }} timeout-minutes: 30 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" From 6de3d5279fc4b5cb176a4b21b43925b1f445abbc Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 11:42:45 +0200 Subject: [PATCH 1028/1893] 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 --- cloud-hypervisor/tests/integration.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index a763ccba4c..4360f71276 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -10475,6 +10475,9 @@ mod aarch64_acpi { mod rate_limiter { use super::*; + const NET_RATE_LIMITER_RUNTIME: u32 = 10; + const BLOCK_RATE_LIMITER_RUNTIME: u32 = 10; + // Check if the 'measured' rate is within the expected 'difference' (in percentage) // compared to given 'limit' rate. fn check_rate_limit(measured: f64, limit: f64, difference: f64) -> bool { @@ -10498,7 +10501,6 @@ mod rate_limiter { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - let test_timeout = 10; let num_queues = 2; let queue_size = 256; let bw_size = 10485760_u64; // bytes @@ -10528,9 +10530,14 @@ mod rate_limiter { let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); - let measured_bps = - measure_virtio_net_throughput(test_timeout, num_queues / 2, &guest, rx, true) - .unwrap(); + let measured_bps = measure_virtio_net_throughput( + NET_RATE_LIMITER_RUNTIME, + num_queues / 2, + &guest, + rx, + true, + ) + .unwrap(); assert!(check_rate_limit(measured_bps, limit_bps, 0.1)); }); @@ -10550,7 +10557,6 @@ mod rate_limiter { } fn _test_rate_limiter_block(bandwidth: bool, num_queues: u32) { - let test_timeout = 10; let fio_ops = FioOps::RandRW; let bw_size = if bandwidth { @@ -10618,7 +10624,7 @@ mod rate_limiter { let fio_command = format!( "sudo fio --filename=/dev/vdc --name=test --output-format=json \ --direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \ - --rw={fio_ops} --runtime={test_timeout} --numjobs={num_queues}" + --rw={fio_ops} --runtime={BLOCK_RATE_LIMITER_RUNTIME} --numjobs={num_queues}" ); let output = guest.ssh_command(&fio_command).unwrap(); From f1160381110ce6a362b0dd9a946f44976812b1f0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 11:43:32 +0200 Subject: [PATCH 1029/1893] 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 --- cloud-hypervisor/tests/integration.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 4360f71276..f7f4e55544 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -10475,8 +10475,8 @@ mod aarch64_acpi { mod rate_limiter { use super::*; - const NET_RATE_LIMITER_RUNTIME: u32 = 10; - const BLOCK_RATE_LIMITER_RUNTIME: u32 = 10; + const NET_RATE_LIMITER_RUNTIME: u32 = 20; + const BLOCK_RATE_LIMITER_RUNTIME: u32 = 20; // Check if the 'measured' rate is within the expected 'difference' (in percentage) // compared to given 'limit' rate. @@ -10503,12 +10503,12 @@ mod rate_limiter { let num_queues = 2; let queue_size = 256; - let bw_size = 10485760_u64; // bytes - let bw_refill_time = 100; // ms + let bw_size = 104857600_u64; // bytes + let bw_refill_time = 1000; // ms let limit_bps = (bw_size * 8 * 1000) as f64 / bw_refill_time as f64; let net_params = format!( - "tap=,mac={},ip={},mask=255.255.255.128,num_queues={},queue_size={},bw_size={},bw_refill_time={}", + "tap=,mac={},ip={},mask=255.255.255.128,num_queues={},queue_size={},bw_size={},bw_one_time_burst=0,bw_refill_time={}", guest.network.guest_mac0, guest.network.host_ip0, num_queues, @@ -10560,11 +10560,11 @@ mod rate_limiter { let fio_ops = FioOps::RandRW; let bw_size = if bandwidth { - 10485760_u64 // bytes + 104857600_u64 // bytes } else { - 100_u64 // I/O + 1000_u64 // I/O }; - let bw_refill_time = 100; // ms + let bw_refill_time = 1000; // ms let limit_rate = (bw_size * 1000) as f64 / bw_refill_time as f64; let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); @@ -10585,11 +10585,11 @@ mod rate_limiter { let test_blk_params = if bandwidth { format!( - "path={blk_rate_limiter_test_img},num_queues={num_queues},bw_size={bw_size},bw_refill_time={bw_refill_time},image_type=raw" + "path={blk_rate_limiter_test_img},num_queues={num_queues},bw_size={bw_size},bw_one_time_burst=0,bw_refill_time={bw_refill_time},image_type=raw" ) } else { format!( - "path={blk_rate_limiter_test_img},num_queues={num_queues},ops_size={bw_size},ops_refill_time={bw_refill_time},image_type=raw" + "path={blk_rate_limiter_test_img},num_queues={num_queues},ops_size={bw_size},ops_one_time_burst=0,ops_refill_time={bw_refill_time},image_type=raw" ) }; From 36d6dacee2a42626430b74dc292a086a270e5527 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 11:43:58 +0200 Subject: [PATCH 1030/1893] 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 --- cloud-hypervisor/tests/integration.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index f7f4e55544..07acd2d856 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -10643,15 +10643,14 @@ mod rate_limiter { } fn _test_rate_limiter_group_block(bandwidth: bool, num_queues: u32, num_disks: u32) { - let test_timeout = 10; let fio_ops = FioOps::RandRW; let bw_size = if bandwidth { - 10485760_u64 // bytes + 104857600_u64 // bytes } else { - 100_u64 // I/O + 1000_u64 // I/O }; - let bw_refill_time = 100; // ms + let bw_refill_time = 1000; // ms let limit_rate = (bw_size * 1000) as f64 / bw_refill_time as f64; let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); @@ -10660,9 +10659,13 @@ mod rate_limiter { let test_img_dir = TempDir::new_with_prefix("/var/tmp/ch").unwrap(); let rate_limit_group_arg = if bandwidth { - format!("id=group0,bw_size={bw_size},bw_refill_time={bw_refill_time}") + format!( + "id=group0,bw_size={bw_size},bw_one_time_burst=0,bw_refill_time={bw_refill_time}" + ) } else { - format!("id=group0,ops_size={bw_size},ops_refill_time={bw_refill_time}") + format!( + "id=group0,ops_size={bw_size},ops_one_time_burst=0,ops_refill_time={bw_refill_time}" + ) }; let mut disk_args = vec![ @@ -10718,7 +10721,7 @@ mod rate_limiter { let mut fio_command = format!( "sudo fio --name=global --output-format=json \ --direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \ - --rw={fio_ops} --runtime={test_timeout} --numjobs={num_queues}" + --rw={fio_ops} --runtime={BLOCK_RATE_LIMITER_RUNTIME} --numjobs={num_queues}" ); // Generate additional argument for each disk: From 9db5f0439ec3fc9b8e040f8223539ad03c1be215 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 11:46:49 +0200 Subject: [PATCH 1031/1893] 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 --- cloud-hypervisor/tests/integration.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 07acd2d856..bd4168d771 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -10477,6 +10477,7 @@ mod rate_limiter { const NET_RATE_LIMITER_RUNTIME: u32 = 20; const BLOCK_RATE_LIMITER_RUNTIME: u32 = 20; + const BLOCK_RATE_LIMITER_RAMP_TIME: u32 = 5; // Check if the 'measured' rate is within the expected 'difference' (in percentage) // compared to given 'limit' rate. @@ -10624,7 +10625,8 @@ mod rate_limiter { let fio_command = format!( "sudo fio --filename=/dev/vdc --name=test --output-format=json \ --direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \ - --rw={fio_ops} --runtime={BLOCK_RATE_LIMITER_RUNTIME} --numjobs={num_queues}" + --rw={fio_ops} --runtime={BLOCK_RATE_LIMITER_RUNTIME} \ + --ramp_time={BLOCK_RATE_LIMITER_RAMP_TIME} --numjobs={num_queues}", ); let output = guest.ssh_command(&fio_command).unwrap(); @@ -10721,7 +10723,8 @@ mod rate_limiter { let mut fio_command = format!( "sudo fio --name=global --output-format=json \ --direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \ - --rw={fio_ops} --runtime={BLOCK_RATE_LIMITER_RUNTIME} --numjobs={num_queues}" + --rw={fio_ops} --runtime={BLOCK_RATE_LIMITER_RUNTIME} \ + --ramp_time={BLOCK_RATE_LIMITER_RAMP_TIME} --numjobs={num_queues}", ); // Generate additional argument for each disk: From 98dbe6d1283be476ccf499c163a00e3fa331f99e Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:47:43 -0700 Subject: [PATCH 1032/1893] 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 --- block/src/raw_async_aio.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 66f7a667bf..790525a008 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -19,6 +19,7 @@ use crate::async_io::{ }; use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support, query_device_size}; +#[derive(Debug)] pub struct RawFileDiskAio { file: File, } From fca54cb14294f8961dfe6f9582c1e6fd3d3603bc Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:48:02 -0700 Subject: [PATCH 1033/1893] 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 --- block/src/raw_async_aio.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 790525a008..c2da2ccd19 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -17,7 +17,8 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{ AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, }; -use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support, query_device_size}; +use crate::error::{BlockError, BlockErrorKind, BlockResult}; +use crate::{DiskTopology, SECTOR_SIZE, disk_file, probe_sparse_support, query_device_size}; #[derive(Debug)] pub struct RawFileDiskAio { @@ -69,6 +70,14 @@ impl DiskFile for RawFileDiskAio { } } +impl disk_file::DiskSize for RawFileDiskAio { + fn logical_size(&self) -> BlockResult { + query_device_size(&self.file) + .map(|(logical_size, _)| logical_size) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e))) + } +} + pub struct RawFileAsyncAio { fd: RawFd, ctx: aio::IoContext, From 78bbdbef86cf181a8322a997ab7acaebf1a9b0f2 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:48:18 -0700 Subject: [PATCH 1034/1893] 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 --- block/src/raw_async_aio.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index c2da2ccd19..c627318cde 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -78,6 +78,14 @@ impl disk_file::DiskSize for RawFileDiskAio { } } +impl disk_file::PhysicalSize for RawFileDiskAio { + fn physical_size(&self) -> BlockResult { + query_device_size(&self.file) + .map(|(_, physical_size)| physical_size) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e))) + } +} + pub struct RawFileAsyncAio { fd: RawFd, ctx: aio::IoContext, From df890599c20a9b29f4b266c9bde635531f9650da Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:48:31 -0700 Subject: [PATCH 1035/1893] 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 --- block/src/raw_async_aio.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index c627318cde..86b9271cfb 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -86,6 +86,12 @@ impl disk_file::PhysicalSize for RawFileDiskAio { } } +impl disk_file::DiskFd for RawFileDiskAio { + fn fd(&self) -> BorrowedDiskFd<'_> { + BorrowedDiskFd::new(self.file.as_raw_fd()) + } +} + pub struct RawFileAsyncAio { fd: RawFd, ctx: aio::IoContext, From 190380c9bad3563f6d5bfb3885aafe6ea09b45fd Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:49:01 -0700 Subject: [PATCH 1036/1893] 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 --- block/src/raw_async_aio.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 86b9271cfb..6c1fe0aad6 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -92,6 +92,15 @@ impl disk_file::DiskFd for RawFileDiskAio { } } +impl disk_file::Geometry for RawFileDiskAio { + fn topology(&self) -> DiskTopology { + DiskTopology::probe(&self.file).unwrap_or_else(|_| { + warn!("Unable to get device topology. Using default topology"); + DiskTopology::default() + }) + } +} + pub struct RawFileAsyncAio { fd: RawFd, ctx: aio::IoContext, From b7cf8737acbaeed0cb80129d468a4a0e773d2785 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:49:15 -0700 Subject: [PATCH 1037/1893] 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 --- block/src/raw_async_aio.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 6c1fe0aad6..b12469312c 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -101,6 +101,12 @@ impl disk_file::Geometry for RawFileDiskAio { } } +impl disk_file::SparseCapable for RawFileDiskAio { + fn supports_sparse_operations(&self) -> bool { + probe_sparse_support(&self.file) + } +} + pub struct RawFileAsyncAio { fd: RawFd, ctx: aio::IoContext, From 62264cb3c730a8a53f0c18e0deb761e85dbaa5d7 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:49:28 -0700 Subject: [PATCH 1038/1893] 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 --- block/src/raw_async_aio.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index b12469312c..7e7ff26c69 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -107,6 +107,14 @@ impl disk_file::SparseCapable for RawFileDiskAio { } } +impl disk_file::Resizable for RawFileDiskAio { + fn resize(&mut self, size: u64) -> BlockResult<()> { + self.file + .set_len(size) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e))) + } +} + pub struct RawFileAsyncAio { fd: RawFd, ctx: aio::IoContext, From 39bbbaaa598506fd6827ac7356489a70a11fe67a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:49:42 -0700 Subject: [PATCH 1039/1893] 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 --- block/src/raw_async_aio.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 7e7ff26c69..9a990c1050 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -115,6 +115,8 @@ impl disk_file::Resizable for RawFileDiskAio { } } +impl disk_file::DiskFile for RawFileDiskAio {} + pub struct RawFileAsyncAio { fd: RawFd, ctx: aio::IoContext, From 6df7cda4ad342d65fcb66a25e3e469f7be6bb20d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:49:58 -0700 Subject: [PATCH 1040/1893] 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 --- block/src/raw_async_aio.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 9a990c1050..74e1611525 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -117,6 +117,24 @@ impl disk_file::Resizable for RawFileDiskAio { impl disk_file::DiskFile for RawFileDiskAio {} +impl disk_file::AsyncDiskFile for RawFileDiskAio { + fn try_clone(&self) -> BlockResult> { + let file = self + .file + .try_clone() + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?; + Ok(Box::new(RawFileDiskAio { file })) + } + + fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + let mut raw = RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth) + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)))?; + raw.alignment = + DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); + Ok(Box::new(raw) as Box) + } +} + pub struct RawFileAsyncAio { fd: RawFd, ctx: aio::IoContext, From 783cc8bbd9142c17eef6d10a6d308e01e9a46cc1 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:51:43 -0700 Subject: [PATCH 1041/1893] 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 --- block/src/raw_async_aio.rs | 43 +------------------------------------- vmm/src/device_manager.rs | 3 +-- 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 74e1611525..f320be2115 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -14,9 +14,7 @@ use log::warn; use vmm_sys_util::aio; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{ - AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult, -}; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::error::{BlockError, BlockErrorKind, BlockResult}; use crate::{DiskTopology, SECTOR_SIZE, disk_file, probe_sparse_support, query_device_size}; @@ -31,45 +29,6 @@ impl RawFileDiskAio { } } -impl DiskFile for RawFileDiskAio { - fn logical_size(&mut self) -> DiskFileResult { - Ok(query_device_size(&self.file) - .map_err(DiskFileError::Size)? - .0) - } - - fn physical_size(&mut self) -> DiskFileResult { - Ok(query_device_size(&self.file) - .map_err(DiskFileError::Size)? - .1) - } - - fn new_async_io(&self, ring_depth: u32) -> DiskFileResult> { - let mut raw = RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth) - .map_err(DiskFileError::NewAsyncIo)?; - raw.alignment = - DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); - Ok(Box::new(raw) as Box) - } - - fn topology(&mut self) -> DiskTopology { - if let Ok(topology) = DiskTopology::probe(&self.file) { - topology - } else { - warn!("Unable to get device topology. Using default topology"); - DiskTopology::default() - } - } - - fn supports_sparse_operations(&self) -> bool { - probe_sparse_support(&self.file) - } - - fn fd(&mut self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.file.as_raw_fd()) - } -} - impl disk_file::DiskSize for RawFileDiskAio { fn logical_size(&self) -> BlockResult { query_device_size(&self.file) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 72ce17b26a..747e6311c1 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -32,7 +32,6 @@ use arch::layout::{APIC_START, IOAPIC_SIZE, IOAPIC_START}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use arch::{DeviceType, MmioDeviceInfo}; use arch::{NumaNodes, layout}; -use block::async_io::DiskFile; use block::disk_file::DiskBackend; use block::error::BlockError; use block::fixed_vhd_sync::FixedVhdDiskSync; @@ -2760,7 +2759,7 @@ impl DeviceManager { } } else if !disk_cfg.disable_aio && self.aio_is_supported() { info!("Using asynchronous RAW disk file (aio)"); - DiskBackend::Legacy(Box::new(RawFileDiskAio::new(file)) as Box) + DiskBackend::Next(Box::new(RawFileDiskAio::new(file))) } else { info!("Using synchronous RAW disk file"); DiskBackend::Next(Box::new(RawFileDiskSync::new(file))) From f6b4061629f0c6c4afd90aa2453dd10976c94101 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 27 Mar 2026 23:55:41 -0700 Subject: [PATCH 1042/1893] block: Return BlockResult from RawFileAsyncAio::new() Change the return type of RawFileAsyncAio::new() from std::io::Result to BlockResult, 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 --- block/src/raw_async_aio.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index f320be2115..980f8d13a7 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -86,8 +86,7 @@ impl disk_file::AsyncDiskFile for RawFileDiskAio { } fn new_async_io(&self, ring_depth: u32) -> BlockResult> { - let mut raw = RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth) - .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)))?; + let mut raw = RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)?; raw.alignment = DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); Ok(Box::new(raw) as Box) @@ -103,9 +102,11 @@ pub struct RawFileAsyncAio { } impl RawFileAsyncAio { - pub fn new(fd: RawFd, queue_depth: u32) -> std::io::Result { - let eventfd = EventFd::new(libc::EFD_NONBLOCK)?; - let ctx = aio::IoContext::new(queue_depth)?; + pub fn new(fd: RawFd, queue_depth: u32) -> BlockResult { + let eventfd = + EventFd::new(libc::EFD_NONBLOCK).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; + let ctx = + aio::IoContext::new(queue_depth).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; Ok(RawFileAsyncAio { fd, From 8026eb177f892fc39f27331842950bc0174d8a54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 04:16:59 +0000 Subject: [PATCH 1043/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 81baf1e3af..de6391186a 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.44.0 + - uses: crate-ci/typos@v1.45.0 From c60168256a1befccf10172afc192f702cd39968c Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 31 Mar 2026 04:28:41 +0000 Subject: [PATCH 1044/1893] 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 --- net_util/src/ctrl_queue.rs | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/net_util/src/ctrl_queue.rs b/net_util/src/ctrl_queue.rs index 284b6ec4e6..f449d5527a 100644 --- a/net_util/src/ctrl_queue.rs +++ b/net_util/src/ctrl_queue.rs @@ -2,12 +2,16 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use thiserror::Error; use virtio_bindings::virtio_net::{ - VIRTIO_NET_CTRL_GUEST_OFFLOADS, VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, VIRTIO_NET_CTRL_MQ, - VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN, - VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET, VIRTIO_NET_ERR, VIRTIO_NET_OK, + VIRTIO_NET_CTRL_ANNOUNCE, VIRTIO_NET_CTRL_ANNOUNCE_ACK, VIRTIO_NET_CTRL_GUEST_OFFLOADS, + VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, VIRTIO_NET_CTRL_MQ, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX, + VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET, VIRTIO_NET_CTRL_RX, + VIRTIO_NET_CTRL_RX_ALLMULTI, VIRTIO_NET_CTRL_RX_ALLUNI, VIRTIO_NET_CTRL_RX_NOBCAST, + VIRTIO_NET_CTRL_RX_NOMULTI, VIRTIO_NET_CTRL_RX_NOUNI, VIRTIO_NET_CTRL_RX_PROMISC, + VIRTIO_NET_CTRL_VLAN, VIRTIO_NET_CTRL_VLAN_ADD, VIRTIO_NET_CTRL_VLAN_DEL, VIRTIO_NET_ERR, + VIRTIO_NET_OK, }; use virtio_queue::{Queue, QueueT}; use vm_memory::{ByteValued, Bytes, GuestMemoryError}; @@ -53,6 +57,26 @@ pub struct ControlHeader { // SAFETY: ControlHeader only contains a series of integers unsafe impl ByteValued for ControlHeader {} +fn is_tolerated_ctrl_command(ctrl_hdr: ControlHeader) -> bool { + match u32::from(ctrl_hdr.class) { + VIRTIO_NET_CTRL_RX => matches!( + u32::from(ctrl_hdr.cmd), + VIRTIO_NET_CTRL_RX_PROMISC + | VIRTIO_NET_CTRL_RX_ALLMULTI + | VIRTIO_NET_CTRL_RX_ALLUNI + | VIRTIO_NET_CTRL_RX_NOMULTI + | VIRTIO_NET_CTRL_RX_NOUNI + | VIRTIO_NET_CTRL_RX_NOBCAST + ), + VIRTIO_NET_CTRL_VLAN => matches!( + u32::from(ctrl_hdr.cmd), + VIRTIO_NET_CTRL_VLAN_ADD | VIRTIO_NET_CTRL_VLAN_DEL + ), + VIRTIO_NET_CTRL_ANNOUNCE => u32::from(ctrl_hdr.cmd) == VIRTIO_NET_CTRL_ANNOUNCE_ACK, + _ => false, + } +} + pub struct CtrlQueue { pub taps: Vec, } @@ -128,6 +152,10 @@ impl CtrlQueue { false } } + _ if is_tolerated_ctrl_command(ctrl_hdr) => { + debug!("Ignoring unsupported but tolerated control command {ctrl_hdr:?}"); + true + } _ => { warn!("Unsupported command {ctrl_hdr:?}"); false From 7461143194e1367869ae05dde72f1f531c1d980f Mon Sep 17 00:00:00 2001 From: CMGS Date: Tue, 31 Mar 2026 14:30:31 +0000 Subject: [PATCH 1045/1893] 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 --- net_util/src/ctrl_queue.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net_util/src/ctrl_queue.rs b/net_util/src/ctrl_queue.rs index f449d5527a..e42b4c0ca5 100644 --- a/net_util/src/ctrl_queue.rs +++ b/net_util/src/ctrl_queue.rs @@ -171,7 +171,9 @@ impl CtrlQueue { .translate_gva(access_platform, status_desc.len() as usize), ) .map_err(Error::GuestMemory)?; - let len = ctrl_desc.len() + data_desc.len() + status_desc.len(); + // Per virtio spec 2.6.8, used_len is the number of bytes written + // to device-writable descriptors. Only the status byte is written. + let len = status_desc.len(); queue .add_used(desc_chain.memory(), desc_chain.head_index(), len) From ef9133a3eedb832e0d47ea243f7705895274707f Mon Sep 17 00:00:00 2001 From: Chinmoy Date: Mon, 16 Feb 2026 22:28:31 +0530 Subject: [PATCH 1046/1893] vmm: acpi: Take &T instead of &Arc> Refactor ACPI table creation functions to accept borrowed references, removing double indirection and moving locking to callers. Signed-off-by: Chinmoy --- vmm/src/acpi.rs | 79 ++++++++++++++++----------------------- vmm/src/device_manager.rs | 4 +- vmm/src/vm.rs | 18 ++++----- 3 files changed, 43 insertions(+), 58 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index 8f46b20ddb..ac05306bcf 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: Apache-2.0 // -use std::sync::{Arc, Mutex}; use std::time::Instant; use acpi_tables::Aml; @@ -192,7 +191,7 @@ bitflags! { impl MemoryAffinity { fn from_region( - region: &Arc, + region: &GuestRegionMmap, proximity_domain: u32, flags: MemAffinityFlags, ) -> Self { @@ -258,9 +257,9 @@ struct ViotPciRangeNode { } pub fn create_dsdt_table( - device_manager: &Arc>, - cpu_manager: &Arc>, - memory_manager: &Arc>, + device_manager: &DeviceManager, + cpu_manager: &CpuManager, + memory_manager: &MemoryManager, ) -> Sdt { trace_scoped!("create_dsdt_table"); // DSDT @@ -268,9 +267,9 @@ pub fn create_dsdt_table( let mut bytes = Vec::new(); - device_manager.lock().unwrap().to_aml_bytes(&mut bytes); - cpu_manager.lock().unwrap().to_aml_bytes(&mut bytes); - memory_manager.lock().unwrap().to_aml_bytes(&mut bytes); + device_manager.to_aml_bytes(&mut bytes); + cpu_manager.to_aml_bytes(&mut bytes); + memory_manager.to_aml_bytes(&mut bytes); dsdt.append_slice(&bytes); dsdt @@ -278,14 +277,13 @@ pub fn create_dsdt_table( const FACP_DSDT_OFFSET: usize = 140; -fn create_facp_table(dsdt_offset: GuestAddress, device_manager: &Arc>) -> Sdt { +fn create_facp_table(dsdt_offset: GuestAddress, device_manager: &DeviceManager) -> Sdt { trace_scoped!("create_facp_table"); // Revision 6 of the ACPI FADT table is 276 bytes long let mut facp = Sdt::new(*b"FACP", 276, 6, *b"CLOUDH", *b"CHFACP ", 1); { - let device_manager = device_manager.lock().unwrap(); if let Some(address) = device_manager.acpi_platform_addresses().reset_reg_address { // RESET_REG facp.write(116, address); @@ -369,7 +367,7 @@ fn create_tpm2_table() -> Sdt { fn create_srat_table( numa_nodes: &NumaNodes, - device_manager: &Arc>, + device_manager: &DeviceManager, #[cfg(target_arch = "x86_64")] topology: Option<(u16, u16, u16, u16)>, ) -> Sdt { let mut srat = Sdt::new(*b"SRAT", 36, 3, *b"CLOUDH", *b"CHSRAT ", 1); @@ -381,7 +379,6 @@ fn create_srat_table( assert_eq!(std::mem::size_of::(), 40); // Confirm struct size matches ACPI 6.6 spec assert_eq!(std::mem::size_of::(), 32); - let dm = device_manager.lock().unwrap(); for (node_id, node) in numa_nodes.iter() { let proximity_domain = *node_id; @@ -436,7 +433,7 @@ fn create_srat_table( // Add Generic Initiator Affinity structures for device-only NUMA nodes if let Some(device_id) = &node.device_id { // Resolve device_id to guest BDF - if let Some(bdf) = dm.get_device_bdf(device_id) { + if let Some(bdf) = device_manager.get_device_bdf(device_id) { srat.append(GenericInitiatorAffinity::from_pci_bdf( bdf, proximity_domain, @@ -852,9 +849,9 @@ fn create_viot_table(iommu_bdf: &PciBdf, devices_bdf: &[PciBdf]) -> Sdt { // * `Vec` contains a list of table pointers stored in XSDT. fn create_acpi_tables_internal( dsdt_addr: GuestAddress, - device_manager: &Arc>, - cpu_manager: &Arc>, - memory_manager: &Arc>, + device_manager: &DeviceManager, + cpu_manager: &CpuManager, + memory_manager: &MemoryManager, numa_nodes: &NumaNodes, tpm_enabled: bool, ) -> (Rsdp, Vec, Vec) { @@ -876,15 +873,13 @@ fn create_acpi_tables_internal( // MADT #[cfg(target_arch = "aarch64")] let vgic = device_manager - .lock() - .unwrap() .get_interrupt_controller() .unwrap() .lock() .unwrap() .get_vgic() .unwrap(); - let madt = cpu_manager.lock().unwrap().create_madt( + let madt = cpu_manager.create_madt( #[cfg(target_arch = "aarch64")] vgic, ); @@ -897,7 +892,7 @@ fn create_acpi_tables_internal( // PPTT #[cfg(target_arch = "aarch64")] { - let pptt = cpu_manager.lock().unwrap().create_pptt(); + let pptt = cpu_manager.create_pptt(); let pptt_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(pptt.as_slice()); xsdt_table_pointers.push(pptt_addr.0); @@ -917,7 +912,7 @@ fn create_acpi_tables_internal( } // MCFG - let mcfg = create_mcfg_table(device_manager.lock().unwrap().pci_segments()); + let mcfg = create_mcfg_table(device_manager.pci_segments()); let mcfg_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(mcfg.as_slice()); xsdt_table_pointers.push(mcfg_addr.0); @@ -928,16 +923,12 @@ fn create_acpi_tables_internal( #[cfg(target_arch = "aarch64")] { let is_serial_on = device_manager - .lock() - .unwrap() .get_device_info() .clone() .contains_key(&(DeviceType::Serial, DeviceType::Serial.to_string())); let serial_device_addr = arch::layout::LEGACY_SERIAL_MAPPED_IO_START.raw_value(); let serial_device_irq = if is_serial_on { device_manager - .lock() - .unwrap() .get_device_info() .clone() .get(&(DeviceType::Serial, DeviceType::Serial.to_string())) @@ -979,7 +970,7 @@ fn create_acpi_tables_internal( // Only created if the NUMA nodes list is not empty. if !numa_nodes.is_empty() { #[cfg(target_arch = "x86_64")] - let topology = cpu_manager.lock().unwrap().get_vcpu_topology(); + let topology = cpu_manager.get_vcpu_topology(); // SRAT let srat = create_srat_table( numa_nodes, @@ -1003,7 +994,7 @@ fn create_acpi_tables_internal( #[cfg(target_arch = "aarch64")] { - let iort = create_iort_table(device_manager.lock().unwrap().pci_segments()); + let iort = create_iort_table(device_manager.pci_segments()); let iort_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); tables_bytes.extend_from_slice(iort.as_slice()); xsdt_table_pointers.push(iort_addr.0); @@ -1012,8 +1003,7 @@ fn create_acpi_tables_internal( } // VIOT - if let Some((iommu_bdf, devices_bdf)) = device_manager.lock().unwrap().iommu_attached_devices() - { + if let Some((iommu_bdf, devices_bdf)) = device_manager.iommu_attached_devices() { let viot = create_viot_table(iommu_bdf, devices_bdf); let viot_addr = prev_tbl_addr.checked_add(prev_tbl_len).unwrap(); @@ -1040,9 +1030,9 @@ fn create_acpi_tables_internal( #[cfg(feature = "fw_cfg")] pub fn create_acpi_tables_for_fw_cfg( - device_manager: &Arc>, - cpu_manager: &Arc>, - memory_manager: &Arc>, + device_manager: &DeviceManager, + cpu_manager: &CpuManager, + memory_manager: &MemoryManager, numa_nodes: &NumaNodes, tpm_enabled: bool, ) -> Result<(), crate::vm::Error> { @@ -1087,8 +1077,6 @@ pub fn create_acpi_tables_for_fw_cfg( checksums.push(xsdt_checksum); device_manager - .lock() - .unwrap() .fw_cfg() .expect("fw_cfg must be present") .lock() @@ -1099,9 +1087,9 @@ pub fn create_acpi_tables_for_fw_cfg( pub fn create_acpi_tables( guest_mem: &GuestMemoryMmap, - device_manager: &Arc>, - cpu_manager: &Arc>, - memory_manager: &Arc>, + device_manager: &DeviceManager, + cpu_manager: &CpuManager, + memory_manager: &MemoryManager, numa_nodes: &NumaNodes, tpm_enabled: bool, ) -> GuestAddress { @@ -1139,9 +1127,9 @@ pub fn create_acpi_tables( #[cfg(feature = "tdx")] pub fn create_acpi_tables_tdx( - device_manager: &Arc>, - cpu_manager: &Arc>, - memory_manager: &Arc>, + device_manager: &DeviceManager, + cpu_manager: &CpuManager, + memory_manager: &MemoryManager, numa_nodes: &NumaNodes, ) -> Vec { // DSDT @@ -1155,18 +1143,16 @@ pub fn create_acpi_tables_tdx( tables.push(create_facp_table(GuestAddress(0), device_manager)); // MADT - tables.push(cpu_manager.lock().unwrap().create_madt()); + tables.push(cpu_manager.create_madt()); // MCFG - tables.push(create_mcfg_table( - device_manager.lock().unwrap().pci_segments(), - )); + tables.push(create_mcfg_table(device_manager.pci_segments())); // SRAT and SLIT // Only created if the NUMA nodes list is not empty. if !numa_nodes.is_empty() { #[cfg(target_arch = "x86_64")] - let topology = cpu_manager.lock().unwrap().get_vcpu_topology(); + let topology = cpu_manager.get_vcpu_topology(); // SRAT tables.push(create_srat_table( @@ -1181,8 +1167,7 @@ pub fn create_acpi_tables_tdx( } // VIOT - if let Some((iommu_bdf, devices_bdf)) = device_manager.lock().unwrap().iommu_attached_devices() - { + if let Some((iommu_bdf, devices_bdf)) = device_manager.iommu_attached_devices() { tables.push(create_viot_table(iommu_bdf, devices_bdf)); } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 747e6311c1..b6d35964f9 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1756,7 +1756,7 @@ impl DeviceManager { } #[cfg(target_arch = "aarch64")] - pub fn get_interrupt_controller(&mut self) -> Option<&Arc>> { + pub fn get_interrupt_controller(&self) -> Option<&Arc>> { self.interrupt_controller.as_ref() } @@ -1793,7 +1793,7 @@ impl DeviceManager { } #[cfg(target_arch = "riscv64")] - pub fn get_interrupt_controller(&mut self) -> Option<&Arc>> { + pub fn get_interrupt_controller(&self) -> Option<&Arc>> { self.interrupt_controller.as_ref() } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index c1e21a5ac9..7a5a19133a 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2514,9 +2514,9 @@ impl Vm { // Loop over the ACPI tables and copy them to the HOB. for acpi_table in crate::acpi::create_acpi_tables_tdx( - &self.device_manager, - &self.cpu_manager, - &self.memory_manager, + &self.device_manager.lock().unwrap(), + &self.cpu_manager.lock().unwrap(), + &self.memory_manager.lock().unwrap(), &self.numa_nodes, ) { hob.add_acpi_table(&mem, acpi_table.as_slice()) @@ -2576,9 +2576,9 @@ impl Vm { let tpm_enabled = self.config.lock().unwrap().tpm.is_some(); let rsdp_addr = crate::acpi::create_acpi_tables( &mem, - &self.device_manager, - &self.cpu_manager, - &self.memory_manager, + &self.device_manager.lock().unwrap(), + &self.cpu_manager.lock().unwrap(), + &self.memory_manager.lock().unwrap(), &self.numa_nodes, tpm_enabled, ); @@ -2643,9 +2643,9 @@ impl Vm { if fw_cfg_config.acpi_tables { let tpm_enabled = self.config.lock().unwrap().tpm.is_some(); crate::acpi::create_acpi_tables_for_fw_cfg( - &self.device_manager, - &self.cpu_manager, - &self.memory_manager, + &self.device_manager.lock().unwrap(), + &self.cpu_manager.lock().unwrap(), + &self.memory_manager.lock().unwrap(), &self.numa_nodes, tpm_enabled, )?; From d0b253472d3c69283a55fd22891b7b8d0a2e3c20 Mon Sep 17 00:00:00 2001 From: Chinmoy Date: Mon, 16 Feb 2026 22:31:30 +0530 Subject: [PATCH 1047/1893] pci, devices, virtio-devices, vmm: Refactor allocate_bars Refactor PciDevice::allocate_bars trait and all implementations to take &mut SystemAllocator instead of &Arc>, removing double indirection. The caller in device_manager.rs now acquires the lock before calling allocate_bars. Signed-off-by: Chinmoy --- devices/src/ivshmem.rs | 2 +- devices/src/pvmemcontrol.rs | 4 ++-- devices/src/pvpanic.rs | 4 ++-- pci/src/device.rs | 4 ++-- pci/src/vfio.rs | 6 ++---- pci/src/vfio_user.rs | 2 +- virtio-devices/src/transport/pci_device.rs | 2 +- vmm/src/device_manager.rs | 2 +- 8 files changed, 12 insertions(+), 14 deletions(-) diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs index fff48a72c6..98291c74e8 100644 --- a/devices/src/ivshmem.rs +++ b/devices/src/ivshmem.rs @@ -217,7 +217,7 @@ impl BusDevice for IvshmemDevice { impl PciDevice for IvshmemDevice { fn allocate_bars( &mut self, - _allocator: &Arc>, + _allocator: &mut SystemAllocator, mmio32_allocator: &mut AddressAllocator, mmio64_allocator: &mut AddressAllocator, resources: Option>, diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index 06e0e24923..d4b37456be 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use std::ffi::CString; -use std::sync::{Arc, Barrier, Mutex, RwLock}; +use std::sync::{Arc, Barrier, RwLock}; use std::{io, result}; use log::{debug, warn}; @@ -722,7 +722,7 @@ impl PciDevice for PvmemcontrolPciDevice { fn allocate_bars( &mut self, - _allocator: &Arc>, + _allocator: &mut SystemAllocator, mmio32_allocator: &mut AddressAllocator, _mmio64_allocator: &mut AddressAllocator, resources: Option>, diff --git a/devices/src/pvpanic.rs b/devices/src/pvpanic.rs index 0451da1a05..9540a91252 100644 --- a/devices/src/pvpanic.rs +++ b/devices/src/pvpanic.rs @@ -5,7 +5,7 @@ use std::any::Any; use std::result; -use std::sync::{Arc, Barrier, Mutex}; +use std::sync::{Arc, Barrier}; use anyhow::anyhow; use event_monitor::event; @@ -174,7 +174,7 @@ impl PciDevice for PvPanicDevice { fn allocate_bars( &mut self, - _allocator: &Arc>, + _allocator: &mut SystemAllocator, mmio32_allocator: &mut AddressAllocator, _mmio64_allocator: &mut AddressAllocator, resources: Option>, diff --git a/pci/src/device.rs b/pci/src/device.rs index 3a23ea7772..29c89b8c42 100644 --- a/pci/src/device.rs +++ b/pci/src/device.rs @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause use std::any::Any; -use std::sync::{Arc, Barrier, Mutex}; +use std::sync::{Arc, Barrier}; use std::{io, result}; use serde::{Deserialize, Serialize}; @@ -49,7 +49,7 @@ pub trait PciDevice: Send { /// returns an address. Returns a Vec of (GuestAddress, GuestUsize) tuples. fn allocate_bars( &mut self, - _allocator: &Arc>, + _allocator: &mut SystemAllocator, _mmio32_allocator: &mut AddressAllocator, _mmio64_allocator: &mut AddressAllocator, _resources: Option>, diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index f1d22ff63c..53c77a95c1 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -602,7 +602,7 @@ impl VfioCommon { #[allow(unused_variables)] pub(crate) fn allocate_bars( &mut self, - allocator: &Arc>, + allocator: &mut SystemAllocator, mmio32_allocator: &mut AddressAllocator, mmio64_allocator: &mut AddressAllocator, resources: Option<&[Resource]>, @@ -741,8 +741,6 @@ impl VfioCommon { PciBarRegionType::IoRegion => { // The address needs to be 4 bytes aligned. allocator - .lock() - .unwrap() .allocate_io_addresses(restored_bar_addr, region_size, Some(0x4)) .ok_or(PciDeviceError::IoAllocationFailed(region_size))? } @@ -1852,7 +1850,7 @@ const PCI_ROM_EXP_BAR_INDEX: usize = 12; impl PciDevice for VfioPciDevice { fn allocate_bars( &mut self, - allocator: &Arc>, + allocator: &mut SystemAllocator, mmio32_allocator: &mut AddressAllocator, mmio64_allocator: &mut AddressAllocator, resources: Option>, diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index 248f87272a..456047d42d 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -391,7 +391,7 @@ impl Vfio for VfioUserClientWrapper { impl PciDevice for VfioUserPciDevice { fn allocate_bars( &mut self, - allocator: &Arc>, + allocator: &mut SystemAllocator, mmio32_allocator: &mut AddressAllocator, mmio64_allocator: &mut AddressAllocator, resources: Option>, diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index dd54472d39..36975e3f7f 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -978,7 +978,7 @@ impl PciDevice for VirtioPciDevice { fn allocate_bars( &mut self, - _allocator: &Arc>, + _allocator: &mut SystemAllocator, mmio32_allocator: &mut AddressAllocator, mmio64_allocator: &mut AddressAllocator, resources: Option>, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index b6d35964f9..edb8b76af0 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3995,7 +3995,7 @@ impl DeviceManager { .lock() .unwrap() .allocate_bars( - &self.address_manager.allocator, + &mut self.address_manager.allocator.lock().unwrap(), &mut self.pci_segments[segment_id as usize] .mem32_allocator .lock() From 5ae329305a047a68dce6f58afb83633ecff53e6a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 1 Apr 2026 21:23:37 +0200 Subject: [PATCH 1048/1893] 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 --- virtio-devices/src/block.rs | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 1080b71bcb..bd740fc4a5 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -974,24 +974,24 @@ impl Block { } } - fn update_writeback(&mut self) { - // Use writeback from config if VIRTIO_BLK_F_CONFIG_WCE - let writeback = if self.common.feature_acked(VIRTIO_BLK_F_CONFIG_WCE.into()) { - self.config.writeback == 1 - } else { - // Else check if VIRTIO_BLK_F_FLUSH negotiated - self.common.feature_acked(VIRTIO_BLK_F_FLUSH.into()) - }; + /// The virtio v1.2 spec says "If VIRTIO_BLK_F_CONFIG_WCE was not + /// negotiated but VIRTIO_BLK_F_FLUSH was, the driver SHOULD assume + /// presence of a writeback cache." It also says "If + /// VIRTIO_BLK_F_CONFIG_WCE is negotiated but VIRTIO_BLK_F_FLUSH is not, + /// the device MUST initialize writeback to 0." + fn is_writeback_enabled(&self, desired: bool) -> bool { + let flush = self.common.feature_acked(VIRTIO_BLK_F_FLUSH.into()); + let wce = self.common.feature_acked(VIRTIO_BLK_F_CONFIG_WCE.into()); + if wce { flush && desired } else { flush } + } + fn set_writeback_mode(&mut self, enabled: bool) { + self.config.writeback = enabled as u8; + self.writeback.store(enabled, Ordering::Release); info!( "Changing cache mode to {}", - if writeback { - "writeback" - } else { - "writethrough" - } + if enabled { "writeback" } else { "writethrough" } ); - self.writeback.store(writeback, Ordering::Release); } pub fn resize(&mut self, new_size: u64) -> Result<()> { @@ -1073,8 +1073,8 @@ impl VirtioDevice for Block { return; } - self.config.writeback = data[0]; - self.update_writeback(); + let writeback = self.is_writeback_enabled(data[0] == 1); + self.set_writeback_mode(writeback); } fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { @@ -1097,7 +1097,8 @@ impl VirtioDevice for Block { // Recompute the barrier size from the queues that are actually activated. self.common.paused_sync = Some(Arc::new(Barrier::new(queues.len() + 1))); - self.update_writeback(); + let writeback = self.is_writeback_enabled(self.config.writeback == 1); + self.set_writeback_mode(writeback); let mut epoll_threads = Vec::new(); let event_idx = self.common.feature_acked(VIRTIO_RING_F_EVENT_IDX.into()); @@ -1167,6 +1168,7 @@ impl VirtioDevice for Block { fn reset(&mut self) -> Option> { let result = self.common.reset(); + self.set_writeback_mode(true); event!("virtio-device", "reset", "id", &self.id); result } From 488927a5eacb257a68fb7467371bf408e161d5ca Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 19:01:56 -0700 Subject: [PATCH 1049/1893] 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 --- performance-metrics/src/performance_tests.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 1022aac19e..fa2f8ac3dc 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -5,7 +5,6 @@ // Performance tests -use std::path::PathBuf; use std::time::Duration; use std::{fs, thread}; @@ -130,22 +129,6 @@ fn performance_test_new_guest(disk_config: Box) -> Guest { const DIRECT_KERNEL_BOOT_CMDLINE: &str = "root=/dev/vda1 console=hvc0 rw systemd.journald.forward_to_console=1"; -// Creates the path for direct kernel boot and return the path. -// For x86_64, this function returns the vmlinux kernel path. -// For AArch64, this function returns the PE kernel path. -fn direct_kernel_boot_path() -> PathBuf { - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - - let mut kernel_path = workload_path; - #[cfg(target_arch = "x86_64")] - kernel_path.push("vmlinux-x86_64"); - #[cfg(target_arch = "aarch64")] - kernel_path.push("Image-arm64"); - - kernel_path -} - fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool { let mut cmd = std::process::Command::new(clh_command("ch-remote")); cmd.args([&format!("--api-socket={api_socket}"), command]); From 4cf10737e4fe6fcd738261eea7d22c910555569c Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 19:02:25 -0700 Subject: [PATCH 1050/1893] 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 --- performance-metrics/src/performance_tests.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index fa2f8ac3dc..c12c1955a7 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -126,9 +126,6 @@ fn performance_test_new_guest(disk_config: Box) -> Guest { Guest::new_from_ip_range(disk_config, "172.19", 0) } -const DIRECT_KERNEL_BOOT_CMDLINE: &str = - "root=/dev/vda1 console=hvc0 rw systemd.journald.forward_to_console=1"; - fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool { let mut cmd = std::process::Command::new(clh_command("ch-remote")); cmd.args([&format!("--api-socket={api_socket}"), command]); From 1400e614cb7379e34f638a82c2c936a72f95593c Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 19:03:17 -0700 Subject: [PATCH 1051/1893] 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 --- performance-metrics/src/performance_tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index c12c1955a7..a015f13c63 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -13,8 +13,6 @@ use thiserror::Error; use crate::{ImageFormat, PerformanceTestControl, PerformanceTestOverrides, mean}; -#[cfg(target_arch = "x86_64")] -pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw"; #[cfg(target_arch = "aarch64")] pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-0-update-tool.raw"; From c8bfac66f4449c8f04b631fb3970bbf0b8bd5f57 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 19:15:54 -0700 Subject: [PATCH 1052/1893] 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 --- cloud-hypervisor/tests/common/utils.rs | 35 ------------------------- test_infra/src/lib.rs | 36 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs index aff1e94ef5..5064e1970e 100644 --- a/cloud-hypervisor/tests/common/utils.rs +++ b/cloud-hypervisor/tests/common/utils.rs @@ -255,41 +255,6 @@ pub(crate) fn prepare_swtpm_daemon(tmp_dir: &TempDir) -> (std::process::Command, (swtpm_command, swtpm_socket_path) } -pub(crate) fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool { - let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args([&format!("--api-socket={api_socket}"), command]); - - if let Some(arg) = arg { - cmd.arg(arg); - } - let output = cmd.output().unwrap(); - if output.status.success() { - true - } else { - eprintln!("Error running ch-remote command: {:?}", &cmd); - let stderr = String::from_utf8_lossy(&output.stderr); - eprintln!("stderr: {stderr}"); - false - } -} - -pub(crate) fn remote_command_w_output( - api_socket: &str, - command: &str, - arg: Option<&str>, -) -> (bool, Vec) { - let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args([&format!("--api-socket={api_socket}"), command]); - - if let Some(arg) = arg { - cmd.arg(arg); - } - - let output = cmd.output().expect("Failed to launch ch-remote"); - - (output.status.success(), output.stdout) -} - pub(crate) fn resize_command( api_socket: &str, desired_vcpus: Option, diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index a299b2c494..248e4efa34 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1776,6 +1776,42 @@ pub fn clh_command(cmd: &str) -> String { String::from(full_path.to_str().unwrap()) } +pub fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool { + let mut cmd = Command::new(clh_command("ch-remote")); + cmd.args([&format!("--api-socket={api_socket}"), command]); + + if let Some(arg) = arg { + cmd.arg(arg); + } + + let output = cmd.output().unwrap(); + if output.status.success() { + true + } else { + eprintln!("Error running ch-remote command: {:?}", &cmd); + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!("stderr: {stderr}"); + false + } +} + +pub fn remote_command_w_output( + api_socket: &str, + command: &str, + arg: Option<&str>, +) -> (bool, Vec) { + let mut cmd = Command::new(clh_command("ch-remote")); + cmd.args([&format!("--api-socket={api_socket}"), command]); + + if let Some(arg) = arg { + cmd.arg(arg); + } + + let output = cmd.output().expect("Failed to launch ch-remote"); + + (output.status.success(), output.stdout) +} + pub fn parse_iperf3_output(output: &[u8], sender: bool, bandwidth: bool) -> Result { std::panic::catch_unwind(|| { let s = String::from_utf8_lossy(output); From 1b479e40eaa31ed26aa6dc065ed0ba07eeabee7b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 19:15:59 -0700 Subject: [PATCH 1053/1893] 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 --- performance-metrics/src/performance_tests.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index a015f13c63..813963481b 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -124,26 +124,6 @@ fn performance_test_new_guest(disk_config: Box) -> Guest { Guest::new_from_ip_range(disk_config, "172.19", 0) } -fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool { - let mut cmd = std::process::Command::new(clh_command("ch-remote")); - cmd.args([&format!("--api-socket={api_socket}"), command]); - - if let Some(arg) = arg { - cmd.arg(arg); - } - let output = cmd.output().unwrap(); - if output.status.success() { - true - } else { - eprintln!( - "Error running ch-remote command: {:?}\nstderr: {}", - &cmd, - String::from_utf8_lossy(&output.stderr) - ); - false - } -} - pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 { let test_timeout = control.test_timeout; let (rx, bandwidth) = control.net_control.unwrap(); From d2f6476149ce7220a5994a3b9cff2c70c7cf3764 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Mon, 9 Mar 2026 10:02:29 +0100 Subject: [PATCH 1054/1893] vmm: move migration socket helpers into transport module On-behalf-of: SAP sebastian.eydam@sap.com Signed-off-by: Sebastian Eydam --- vmm/src/lib.rs | 81 ++++----------------------------- vmm/src/migration_transport.rs | 83 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 71 deletions(-) create mode 100644 vmm/src/migration_transport.rs diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 03edec26b5..071a4abc59 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -6,10 +6,11 @@ use std::collections::HashMap; use std::fs::File; use std::io::{Read, Write, stdout}; -use std::net::{TcpListener, TcpStream}; +use std::net::TcpStream; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::os::unix::net::{UnixListener, UnixStream}; +use std::os::unix::net::UnixStream; use std::panic::AssertUnwindSafe; +#[cfg(feature = "guest_debug")] use std::path::PathBuf; use std::sync::mpsc::{Receiver, RecvError, SendError, Sender}; use std::sync::{Arc, Mutex}; @@ -85,6 +86,7 @@ pub mod interrupt; pub mod landlock; pub mod memory_manager; pub mod migration; +pub mod migration_transport; mod pci_segment; pub mod seccomp_filters; mod serial_manager; @@ -1155,73 +1157,6 @@ impl Vmm { Ok(()) } - fn socket_url_to_path(url: &str) -> result::Result { - url.strip_prefix("unix:") - .ok_or_else(|| { - MigratableError::MigrateSend(anyhow!("Could not extract path from URL: {url}")) - }) - .map(|s| s.into()) - } - - fn send_migration_socket( - destination_url: &str, - ) -> std::result::Result { - if let Some(address) = destination_url.strip_prefix("tcp:") { - info!("Connecting to TCP socket at {address}"); - - let socket = TcpStream::connect(address).map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error connecting to TCP socket: {e}")) - })?; - - Ok(SocketStream::Tcp(socket)) - } else { - let path = Vmm::socket_url_to_path(destination_url)?; - info!("Connecting to UNIX socket at {path:?}"); - - let socket = UnixStream::connect(&path).map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error connecting to UNIX socket: {e}")) - })?; - - Ok(SocketStream::Unix(socket)) - } - } - - fn receive_migration_socket( - receiver_url: &str, - ) -> std::result::Result { - if let Some(address) = receiver_url.strip_prefix("tcp:") { - let listener = TcpListener::bind(address).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error binding to TCP socket: {e}")) - })?; - - let (socket, _addr) = listener.accept().map_err(|e| { - MigratableError::MigrateReceive(anyhow!( - "Error accepting connection on TCP socket: {e}" - )) - })?; - - Ok(SocketStream::Tcp(socket)) - } else { - let path = Vmm::socket_url_to_path(receiver_url)?; - let listener = UnixListener::bind(&path).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error binding to UNIX socket: {e}")) - })?; - - let (socket, _addr) = listener.accept().map_err(|e| { - MigratableError::MigrateReceive(anyhow!( - "Error accepting connection on UNIX socket: {e}" - )) - })?; - - // Remove the UNIX socket file after accepting the connection - std::fs::remove_file(&path).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error removing UNIX socket file: {e}")) - })?; - - Ok(SocketStream::Unix(socket)) - } - } - /// Transmits the given [`MemoryRangeTable`] over the wire if there is at /// least one region. /// @@ -1440,7 +1375,8 @@ impl Vmm { send_data_migration: &VmSendMigrationData, ) -> result::Result<(), MigratableError> { // Set up the socket connection - let mut socket = Self::send_migration_socket(&send_data_migration.destination_url)?; + let mut socket = + migration_transport::send_migration_socket(&send_data_migration.destination_url)?; // Start the migration Request::start().write_to(&mut socket)?; @@ -2481,7 +2417,8 @@ impl RequestHandler for Vmm { ); // Accept the connection and get the socket - let mut socket = Vmm::receive_migration_socket(&receive_data_migration.receiver_url)?; + let mut socket = + migration_transport::receive_migration_socket(&receive_data_migration.receiver_url)?; event!("vm", "migration-receive-started"); @@ -2610,6 +2547,8 @@ const DEVICE_MANAGER_SNAPSHOT_ID: &str = "device-manager"; #[cfg(test)] mod unit_tests { + use std::path::PathBuf; + use super::*; #[cfg(target_arch = "x86_64")] use crate::vm_config::DebugConsoleConfig; diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs new file mode 100644 index 0000000000..820501a3e9 --- /dev/null +++ b/vmm/src/migration_transport.rs @@ -0,0 +1,83 @@ +// Copyright © 2026 Contributors to the Cloud Hypervisor project +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::net::{TcpListener, TcpStream}; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::path::PathBuf; +use std::result::Result; + +use anyhow::anyhow; +use log::info; +use vm_migration::MigratableError; + +use crate::SocketStream; + +/// Extract a UNIX socket path from a "unix:" migration URL. +fn socket_url_to_path(url: &str) -> Result { + url.strip_prefix("unix:") + .ok_or_else(|| anyhow!("Could not extract path from URL: {url}")) + .map(|s| s.into()) +} + +/// Connect to a migration endpoint and return the established stream. +pub(crate) fn send_migration_socket( + destination_url: &str, +) -> Result { + if let Some(address) = destination_url.strip_prefix("tcp:") { + info!("Connecting to TCP socket at {address}"); + + let socket = TcpStream::connect(address).map_err(|e| { + MigratableError::MigrateSend(anyhow!("Error connecting to TCP socket: {e}")) + })?; + + Ok(SocketStream::Tcp(socket)) + } else { + let path = socket_url_to_path(destination_url).map_err(MigratableError::MigrateSend)?; + info!("Connecting to UNIX socket at {path:?}"); + + let socket = UnixStream::connect(&path).map_err(|e| { + MigratableError::MigrateSend(anyhow!("Error connecting to UNIX socket: {e}")) + })?; + + Ok(SocketStream::Unix(socket)) + } +} + +/// Bind and accept a migration connection for the receiver side. +pub(crate) fn receive_migration_socket( + receiver_url: &str, +) -> Result { + if let Some(address) = receiver_url.strip_prefix("tcp:") { + let listener = TcpListener::bind(address).map_err(|e| { + MigratableError::MigrateReceive(anyhow!("Error binding to TCP socket: {e}")) + })?; + + let (socket, _addr) = listener.accept().map_err(|e| { + MigratableError::MigrateReceive(anyhow!( + "Error accepting connection on TCP socket: {e}" + )) + })?; + + Ok(SocketStream::Tcp(socket)) + } else { + let path = socket_url_to_path(receiver_url).map_err(MigratableError::MigrateSend)?; + let listener = UnixListener::bind(&path).map_err(|e| { + MigratableError::MigrateReceive(anyhow!("Error binding to UNIX socket: {e}")) + })?; + + let (socket, _addr) = listener.accept().map_err(|e| { + MigratableError::MigrateReceive(anyhow!( + "Error accepting connection on UNIX socket: {e}" + )) + })?; + + // Remove the UNIX socket file after accepting the connection + std::fs::remove_file(&path).map_err(|e| { + MigratableError::MigrateReceive(anyhow!("Error removing UNIX socket file: {e}")) + })?; + + Ok(SocketStream::Unix(socket)) + } +} From bb3e1b4073eb31c30ab0bb01588fef5c58be42ab Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Thu, 26 Mar 2026 17:27:51 +0100 Subject: [PATCH 1055/1893] 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 --- vmm/src/migration_transport.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 820501a3e9..ddc2337b5e 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -73,11 +73,6 @@ pub(crate) fn receive_migration_socket( )) })?; - // Remove the UNIX socket file after accepting the connection - std::fs::remove_file(&path).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error removing UNIX socket file: {e}")) - })?; - Ok(SocketStream::Unix(socket)) } } From d4a8d55074da94caf770de126e80507af544b2cc Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Mon, 9 Mar 2026 10:32:06 +0100 Subject: [PATCH 1056/1893] vmm: extract small request/response helpers to reduce boilerplate On-behalf-of: SAP sebastian.eydam@sap.com Signed-off-by: Sebastian Eydam --- vmm/src/lib.rs | 14 +++++++------- vmm/src/migration_transport.rs | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 071a4abc59..5926ac70b1 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1176,7 +1176,7 @@ impl Vmm { table.write_to(socket)?; // And then the memory itself vm.send_memory_regions(table, socket)?; - Response::read_from(socket)?.ok_or_abandon( + migration_transport::expect_ok_response( socket, MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")), )?; @@ -1379,9 +1379,9 @@ impl Vmm { migration_transport::send_migration_socket(&send_data_migration.destination_url)?; // Start the migration - Request::start().write_to(&mut socket)?; - Response::read_from(&mut socket)?.ok_or_abandon( + migration_transport::send_request_expect_ok( &mut socket, + Request::start(), MigratableError::MigrateSend(anyhow!("Error starting migration")), )?; @@ -1439,7 +1439,7 @@ impl Vmm { socket .write_all(&config_data) .map_err(MigratableError::MigrateSocket)?; - Response::read_from(&mut socket)?.ok_or_abandon( + migration_transport::expect_ok_response( &mut socket, MigratableError::MigrateSend(anyhow!("Error during config migration")), )?; @@ -1466,15 +1466,15 @@ impl Vmm { socket .write_all(&snapshot_data) .map_err(MigratableError::MigrateSocket)?; - Response::read_from(&mut socket)?.ok_or_abandon( + migration_transport::expect_ok_response( &mut socket, MigratableError::MigrateSend(anyhow!("Error during state migration")), )?; // Complete the migration // At this step, the receiving VMM will acquire disk locks again. - Request::complete().write_to(&mut socket)?; - Response::read_from(&mut socket)?.ok_or_abandon( + migration_transport::send_request_expect_ok( &mut socket, + Request::complete(), MigratableError::MigrateSend(anyhow!("Error completing migration")), )?; diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index ddc2337b5e..990f14473e 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -11,6 +11,7 @@ use std::result::Result; use anyhow::anyhow; use log::info; use vm_migration::MigratableError; +use vm_migration::protocol::{Request, Response}; use crate::SocketStream; @@ -76,3 +77,23 @@ pub(crate) fn receive_migration_socket( Ok(SocketStream::Unix(socket)) } } + +/// Read a response and return Ok(()) if it was a [`Response::Ok`]. +pub(crate) fn expect_ok_response( + socket: &mut SocketStream, + error: MigratableError, +) -> Result<(), MigratableError> { + Response::read_from(socket)? + .ok_or_abandon(socket, error) + .map(|_| ()) +} + +/// Send a request and validate that the peer responds with OK. +pub(crate) fn send_request_expect_ok( + socket: &mut SocketStream, + request: Request, + error: MigratableError, +) -> Result<(), MigratableError> { + request.write_to(socket)?; + expect_ok_response(socket, error) +} From 196e48af308f0201e856f38209e9bbdc1850756c Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Mon, 9 Mar 2026 10:41:27 +0100 Subject: [PATCH 1057/1893] 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 --- vmm/src/lib.rs | 10 +--------- vmm/src/migration_transport.rs | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 5926ac70b1..78ff205d31 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1434,15 +1434,7 @@ impl Vmm { common_cpuid, memory_manager_data: vm.memory_manager_data(), }; - let config_data = serde_json::to_vec(&vm_migration_config).unwrap(); - Request::config(config_data.len() as u64).write_to(&mut socket)?; - socket - .write_all(&config_data) - .map_err(MigratableError::MigrateSocket)?; - migration_transport::expect_ok_response( - &mut socket, - MigratableError::MigrateSend(anyhow!("Error during config migration")), - )?; + migration_transport::send_config(&mut socket, &vm_migration_config)?; // Let every Migratable object know about the migration being started. vm.start_migration()?; diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 990f14473e..6cf3e5bd12 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -3,17 +3,19 @@ // SPDX-License-Identifier: Apache-2.0 // +use std::io::Write; use std::net::{TcpListener, TcpStream}; use std::os::unix::net::{UnixListener, UnixStream}; use std::path::PathBuf; use std::result::Result; -use anyhow::anyhow; +use anyhow::{Context, anyhow}; use log::info; +use serde_json; use vm_migration::MigratableError; use vm_migration::protocol::{Request, Response}; -use crate::SocketStream; +use crate::{SocketStream, VmMigrationConfig}; /// Extract a UNIX socket path from a "unix:" migration URL. fn socket_url_to_path(url: &str) -> Result { @@ -97,3 +99,21 @@ pub(crate) fn send_request_expect_ok( request.write_to(socket)?; expect_ok_response(socket, error) } + +/// Serialize and send the VM configuration payload. +pub(crate) fn send_config( + socket: &mut SocketStream, + config: &VmMigrationConfig, +) -> Result<(), MigratableError> { + let config_data = serde_json::to_vec(config) + .context("Error serializing VM migration config") + .map_err(MigratableError::MigrateSend)?; + Request::config(config_data.len() as u64).write_to(socket)?; + socket + .write_all(&config_data) + .map_err(MigratableError::MigrateSocket)?; + expect_ok_response( + socket, + MigratableError::MigrateSend(anyhow!("Error during config migration")), + ) +} From 693cdccbb01d5f2e83a0f3f34ece944a62e0e196 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Mon, 9 Mar 2026 10:54:06 +0100 Subject: [PATCH 1058/1893] vmm: extract a helper to send the VM state On-behalf-of: SAP sebastian.eydam@sap.com Signed-off-by: Sebastian Eydam --- vmm/src/lib.rs | 10 +--------- vmm/src/migration_transport.rs | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 78ff205d31..75de067af3 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1453,15 +1453,7 @@ impl Vmm { // Capture snapshot and send it let vm_snapshot = vm.snapshot()?; - let snapshot_data = serde_json::to_vec(&vm_snapshot).unwrap(); - Request::state(snapshot_data.len() as u64).write_to(&mut socket)?; - socket - .write_all(&snapshot_data) - .map_err(MigratableError::MigrateSocket)?; - migration_transport::expect_ok_response( - &mut socket, - MigratableError::MigrateSend(anyhow!("Error during state migration")), - )?; + migration_transport::send_state(&mut socket, &vm_snapshot)?; // Complete the migration // At this step, the receiving VMM will acquire disk locks again. migration_transport::send_request_expect_ok( diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 6cf3e5bd12..99d0e12dfb 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -12,8 +12,8 @@ use std::result::Result; use anyhow::{Context, anyhow}; use log::info; use serde_json; -use vm_migration::MigratableError; use vm_migration::protocol::{Request, Response}; +use vm_migration::{MigratableError, Snapshot}; use crate::{SocketStream, VmMigrationConfig}; @@ -117,3 +117,21 @@ pub(crate) fn send_config( MigratableError::MigrateSend(anyhow!("Error during config migration")), ) } + +/// Serialize and send the VM snapshot payload. +pub(crate) fn send_state( + socket: &mut SocketStream, + snapshot: &Snapshot, +) -> Result<(), MigratableError> { + let snapshot_data = serde_json::to_vec(snapshot) + .context("Error serializing VM snapshot") + .map_err(MigratableError::MigrateSend)?; + Request::state(snapshot_data.len() as u64).write_to(socket)?; + socket + .write_all(&snapshot_data) + .map_err(MigratableError::MigrateSocket)?; + expect_ok_response( + socket, + MigratableError::MigrateSend(anyhow!("Error during state migration")), + ) +} From e03c0f7708db3b662a607933b9595d1a0ed3b93e Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Mon, 9 Mar 2026 10:13:09 +0100 Subject: [PATCH 1059/1893] vmm: move function to send dirty pages into transport module On-behalf-of: SAP sebastian.eydam@sap.com Signed-off-by: Sebastian Eydam --- vmm/src/lib.rs | 31 ++----------------------------- vmm/src/migration_transport.rs | 30 +++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 75de067af3..db84542e30 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1157,33 +1157,6 @@ impl Vmm { Ok(()) } - /// Transmits the given [`MemoryRangeTable`] over the wire if there is at - /// least one region. - /// - /// Sends a memory migration request, the range table, and the corresponding - /// guest memory regions over the given socket. Waits for acknowledgment - /// from the destination. - fn vm_send_dirty_pages( - vm: &mut Vm, - socket: &mut SocketStream, - table: &MemoryRangeTable, - ) -> result::Result<(), MigratableError> { - if table.regions().is_empty() { - return Ok(()); - } - - Request::memory(table.length()).write_to(socket)?; - table.write_to(socket)?; - // And then the memory itself - vm.send_memory_regions(table, socket)?; - migration_transport::expect_ok_response( - socket, - MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")), - )?; - - Ok(()) - } - /// Performs the initial memory transmission (iteration zero) plus a /// variable number of memory iterations with the goal to eventually migrate /// the VM in a reasonably small downtime. @@ -1214,7 +1187,7 @@ impl Vmm { // Send the current dirty pages let transfer_begin = Instant::now(); - Self::vm_send_dirty_pages(vm, socket, &iteration_table)?; + migration_transport::vm_send_dirty_pages(vm, socket, &iteration_table)?; let transfer_duration = transfer_begin.elapsed(); ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); @@ -1356,7 +1329,7 @@ impl Vmm { ctx.update_metrics_before_transfer(iteration_begin, &final_table); let transfer_begin = Instant::now(); - Vmm::vm_send_dirty_pages(vm, socket, &final_table)?; + migration_transport::vm_send_dirty_pages(vm, socket, &final_table)?; let transfer_duration = transfer_begin.elapsed(); ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); ctx.iteration += 1; diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 99d0e12dfb..fd3e035e11 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -12,9 +12,10 @@ use std::result::Result; use anyhow::{Context, anyhow}; use log::info; use serde_json; -use vm_migration::protocol::{Request, Response}; +use vm_migration::protocol::{MemoryRangeTable, Request, Response}; use vm_migration::{MigratableError, Snapshot}; +use crate::vm::Vm; use crate::{SocketStream, VmMigrationConfig}; /// Extract a UNIX socket path from a "unix:" migration URL. @@ -135,3 +136,30 @@ pub(crate) fn send_state( MigratableError::MigrateSend(anyhow!("Error during state migration")), ) } + +/// Transmits the given [`MemoryRangeTable`] over the wire if there is at +/// least one region. +/// +/// Sends a memory migration request, the range table, and the corresponding +/// guest memory regions over the given socket. Waits for acknowledgment +/// from the destination. +pub(crate) fn vm_send_dirty_pages( + vm: &mut Vm, + socket: &mut SocketStream, + table: &MemoryRangeTable, +) -> Result<(), MigratableError> { + if table.regions().is_empty() { + return Ok(()); + } + + Request::memory(table.length()).write_to(socket)?; + table.write_to(socket)?; + // And then the memory itself + vm.send_memory_regions(table, socket)?; + expect_ok_response( + socket, + MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")), + )?; + + Ok(()) +} From 9248143e18d8bd4fb13e2b7f81dc5f4f95ebdc86 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Mon, 9 Mar 2026 16:19:41 +0100 Subject: [PATCH 1060/1893] 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 --- vmm/src/lib.rs | 4 +-- vmm/src/migration_transport.rs | 56 +++++++++++++++++++++++++--------- vmm/src/vm.rs | 49 +++-------------------------- 3 files changed, 48 insertions(+), 61 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index db84542e30..1a9c53dd31 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1187,7 +1187,7 @@ impl Vmm { // Send the current dirty pages let transfer_begin = Instant::now(); - migration_transport::vm_send_dirty_pages(vm, socket, &iteration_table)?; + migration_transport::send_memory_ranges(&vm.guest_memory(), &iteration_table, socket)?; let transfer_duration = transfer_begin.elapsed(); ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); @@ -1329,7 +1329,7 @@ impl Vmm { ctx.update_metrics_before_transfer(iteration_begin, &final_table); let transfer_begin = Instant::now(); - migration_transport::vm_send_dirty_pages(vm, socket, &final_table)?; + migration_transport::send_memory_ranges(&vm.guest_memory(), &final_table, socket)?; let transfer_duration = transfer_begin.elapsed(); ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); ctx.iteration += 1; diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index fd3e035e11..22dac6f71d 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -12,11 +12,11 @@ use std::result::Result; use anyhow::{Context, anyhow}; use log::info; use serde_json; +use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic}; use vm_migration::protocol::{MemoryRangeTable, Request, Response}; use vm_migration::{MigratableError, Snapshot}; -use crate::vm::Vm; -use crate::{SocketStream, VmMigrationConfig}; +use crate::{GuestMemoryMmap, SocketStream, VmMigrationConfig}; /// Extract a UNIX socket path from a "unix:" migration URL. fn socket_url_to_path(url: &str) -> Result { @@ -137,29 +137,55 @@ pub(crate) fn send_state( ) } -/// Transmits the given [`MemoryRangeTable`] over the wire if there is at -/// least one region. +/// Transmits the given [`MemoryRangeTable`] and the corresponding guest memory +/// content over the wire if there is at least one range. /// /// Sends a memory migration request, the range table, and the corresponding -/// guest memory regions over the given socket. Waits for acknowledgment +/// guest memory range over the given socket. Waits for acknowledgment /// from the destination. -pub(crate) fn vm_send_dirty_pages( - vm: &mut Vm, +pub(crate) fn send_memory_ranges( + guest_memory: &GuestMemoryAtomic, + ranges: &MemoryRangeTable, socket: &mut SocketStream, - table: &MemoryRangeTable, ) -> Result<(), MigratableError> { - if table.regions().is_empty() { + if ranges.regions().is_empty() { return Ok(()); } - Request::memory(table.length()).write_to(socket)?; - table.write_to(socket)?; + // Send the memory table + Request::memory(ranges.length()).write_to(socket)?; + ranges.write_to(socket)?; + // And then the memory itself - vm.send_memory_regions(table, socket)?; + let mem = guest_memory.memory(); + for range in ranges.regions() { + let mut offset: u64 = 0; + // Here we are manually handling the retry in case we can't read the + // whole region at once because we can't use the implementation + // from vm-memory::GuestMemory of write_all_to() as it is not + // following the correct behavior. For more info about this issue + // see: https://github.com/rust-vmm/vm-memory/issues/174 + loop { + let bytes_written = mem + .write_volatile_to( + GuestAddress(range.gpa + offset), + socket, + (range.length - offset) as usize, + ) + .map_err(|e| { + MigratableError::MigrateSend(anyhow!( + "Error transferring memory to socket: {e}" + )) + })?; + offset += bytes_written as u64; + + if offset == range.length { + break; + } + } + } expect_ok_response( socket, MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")), - )?; - - Ok(()) + ) } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 7a5a19133a..c0eaba7ed6 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -68,7 +68,7 @@ use vm_device::Bus; use vm_memory::GuestMemory; #[cfg(feature = "tdx")] use vm_memory::{Address, ByteValued, GuestMemoryRegion, ReadVolatile}; -use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, WriteVolatile}; +use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic}; use vm_migration::protocol::{MemoryRangeTable, Request, Response}; use vm_migration::{ Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, snapshot_from_id, @@ -2853,49 +2853,6 @@ impl Vm { Ok(()) } - /// Writes the contents of the given guest memory regions to the provided sink. - /// Used, for example, during VM live migration to transfer memory to a socket. - pub fn send_memory_regions( - &mut self, - ranges: &MemoryRangeTable, - fd: &mut F, - ) -> std::result::Result<(), MigratableError> - where - F: WriteVolatile, - { - let guest_memory = self.memory_manager.lock().as_ref().unwrap().guest_memory(); - let mem = guest_memory.memory(); - - for range in ranges.regions() { - let mut offset: u64 = 0; - // Here we are manually handling the retry in case we can't the - // whole region at once because we can't use the implementation - // from vm-memory::GuestMemory of write_all_to() as it is not - // following the correct behavior. For more info about this issue - // see: https://github.com/rust-vmm/vm-memory/issues/174 - loop { - let bytes_written = mem - .write_volatile_to( - GuestAddress(range.gpa + offset), - fd, - (range.length - offset) as usize, - ) - .map_err(|e| { - MigratableError::MigrateSend(anyhow!( - "Error transferring memory to socket: {e}" - )) - })?; - offset += bytes_written as u64; - - if offset == range.length { - break; - } - } - } - - Ok(()) - } - pub fn memory_range_table(&self) -> std::result::Result { self.memory_manager .lock() @@ -2903,6 +2860,10 @@ impl Vm { .memory_range_table(false) } + pub fn guest_memory(&self) -> GuestMemoryAtomic { + self.memory_manager.lock().unwrap().guest_memory() + } + pub fn device_tree(&self) -> Arc> { self.device_manager.lock().unwrap().device_tree() } From e175ad64f2403c030e781f2c7d90166030f5c6c8 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Tue, 10 Mar 2026 09:47:19 +0100 Subject: [PATCH 1061/1893] 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 --- vmm/src/lib.rs | 90 ++------------------------------ vmm/src/migration_transport.rs | 95 ++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 90 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 1a9c53dd31..c08ec9e28d 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -6,9 +6,7 @@ use std::collections::HashMap; use std::fs::File; use std::io::{Read, Write, stdout}; -use std::net::TcpStream; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::os::unix::net::UnixStream; use std::panic::AssertUnwindSafe; #[cfg(feature = "guest_debug")] use std::path::PathBuf; @@ -38,8 +36,8 @@ use serde::{Deserialize, Serialize}; use signal_hook::iterator::{Handle, Signals}; use thiserror::Error; use tracer::trace_scoped; -use vm_memory::bitmap::{AtomicBitmap, BitmapSlice}; -use vm_memory::{ReadVolatile, VolatileMemoryError, VolatileSlice, WriteVolatile}; +use vm_memory::ReadVolatile; +use vm_memory::bitmap::AtomicBitmap; use vm_migration::protocol::*; use vm_migration::{ MemoryMigrationContext, Migratable, MigratableError, Pausable, Snapshot, Snapshottable, @@ -61,6 +59,7 @@ use crate::memory_manager::MemoryManager; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] use crate::migration::get_vm_snapshot; use crate::migration::{recv_vm_config, recv_vm_state}; +use crate::migration_transport::SocketStream; use crate::seccomp_filters::{Thread, get_seccomp_filter}; use crate::vm::{Error as VmError, Vm, VmState}; use crate::vm_config::{ @@ -265,89 +264,6 @@ impl From for EpollDispatch { } } -enum SocketStream { - Unix(UnixStream), - Tcp(TcpStream), -} - -impl Read for SocketStream { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - match self { - SocketStream::Unix(stream) => stream.read(buf), - SocketStream::Tcp(stream) => stream.read(buf), - } - } -} - -impl Write for SocketStream { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - match self { - SocketStream::Unix(stream) => stream.write(buf), - SocketStream::Tcp(stream) => stream.write(buf), - } - } - - fn flush(&mut self) -> std::io::Result<()> { - match self { - SocketStream::Unix(stream) => stream.flush(), - SocketStream::Tcp(stream) => stream.flush(), - } - } -} - -impl AsRawFd for SocketStream { - fn as_raw_fd(&self) -> RawFd { - match self { - SocketStream::Unix(s) => s.as_raw_fd(), - SocketStream::Tcp(s) => s.as_raw_fd(), - } - } -} - -impl ReadVolatile for SocketStream { - fn read_volatile( - &mut self, - buf: &mut VolatileSlice, - ) -> std::result::Result { - match self { - SocketStream::Unix(s) => s.read_volatile(buf), - SocketStream::Tcp(s) => s.read_volatile(buf), - } - } - - fn read_exact_volatile( - &mut self, - buf: &mut VolatileSlice, - ) -> std::result::Result<(), VolatileMemoryError> { - match self { - SocketStream::Unix(s) => s.read_exact_volatile(buf), - SocketStream::Tcp(s) => s.read_exact_volatile(buf), - } - } -} - -impl WriteVolatile for SocketStream { - fn write_volatile( - &mut self, - buf: &VolatileSlice, - ) -> std::result::Result { - match self { - SocketStream::Unix(s) => s.write_volatile(buf), - SocketStream::Tcp(s) => s.write_volatile(buf), - } - } - - fn write_all_volatile( - &mut self, - buf: &VolatileSlice, - ) -> std::result::Result<(), VolatileMemoryError> { - match self { - SocketStream::Unix(s) => s.write_all_volatile(buf), - SocketStream::Tcp(s) => s.write_all_volatile(buf), - } - } -} - pub struct EpollContext { epoll_file: File, } diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 22dac6f71d..168d718be7 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -3,8 +3,9 @@ // SPDX-License-Identifier: Apache-2.0 // -use std::io::Write; +use std::io::{self, Read, Write}; use std::net::{TcpListener, TcpStream}; +use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::{UnixListener, UnixStream}; use std::path::PathBuf; use std::result::Result; @@ -12,11 +13,99 @@ use std::result::Result; use anyhow::{Context, anyhow}; use log::info; use serde_json; -use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic}; +use vm_memory::bitmap::BitmapSlice; +use vm_memory::{ + Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, ReadVolatile, VolatileMemoryError, + VolatileSlice, WriteVolatile, +}; use vm_migration::protocol::{MemoryRangeTable, Request, Response}; use vm_migration::{MigratableError, Snapshot}; -use crate::{GuestMemoryMmap, SocketStream, VmMigrationConfig}; +use crate::{GuestMemoryMmap, VmMigrationConfig}; + +/// Transport-agnostic stream used by the migration protocol. +pub(crate) enum SocketStream { + Unix(UnixStream), + Tcp(TcpStream), +} + +impl Read for SocketStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + SocketStream::Unix(stream) => stream.read(buf), + SocketStream::Tcp(stream) => stream.read(buf), + } + } +} + +impl Write for SocketStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + SocketStream::Unix(stream) => stream.write(buf), + SocketStream::Tcp(stream) => stream.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match self { + SocketStream::Unix(stream) => stream.flush(), + SocketStream::Tcp(stream) => stream.flush(), + } + } +} + +impl AsRawFd for SocketStream { + fn as_raw_fd(&self) -> RawFd { + match self { + SocketStream::Unix(s) => s.as_raw_fd(), + SocketStream::Tcp(s) => s.as_raw_fd(), + } + } +} + +impl ReadVolatile for SocketStream { + fn read_volatile( + &mut self, + buf: &mut VolatileSlice, + ) -> Result { + match self { + SocketStream::Unix(s) => s.read_volatile(buf), + SocketStream::Tcp(s) => s.read_volatile(buf), + } + } + + fn read_exact_volatile( + &mut self, + buf: &mut VolatileSlice, + ) -> Result<(), VolatileMemoryError> { + match self { + SocketStream::Unix(s) => s.read_exact_volatile(buf), + SocketStream::Tcp(s) => s.read_exact_volatile(buf), + } + } +} + +impl WriteVolatile for SocketStream { + fn write_volatile( + &mut self, + buf: &VolatileSlice, + ) -> Result { + match self { + SocketStream::Unix(s) => s.write_volatile(buf), + SocketStream::Tcp(s) => s.write_volatile(buf), + } + } + + fn write_all_volatile( + &mut self, + buf: &VolatileSlice, + ) -> Result<(), VolatileMemoryError> { + match self { + SocketStream::Unix(s) => s.write_all_volatile(buf), + SocketStream::Tcp(s) => s.write_all_volatile(buf), + } + } +} /// Extract a UNIX socket path from a "unix:" migration URL. fn socket_url_to_path(url: &str) -> Result { From ec42ee8004064c2038f7d659b64fe5df80def1ea Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Tue, 10 Mar 2026 10:07:10 +0100 Subject: [PATCH 1062/1893] 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 --- vmm/src/lib.rs | 16 ++++--------- vmm/src/memory_manager.rs | 43 +--------------------------------- vmm/src/migration_transport.rs | 38 ++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 53 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index c08ec9e28d..c2fb3e9e68 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -36,7 +36,6 @@ use serde::{Deserialize, Serialize}; use signal_hook::iterator::{Handle, Signals}; use thiserror::Error; use tracer::trace_scoped; -use vm_memory::ReadVolatile; use vm_memory::bitmap::AtomicBitmap; use vm_migration::protocol::*; use vm_migration::{ @@ -1056,21 +1055,16 @@ impl Vmm { Ok(()) } - fn vm_receive_memory( + fn vm_receive_memory( &mut self, req: &Request, - socket: &mut T, + socket: &mut SocketStream, memory_manager: &mut MemoryManager, - ) -> std::result::Result<(), MigratableError> - where - T: Read + ReadVolatile, - { - // Read table + ) -> std::result::Result<(), MigratableError> { let table = MemoryRangeTable::read_from(socket, req.length())?; - // And then read the memory itself - memory_manager.receive_memory_regions(&table, socket)?; - Ok(()) + // And then the memory itself + migration_transport::receive_memory_ranges(&memory_manager.guest_memory(), &table, socket) } /// Performs the initial memory transmission (iteration zero) plus a diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index c8f64b15ad..62b4522cc2 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -39,7 +39,7 @@ use vm_memory::guest_memory::{Error as MmapError, FileOffset}; use vm_memory::mmap::MmapRegionError; use vm_memory::{ Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, - GuestMemoryError, GuestMemoryRegion, GuestUsize, MmapRegion, ReadVolatile, + GuestMemoryError, GuestMemoryRegion, GuestUsize, MmapRegion, }; use vm_migration::protocol::{MemoryRange, MemoryRangeTable}; use vm_migration::{ @@ -2572,47 +2572,6 @@ impl MemoryManager { debug!("coredump total bytes {total_bytes}"); Ok(()) } - - pub fn receive_memory_regions( - &mut self, - ranges: &MemoryRangeTable, - fd: &mut F, - ) -> std::result::Result<(), MigratableError> - where - F: ReadVolatile, - { - let guest_memory = self.guest_memory(); - let mem = guest_memory.memory(); - - for range in ranges.regions() { - let mut offset: u64 = 0; - // Here we are manually handling the retry in case we can't the - // whole region at once because we can't use the implementation - // from vm-memory::GuestMemory of read_exact_from() as it is not - // following the correct behavior. For more info about this issue - // see: https://github.com/rust-vmm/vm-memory/issues/174 - loop { - let bytes_read = mem - .read_volatile_from( - GuestAddress(range.gpa + offset), - fd, - (range.length - offset) as usize, - ) - .map_err(|e| { - MigratableError::MigrateReceive(anyhow!( - "Error receiving memory from socket: {e}" - )) - })?; - offset += bytes_read as u64; - - if offset == range.length { - break; - } - } - } - - Ok(()) - } } struct MemoryNotify { diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 168d718be7..12412b0a04 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -278,3 +278,41 @@ pub(crate) fn send_memory_ranges( MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")), ) } + +/// Receive memory contents for the given range table into guest memory. +pub(crate) fn receive_memory_ranges( + guest_memory: &GuestMemoryAtomic, + ranges: &MemoryRangeTable, + socket: &mut SocketStream, +) -> Result<(), MigratableError> { + let mem = guest_memory.memory(); + + for range in ranges.regions() { + let mut offset: u64 = 0; + // Here we are manually handling the retry in case we can't read the + // whole region at once because we can't use the implementation + // from vm-memory::GuestMemory of read_exact_from() as it is not + // following the correct behavior. For more info about this issue + // see: https://github.com/rust-vmm/vm-memory/issues/174 + loop { + let bytes_read = mem + .read_volatile_from( + GuestAddress(range.gpa + offset), + socket, + (range.length - offset) as usize, + ) + .map_err(|e| { + MigratableError::MigrateReceive(anyhow!( + "Error receiving memory from socket: {e}" + )) + })?; + offset += bytes_read as u64; + + if offset == range.length { + break; + } + } + } + + Ok(()) +} From 765311085f06d864d32ae9644f80bfd839977962 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Tue, 10 Mar 2026 10:26:59 +0100 Subject: [PATCH 1063/1893] 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 --- vmm/src/lib.rs | 55 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index c2fb3e9e68..f118e93e92 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -36,6 +36,7 @@ use serde::{Deserialize, Serialize}; use signal_hook::iterator::{Handle, Signals}; use thiserror::Error; use tracer::trace_scoped; +use vm_memory::GuestMemoryAtomic; use vm_memory::bitmap::AtomicBitmap; use vm_migration::protocol::*; use vm_migration::{ @@ -622,8 +623,16 @@ enum ReceiveMigrationState { /// We received file descriptors for memory. This can only happen on UNIX domain sockets. MemoryFdsReceived(Vec<(u32, File)>), - /// We received the VM configuration. We keep the memory configuration around to populate guest memory. From this point on, the sender can start sending memory updates. - Configured(Arc>), + /// We received the VM configuration. We keep a direct reference to the guest memory + /// around to populate it without having to acquire a lock (which we would have to do + /// when accessing the memory through the memory manager). + /// + /// We keep the memory manager around to pass it into the next state. From this point + /// on, the sender can start sending memory updates. + Configured( + Arc>, + GuestMemoryAtomic, + ), /// Memory is populated and we received the state. The VM is ready to go. StateReceived, @@ -835,14 +844,19 @@ impl Vmm { ))) }; - let mut configure_vm = - |socket: &mut SocketStream, - memory_files: HashMap| - -> std::result::Result>, MigratableError> { - let memory_manager = self.vm_receive_config(req, socket, memory_files)?; - - Ok(memory_manager) - }; + let mut configure_vm = |socket: &mut SocketStream, + memory_files: HashMap| + -> std::result::Result< + ( + Arc>, + GuestMemoryAtomic, + ), + MigratableError, + > { + let memory_manager = self.vm_receive_config(req, socket, memory_files)?; + let guest_memory = memory_manager.lock().unwrap().guest_memory(); + Ok((memory_manager, guest_memory)) + }; let recv_memory_fd = |socket: &mut SocketStream, mut memory_files: Vec<(u32, File)>| @@ -865,23 +879,24 @@ impl Vmm { }, Started => match req.command() { Command::MemoryFd => recv_memory_fd(socket, Vec::new()).map(MemoryFdsReceived), - Command::Config => configure_vm(socket, Default::default()).map(Configured), + Command::Config => { + configure_vm(socket, Default::default()).map(|res| Configured(res.0, res.1)) + } _ => invalid_command(), }, MemoryFdsReceived(memory_files) => match req.command() { Command::MemoryFd => recv_memory_fd(socket, memory_files).map(MemoryFdsReceived), - Command::Config => { - configure_vm(socket, HashMap::from_iter(memory_files)).map(Configured) - } + Command::Config => configure_vm(socket, HashMap::from_iter(memory_files)) + .map(|res| Configured(res.0, res.1)), _ => invalid_command(), }, - Configured(memory_manager) => match req.command() { + Configured(memory_manager, guest_memory) => match req.command() { Command::Memory => { - self.vm_receive_memory(req, socket, &mut memory_manager.lock().unwrap())?; - Ok(Configured(memory_manager)) + self.vm_receive_memory(req, socket, &guest_memory)?; + Ok(Configured(memory_manager, guest_memory)) } Command::State => { - self.vm_receive_state(req, socket, memory_manager.clone())?; + self.vm_receive_state(req, socket, memory_manager)?; Ok(StateReceived) } _ => invalid_command(), @@ -1059,12 +1074,12 @@ impl Vmm { &mut self, req: &Request, socket: &mut SocketStream, - memory_manager: &mut MemoryManager, + guest_mem: &GuestMemoryAtomic, ) -> std::result::Result<(), MigratableError> { let table = MemoryRangeTable::read_from(socket, req.length())?; // And then the memory itself - migration_transport::receive_memory_ranges(&memory_manager.guest_memory(), &table, socket) + migration_transport::receive_memory_ranges(guest_mem, &table, socket) } /// Performs the initial memory transmission (iteration zero) plus a From 7311211b38d018fa1743e49b4903a6590ae412ef Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Tue, 10 Mar 2026 11:17:20 +0100 Subject: [PATCH 1064/1893] 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 --- vmm/src/lib.rs | 5 +-- vmm/src/migration_transport.rs | 63 ++++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index f118e93e92..311ec84b88 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2298,9 +2298,10 @@ impl RequestHandler for Vmm { receive_data_migration.receiver_url ); + let mut listener = + migration_transport::receive_migration_listener(&receive_data_migration.receiver_url)?; // Accept the connection and get the socket - let mut socket = - migration_transport::receive_migration_socket(&receive_data_migration.receiver_url)?; + let mut socket = listener.accept()?; event!("vm", "migration-receive-started"); diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 12412b0a04..a33ee503cd 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -23,6 +23,31 @@ use vm_migration::{MigratableError, Snapshot}; use crate::{GuestMemoryMmap, VmMigrationConfig}; +/// Transport-agnostic listener used to receive connections. +#[derive(Debug)] +pub(crate) enum ReceiveListener { + Tcp(TcpListener), + Unix(UnixListener), +} + +impl ReceiveListener { + /// Block until a connection is accepted. + pub(crate) fn accept(&mut self) -> Result { + match self { + ReceiveListener::Tcp(listener) => listener + .accept() + .map(|(socket, _)| SocketStream::Tcp(socket)) + .context("Failed to accept TCP migration connection") + .map_err(MigratableError::MigrateReceive), + ReceiveListener::Unix(listener) => listener + .accept() + .map(|(socket, _)| SocketStream::Unix(socket)) + .context("Failed to accept Unix migration connection") + .map_err(MigratableError::MigrateReceive), + } + } +} + /// Transport-agnostic stream used by the migration protocol. pub(crate) enum SocketStream { Unix(UnixStream), @@ -138,35 +163,21 @@ pub(crate) fn send_migration_socket( } } -/// Bind and accept a migration connection for the receiver side. -pub(crate) fn receive_migration_socket( +/// Bind a migration listener for the receiver side. +pub(crate) fn receive_migration_listener( receiver_url: &str, -) -> Result { +) -> Result { if let Some(address) = receiver_url.strip_prefix("tcp:") { - let listener = TcpListener::bind(address).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error binding to TCP socket: {e}")) - })?; - - let (socket, _addr) = listener.accept().map_err(|e| { - MigratableError::MigrateReceive(anyhow!( - "Error accepting connection on TCP socket: {e}" - )) - })?; - - Ok(SocketStream::Tcp(socket)) + TcpListener::bind(address) + .map(ReceiveListener::Tcp) + .context("Error binding to TCP socket") + .map_err(MigratableError::MigrateReceive) } else { - let path = socket_url_to_path(receiver_url).map_err(MigratableError::MigrateSend)?; - let listener = UnixListener::bind(&path).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error binding to UNIX socket: {e}")) - })?; - - let (socket, _addr) = listener.accept().map_err(|e| { - MigratableError::MigrateReceive(anyhow!( - "Error accepting connection on UNIX socket: {e}" - )) - })?; - - Ok(SocketStream::Unix(socket)) + let path = socket_url_to_path(receiver_url).map_err(MigratableError::MigrateReceive)?; + UnixListener::bind(&path) + .map(ReceiveListener::Unix) + .context("Error binding to UNIX socket") + .map_err(MigratableError::MigrateReceive) } } From 98ece1e34713672118dd2aa08a347174f8e0e98a Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Thu, 12 Mar 2026 10:11:49 +0100 Subject: [PATCH 1065/1893] 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 --- vmm/src/migration_transport.rs | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index a33ee503cd..cda22dfac2 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -5,6 +5,7 @@ use std::io::{self, Read, Write}; use std::net::{TcpListener, TcpStream}; +use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::{UnixListener, UnixStream}; use std::path::PathBuf; @@ -46,6 +47,32 @@ impl ReceiveListener { .map_err(MigratableError::MigrateReceive), } } + + /// Same as [`Self::accept`], but returns `None` if the abort event was signaled. + fn abortable_accept( + &mut self, + abort_event: &impl AsRawFd, + ) -> Result, MigratableError> { + if wait_for_readable(&self, abort_event) + .context("Error while waiting for socket to become readable") + .map_err(MigratableError::MigrateReceive)? + { + // The listener is readable; accept the connection. + Ok(Some(self.accept()?)) + } else { + // The abort event was signaled before any connection arrived. + Ok(None) + } + } +} + +impl AsFd for ReceiveListener { + fn as_fd(&self) -> BorrowedFd<'_> { + match self { + ReceiveListener::Tcp(listener) => listener.as_fd(), + ReceiveListener::Unix(listener) => listener.as_fd(), + } + } } /// Transport-agnostic stream used by the migration protocol. @@ -132,6 +159,55 @@ impl WriteVolatile for SocketStream { } } +// Wait for `fd` to become readable. In this case, we return true. In case +// `abort_event` was signaled, return false. +fn wait_for_readable(fd: &impl AsFd, abort_event: &impl AsRawFd) -> Result { + let fd = fd.as_fd().as_raw_fd(); + let abort_event = abort_event.as_raw_fd(); + + let mut poll_fds = [ + libc::pollfd { + fd: abort_event, + events: libc::POLLIN, + revents: 0, + }, + libc::pollfd { + fd, + events: libc::POLLIN, + revents: 0, + }, + ]; + + loop { + // SAFETY: This is safe, because the file descriptors are valid and the + // poll_fds array is properly initialized. + let ret = unsafe { libc::poll(poll_fds.as_mut_ptr(), poll_fds.len() as libc::nfds_t, -1) }; + + if ret >= 0 { + break; + } + + let err = io::Error::last_os_error(); + if err.raw_os_error() == Some(libc::EINTR) { + continue; + } + + return Err(err); + } + + if poll_fds[0].revents & libc::POLLIN != 0 { + return Ok(false); + } + + if poll_fds[1].revents & libc::POLLIN != 0 { + return Ok(true); + } + + Err(io::Error::other( + "Poll returned, but neither file descriptor is readable?", + )) +} + /// Extract a UNIX socket path from a "unix:" migration URL. fn socket_url_to_path(url: &str) -> Result { url.strip_prefix("unix:") From 058954a8c10667ecbb594f79b089172c597d9742 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Thu, 12 Mar 2026 10:29:49 +0100 Subject: [PATCH 1066/1893] 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 --- vmm/src/lib.rs | 14 +------------- vmm/src/migration_transport.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 311ec84b88..be9ebf929b 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -892,7 +892,7 @@ impl Vmm { }, Configured(memory_manager, guest_memory) => match req.command() { Command::Memory => { - self.vm_receive_memory(req, socket, &guest_memory)?; + migration_transport::receive_memory_ranges(&guest_memory, req, socket)?; Ok(Configured(memory_manager, guest_memory)) } Command::State => { @@ -1070,18 +1070,6 @@ impl Vmm { Ok(()) } - fn vm_receive_memory( - &mut self, - req: &Request, - socket: &mut SocketStream, - guest_mem: &GuestMemoryAtomic, - ) -> std::result::Result<(), MigratableError> { - let table = MemoryRangeTable::read_from(socket, req.length())?; - - // And then the memory itself - migration_transport::receive_memory_ranges(guest_mem, &table, socket) - } - /// Performs the initial memory transmission (iteration zero) plus a /// variable number of memory iterations with the goal to eventually migrate /// the VM in a reasonably small downtime. diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index cda22dfac2..6294122140 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -19,7 +19,7 @@ use vm_memory::{ Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, ReadVolatile, VolatileMemoryError, VolatileSlice, WriteVolatile, }; -use vm_migration::protocol::{MemoryRangeTable, Request, Response}; +use vm_migration::protocol::{Command, MemoryRangeTable, Request, Response}; use vm_migration::{MigratableError, Snapshot}; use crate::{GuestMemoryMmap, VmMigrationConfig}; @@ -366,12 +366,17 @@ pub(crate) fn send_memory_ranges( ) } -/// Receive memory contents for the given range table into guest memory. +/// Receive memory contents for the given request and copy it into guest memory. pub(crate) fn receive_memory_ranges( guest_memory: &GuestMemoryAtomic, - ranges: &MemoryRangeTable, + req: &Request, socket: &mut SocketStream, ) -> Result<(), MigratableError> { + debug_assert_eq!(req.command(), Command::Memory); + // Read the memory table + let ranges = MemoryRangeTable::read_from(socket, req.length())?; + + // And then the memory itself let mem = guest_memory.memory(); for range in ranges.regions() { From 5c556880dd63f220f3a7cc8cb05d63e68824b5e6 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Thu, 12 Mar 2026 10:37:33 +0100 Subject: [PATCH 1067/1893] 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 --- vmm/src/migration_transport.rs | 192 ++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 3 deletions(-) diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 6294122140..27fc464fad 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -3,16 +3,17 @@ // SPDX-License-Identifier: Apache-2.0 // -use std::io::{self, Read, Write}; +use std::io::{self, ErrorKind, Read, Write}; use std::net::{TcpListener, TcpStream}; use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::{UnixListener, UnixStream}; use std::path::PathBuf; use std::result::Result; +use std::thread; use anyhow::{Context, anyhow}; -use log::info; +use log::{debug, error, info, warn}; use serde_json; use vm_memory::bitmap::BitmapSlice; use vm_memory::{ @@ -21,6 +22,7 @@ use vm_memory::{ }; use vm_migration::protocol::{Command, MemoryRangeTable, Request, Response}; use vm_migration::{MigratableError, Snapshot}; +use vmm_sys_util::eventfd::EventFd; use crate::{GuestMemoryMmap, VmMigrationConfig}; @@ -115,6 +117,15 @@ impl AsRawFd for SocketStream { } } +impl AsFd for SocketStream { + fn as_fd(&self) -> BorrowedFd<'_> { + match self { + SocketStream::Unix(s) => s.as_fd(), + SocketStream::Tcp(s) => s.as_fd(), + } + } +} + impl ReadVolatile for SocketStream { fn read_volatile( &mut self, @@ -208,6 +219,181 @@ fn wait_for_readable(fd: &impl AsFd, abort_event: &impl AsRawFd) -> Result>>, + + /// This fd gets signaled when the migration stops, and will then stop + /// the [`Self::accept_thread`]. + terminate_fd: EventFd, +} + +impl ReceiveAdditionalConnections { + /// Starts a thread to accept incoming connections and handle them. These + /// additional connections are used to receive additional memory regions + /// during VM migration. + pub(crate) fn new( + listener: ReceiveListener, + guest_memory: GuestMemoryAtomic, + ) -> Result { + let event_fd = EventFd::new(0) + .context("Error creating terminate fd") + .map_err(MigratableError::MigrateReceive)?; + + let terminate_fd = event_fd + .try_clone() + .context("Error cloning terminate fd") + .map_err(MigratableError::MigrateReceive)?; + + let accept_thread = thread::Builder::new() + .name("migrate-receive-accept-connections".to_owned()) + .spawn(move || Self::accept_connections(listener, &terminate_fd, &guest_memory)) + .context("Error creating connection accept thread") + .map_err(MigratableError::MigrateReceive)?; + + Ok(Self { + accept_thread: Some(accept_thread), + terminate_fd: event_fd, + }) + } + + fn accept_connections( + mut listener: ReceiveListener, + terminate_fd: &EventFd, + guest_memory: &GuestMemoryAtomic, + ) -> Result<(), MigratableError> { + let mut threads: Vec>> = Vec::new(); + while let Some(mut socket) = listener.abortable_accept(terminate_fd)? { + let guest_memory = guest_memory.clone(); + let terminate_fd = terminate_fd + .try_clone() + .context("Error cloning terminate fd") + .map_err(MigratableError::MigrateReceive)?; + + match thread::Builder::new() + .name(format!("migrate-receive-memory-{}", threads.len()).to_owned()) + .spawn(move || { + Self::worker_receive_memory(&mut socket, &terminate_fd, &guest_memory) + }) { + Ok(t) => threads.push(t), + Err(e) => { + error!("Error spawning receive-memory thread: {e}"); + break; + } + } + } + + info!("Stopped accepting additional connections. Cleaning up threads."); + + // We only return the first error we encounter here. + let mut first_err = Ok(()); + for thread in threads { + let err = match thread.join() { + Ok(Ok(())) => None, + Ok(Err(e)) => Some(e), + Err(panic) => Some(MigratableError::MigrateReceive(anyhow!( + "receive-memory thread panicked: {panic:?}" + ))), + }; + + if let Some(e) = err { + warn!("Error in receive-memory thread: {e}"); + + if first_err.is_ok() { + first_err = Err(e); + } + } + } + + first_err + } + + // Handles a `Memory` request by writing its payload to the VM memory. + fn worker_receive_memory( + mut socket: &mut SocketStream, + terminate_fd: &EventFd, + guest_memory: &GuestMemoryAtomic, + ) -> Result<(), MigratableError> { + loop { + // We only check whether we should abort when waiting for a new request. If the + // sender stops sending data mid-request, we will hang forever. + if !wait_for_readable(socket, terminate_fd) + .context("Failed to poll fds") + .map_err(MigratableError::MigrateReceive)? + { + info!("Got signal to tear down connection."); + return Ok(()); + } + + let req = match Request::read_from(&mut socket) { + Ok(req) => req, + Err(MigratableError::MigrateSocket(io_error)) + if io_error.kind() == ErrorKind::UnexpectedEof => + { + // EOF is only handled here while reading the next request + // header. Each memory chunk is fully received and acked + // before the worker loops back to Request::read_from(), so + // EOF at this point means the sender finished sending + // memory rather than dropping a chunk mid-transfer. + debug!( + "Connection closed by peer as expected (sender finished sending memory)" + ); + return Ok(()); + } + Err(e) => return Err(e), + }; + + if req.command() != Command::Memory { + error!( + "Dropping connection. Only Memory commands are allowed on additional connections." + ); + return Err(MigratableError::MigrateReceive(anyhow!( + "Received non memory command on migration receive worker: {:?}", + req.command() + ))); + } + + receive_memory_ranges(guest_memory, &req, socket)?; + Response::ok().write_to(socket)?; + } + } + + /// Signals to the worker threads that the migration is finished and joins them. + /// If any thread encountered an error, this error is returned by this function. + pub(crate) fn cleanup(&mut self) -> Result<(), MigratableError> { + self.terminate_fd + .write(1) + .context("Failed to signal termination to worker threads.") + .map_err(MigratableError::MigrateReceive)?; + let accept_thread = self + .accept_thread + .take() + .context("Error taking accept thread.") + .map_err(MigratableError::MigrateReceive)?; + accept_thread + .join() + .map_err(|panic| { + MigratableError::MigrateReceive(anyhow!( + "Accept connections thread panicked: {panic:?}" + )) + }) + .flatten() + } +} + +impl Drop for ReceiveAdditionalConnections { + fn drop(&mut self) { + if self.accept_thread.is_some() { + warn!( + "ReceiveAdditionalConnections was not cleaned up! Either cleanup() was never called (programming error) or it failed before completing." + ); + } + } +} + /// Extract a UNIX socket path from a "unix:" migration URL. fn socket_url_to_path(url: &str) -> Result { url.strip_prefix("unix:") @@ -366,7 +552,7 @@ pub(crate) fn send_memory_ranges( ) } -/// Receive memory contents for the given request and copy it into guest memory. +/// Receive memory contents for the given range table into guest memory. pub(crate) fn receive_memory_ranges( guest_memory: &GuestMemoryAtomic, req: &Request, From 5e563e7ea31457743a5ebaf60c5b604351ad3c0a Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Thu, 12 Mar 2026 13:21:23 +0100 Subject: [PATCH 1068/1893] 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 --- vm-migration/src/protocol.rs | 229 ++++++++++++++++++++++++++++++++++- 1 file changed, 228 insertions(+), 1 deletion(-) diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index ab5975d4e5..0a62375f54 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -272,12 +272,99 @@ pub struct MemoryRange { pub length: u64, } -#[derive(Clone, Default, Serialize, Deserialize)] +/// A set of guest-memory ranges to transfer as one migration payload. +#[derive(Clone, Default, Debug, Serialize, Deserialize)] pub struct MemoryRangeTable { data: Vec, } +/// Iterator returned by [`MemoryRangeTable::partition`]. +/// +/// Each item contains at most `chunk_size` bytes. A range may be split across +/// multiple items. +/// +/// The iterator may reorder ranges for efficiency, so callers must not rely on +/// the order in which chunks or ranges are yielded. +#[derive(Clone, Default, Debug)] +struct MemoryRangeTableIterator { + chunk_size: u64, + data: Vec, +} + +impl MemoryRangeTableIterator { + /// Create an iterator that partitions `table` into chunks of at most + /// `chunk_size` bytes. + pub fn new(table: MemoryRangeTable, chunk_size: u64) -> Self { + MemoryRangeTableIterator { + chunk_size, + data: table.data, + } + } +} + +impl Iterator for MemoryRangeTableIterator { + type Item = MemoryRangeTable; + + /// Return the next memory range in the table, making sure that + /// the returned range is not larger than `chunk_size`. + /// + /// **Note**: Do not rely on the order of the ranges returned by this + /// iterator. This allows for a more efficient implementation. + fn next(&mut self) -> Option { + let mut ranges: Vec = vec![]; + let mut ranges_size: u64 = 0; + + loop { + assert!(ranges_size <= self.chunk_size); + + if ranges_size == self.chunk_size || self.data.is_empty() { + break; + } + + if let Some(range) = self.data.pop() { + let next_range: MemoryRange = if ranges_size + range.length > self.chunk_size { + // How many bytes we need to put back into the table. + let leftover_bytes = ranges_size + range.length - self.chunk_size; + assert!(leftover_bytes <= range.length); + let returned_bytes = range.length - leftover_bytes; + assert!(returned_bytes <= range.length); + assert_eq!(leftover_bytes + returned_bytes, range.length); + + self.data.push(MemoryRange { + gpa: range.gpa, + length: leftover_bytes, + }); + MemoryRange { + gpa: range.gpa + leftover_bytes, + length: returned_bytes, + } + } else { + range + }; + + ranges_size += next_range.length; + ranges.push(next_range); + } + } + + if ranges.is_empty() { + None + } else { + Some(MemoryRangeTable { data: ranges }) + } + } +} + impl MemoryRangeTable { + pub fn ranges(&self) -> &[MemoryRange] { + &self.data + } + + /// Partitions the table into chunks of at most `chunk_size` bytes. + pub fn partition(self, chunk_size: u64) -> impl Iterator { + MemoryRangeTableIterator::new(self, chunk_size) + } + /// Converts an iterator over a dirty bitmap into an iterator of dirty /// [`MemoryRange`]s, merging consecutive dirty pages into contiguous ranges. /// @@ -413,4 +500,144 @@ mod unit_tests { ] ); } + + #[test] + fn test_memory_range_table_partition() { + // We start the test similar as the one above, but with a input that is simpler to parse for + // developers. + let input = [0b11_0011_0011_0011]; + + let start_gpa = 0x1000; + let page_size = 0x1000; + + let table = MemoryRangeTable::from_dirty_bitmap(input, start_gpa, page_size); + let expected_regions = [ + MemoryRange { + gpa: start_gpa, + length: page_size * 2, + }, + MemoryRange { + gpa: start_gpa + 4 * page_size, + length: page_size * 2, + }, + MemoryRange { + gpa: start_gpa + 8 * page_size, + length: page_size * 2, + }, + MemoryRange { + gpa: start_gpa + 12 * page_size, + length: page_size * 2, + }, + ]; + assert_eq!(table.regions(), &expected_regions); + + // In the first test, we expect to see the exact same result as above, as we use the length + // of every region (which is fixed!). + { + let chunks = table + .clone() + .partition(page_size * 2) + .map(|table| table.data) + .collect::>(); + + // The implementation currently returns the ranges in reverse order. + // For better testability, we reverse it. + let chunks = chunks + .into_iter() + .map(|vec| vec.into_iter().rev().collect::>()) + .rev() + .collect::>(); + + assert_eq!( + chunks, + &[ + [expected_regions[0].clone()].to_vec(), + [expected_regions[1].clone()].to_vec(), + [expected_regions[2].clone()].to_vec(), + [expected_regions[3].clone()].to_vec(), + ] + ); + } + + // Next, we have a more sophisticated test with a chunk size of 5 pages. + { + let chunks = table + .clone() + .partition(page_size * 5) + .map(|table| table.data) + .collect::>(); + + // The implementation currently returns the ranges in reverse order. + // For better testability, we reverse it. + let chunks = chunks + .into_iter() + .map(|vec| vec.into_iter().rev().collect::>()) + .rev() + .collect::>(); + + assert_eq!( + chunks, + &[ + vec![ + MemoryRange { + gpa: start_gpa, + length: 2 * page_size + }, + MemoryRange { + gpa: start_gpa + 4 * page_size, + length: page_size + } + ], + vec![ + MemoryRange { + gpa: start_gpa + 5 * page_size, + length: page_size + }, + MemoryRange { + gpa: start_gpa + 8 * page_size, + length: 2 * page_size + }, + MemoryRange { + gpa: start_gpa + 12 * page_size, + length: 2 * page_size + } + ] + ] + ); + } + } + + #[test] + fn test_memory_range_table_partition_uneven_split() { + // Three consecutive dirty pages produce one 3-page range, which lets + // us test an uneven 1+2 page split while using the same helper as the + // other partition tests above. + let input = [0b111]; + let start_gpa = 0x1000; + let page_size = 0x1000; + + let table = MemoryRangeTable::from_dirty_bitmap(input, start_gpa, page_size); + + let chunks = table + .partition(page_size * 2) + .map(|table| table.data) + .collect::>(); + + // The implementation currently returns ranges in reverse order. + let chunks = chunks.into_iter().rev().collect::>(); + + assert_eq!( + chunks, + &[ + vec![MemoryRange { + gpa: start_gpa, + length: page_size, + }], + vec![MemoryRange { + gpa: start_gpa + page_size, + length: page_size * 2, + }], + ] + ); + } } From 5a2dea8fa6ec028df5a2642835b852aa94a52c46 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Thu, 12 Mar 2026 13:23:20 +0100 Subject: [PATCH 1069/1893] 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 --- vmm/src/sync_utils.rs | 127 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 vmm/src/sync_utils.rs diff --git a/vmm/src/sync_utils.rs b/vmm/src/sync_utils.rs new file mode 100644 index 0000000000..14517eac24 --- /dev/null +++ b/vmm/src/sync_utils.rs @@ -0,0 +1,127 @@ +// Copyright © 2026 Cyberus Technology GmbH +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::sync::{Condvar, Mutex}; + +/// A single use abortable gate. The main thread will create the gate and pass +/// it to the memory sending threads. The main thread can always open the gate. +/// That way the main thread can also open the gate before all workers arrive +/// there, e.g. if one worker signals that an error occurred and thus cannot +/// continue. +#[derive(Debug)] +pub struct Gate { + /// True if the gate is open, false otherwise. + open: Mutex, + /// Used to notify waiting threads. + cv: Condvar, +} + +impl Gate { + pub fn new() -> Self { + Self { + open: Mutex::new(false), + cv: Condvar::new(), + } + } + + /// Wait at the gate. Only blocks if the gate is not opened. + pub fn wait(&self) { + let mut open = self.open.lock().unwrap(); + while !*open { + open = self.cv.wait(open).unwrap(); + } + } + + /// Open the gate, releasing all waiting threads. + pub fn open(&self) { + let mut open = self.open.lock().unwrap(); + *open = true; + self.cv.notify_all(); + } +} + +#[cfg(test)] +mod tests { + use std::sync::{Arc, mpsc}; + use std::thread; + use std::time::Duration; + + use super::Gate; + + #[test] + fn gate_blocks_until_open() { + let gate = Arc::new(Gate::new()); + let (tx, rx) = mpsc::channel(); + + let gate_clone = gate.clone(); + thread::spawn(move || { + gate_clone.wait(); + tx.send(()).unwrap(); + }); + + // Give the thread time to block. + thread::sleep(Duration::from_millis(50)); + assert!(rx.try_recv().is_err()); + + gate.open(); + rx.recv_timeout(Duration::from_secs(1)).unwrap(); + } + + #[test] + fn gate_open_before_wait_is_non_blocking() { + let gate = Arc::new(Gate::new()); + gate.open(); + + let (tx, rx) = mpsc::channel(); + let gate_clone = gate.clone(); + thread::spawn(move || { + gate_clone.wait(); + tx.send(()).unwrap(); + }); + + rx.recv_timeout(Duration::from_secs(1)).unwrap(); + } + + #[test] + fn gate_releases_multiple_waiters() { + let gate = Arc::new(Gate::new()); + let (tx, rx) = mpsc::channel(); + + for _ in 0..4 { + let gate_clone = gate.clone(); + let tx = tx.clone(); + thread::spawn(move || { + gate_clone.wait(); + tx.send(()).unwrap(); + }); + } + + // Ensure nobody passed before open. + thread::sleep(Duration::from_millis(50)); + assert!(rx.try_recv().is_err()); + + gate.open(); + + for _ in 0..4 { + rx.recv_timeout(Duration::from_secs(1)).unwrap(); + } + } + + #[test] + fn gate_open_is_idempotent() { + let gate = Arc::new(Gate::new()); + gate.open(); + gate.open(); + + let (tx, rx) = mpsc::channel(); + let gate_clone = gate.clone(); + thread::spawn(move || { + gate_clone.wait(); + tx.send(()).unwrap(); + }); + + rx.recv_timeout(Duration::from_secs(1)).unwrap(); + } +} From 07484abd35acb70584ab6f406b5f7bb8f400a494 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Thu, 12 Mar 2026 13:29:24 +0100 Subject: [PATCH 1070/1893] 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 --- vmm/src/lib.rs | 1 + vmm/src/migration_transport.rs | 339 +++++++++++++++++++++++++++++++++ 2 files changed, 340 insertions(+) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index be9ebf929b..60ec64b617 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -90,6 +90,7 @@ mod pci_segment; pub mod seccomp_filters; mod serial_manager; mod sigwinch_listener; +mod sync_utils; mod uffd; mod userfaultfd; pub mod vm; diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 27fc464fad..4568de9aa9 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -5,12 +5,17 @@ use std::io::{self, ErrorKind, Read, Write}; use std::net::{TcpListener, TcpStream}; +use std::num::NonZeroU32; use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::{UnixListener, UnixStream}; use std::path::PathBuf; use std::result::Result; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::{Receiver, Sender, SyncSender, TrySendError, channel, sync_channel}; +use std::sync::{Arc, Mutex}; use std::thread; +use std::time::Duration; use anyhow::{Context, anyhow}; use log::{debug, error, info, warn}; @@ -24,6 +29,7 @@ use vm_migration::protocol::{Command, MemoryRangeTable, Request, Response}; use vm_migration::{MigratableError, Snapshot}; use vmm_sys_util::eventfd::EventFd; +use crate::sync_utils::Gate; use crate::{GuestMemoryMmap, VmMigrationConfig}; /// Transport-agnostic listener used to receive connections. @@ -394,6 +400,339 @@ impl Drop for ReceiveAdditionalConnections { } } +/// The different kinds of messages we can send to memory sending threads. +#[derive(Debug)] +enum SendMemoryThreadMessage { + /// A chunk of memory that the thread should send to the receiving side of the + /// live migration. + Memory(MemoryRangeTable), + /// A synchronization point after each iteration of sending memory. That way the + /// main thread knows when all memory is sent and acknowledged. + Gate(Arc), + /// Sending memory is done and the threads are not needed anymore. + Disconnect, +} + +/// The different kinds of messages the main thread can receive from a memory +/// sending thread. +#[derive(Debug)] +enum SendMemoryThreadNotify { + /// A sending thread arrived at the gate. The main thread does not wait at the + /// gate, otherwise we could miss error messages. + Gate, + /// A sending thread encountered an error while sending memory. + Error, +} + +/// This struct keeps track of additional threads we use to send VM memory. +pub(crate) struct SendAdditionalConnections { + guest_memory: GuestMemoryAtomic, + threads: Vec>>, + /// Sender to all workers. The receiver is shared by all workers. + message_tx: SyncSender, + /// If an error occurs in one of the memory sending threads, the main thread signals + /// this using this flag. Only the main thread checks this variable, the worker + /// threads will be stopped during cleanup. + worker_error: Arc, + /// After the main thread sent all memory chunks to the sender threads, it waits + /// until one of the workers notifies it. Either because an error occurred, or + /// because they arrived at the gate. + notify_rx: Receiver, +} + +impl SendAdditionalConnections { + /// How many requests can be queued for each connection before the main + /// thread has to wait for workers to catch up. This bounded [`SyncChannel`] + /// provides backpressure, so send_chunk() re-checks worker_error promptly + /// instead of queueing all memory descriptors up front and only noticing + /// failures at the next gate synchronization point. + const BUFFERED_REQUESTS_PER_THREAD: usize = 64; + + /// The size of each chunk of memory to send. + /// + /// We want to make this large, because each chunk is acknowledged and we wait + /// for the ack before sending the next chunk. The challenge is that if it is + /// _too_ large, we become more sensitive to network issues, like packet drops + /// in individual connections, because large amounts of data can pool when + /// throughput on one connection is temporarily reduced. + /// + /// We can consider making this configurable, but a better network protocol that + /// doesn't require ACKs would be more efficient. + /// + /// The best-case throughput per connection can be estimated via: + /// chunk_size / (chunk_size / throughput_per_connection + round_trip_time) + /// + /// This chunk size together with eight connections is sufficient to saturate a 100G link. + const CHUNK_SIZE: u64 = 64 /* MiB */ << 20; + + pub(crate) fn new( + destination: &str, + connections: NonZeroU32, + guest_memory: &GuestMemoryAtomic, + ) -> Result { + let mut threads = Vec::new(); + let configured_connections = connections.get(); + let buffer_size = Self::BUFFERED_REQUESTS_PER_THREAD * configured_connections as usize; + let (message_tx, message_rx) = sync_channel::(buffer_size); + let worker_error = Arc::new(AtomicBool::new(false)); + let (notify_tx, notify_rx) = channel::(); + + // If one connection is configured, we don't have to create any additional threads. + // In this case the main thread does the sending. + if configured_connections == 1 { + return Ok(Self { + guest_memory: guest_memory.clone(), + threads, + message_tx, + worker_error, + notify_rx, + }); + } + + let message_rx = Arc::new(Mutex::new(message_rx)); + // If we use multiple threads to send memory, the main thread only distributes + // the memory chunks to the workers, but does not send memory anymore. Thus in + // this case we create one additional thread for each connection. + for n in 0..configured_connections { + let mut socket = send_migration_socket(destination)?; + let guest_memory = guest_memory.clone(); + let message_rx = message_rx.clone(); + let worker_error = worker_error.clone(); + let notify_tx = notify_tx.clone(); + + let thread = thread::Builder::new() + .name(format!("migrate-send-memory-{n}")) + .spawn(move || { + Self::worker_send_memory( + &mut socket, + &guest_memory, + &message_rx, + &worker_error, + ¬ify_tx, + ) + }) + .inspect_err(|_| { + // If an error occurs here, we still do some light cleanup. + for _ in 0..threads.len() { + message_tx.send(SendMemoryThreadMessage::Disconnect).ok(); + } + threads.drain(..).for_each(|thread| { + thread.join().ok(); + }); + }) + .context("Error spawning send-memory thread") + .map_err(MigratableError::MigrateSend)?; + threads.push(thread); + } + + Ok(Self { + guest_memory: guest_memory.clone(), + threads, + message_tx, + worker_error, + notify_rx, + }) + } + + fn worker_send_memory( + socket: &mut SocketStream, + guest_memory: &GuestMemoryAtomic, + message_rx: &Mutex>, + worker_error: &AtomicBool, + notify_tx: &Sender, + ) -> Result<(), MigratableError> { + info!("Spawned thread to send VM memory."); + loop { + // Every memory sending thread receives messages from the main thread through this + // channel. The lock is necessary to synchronize the multiple consumers. If the + // workers are very quick, lock contention could become a performance issue. + let message = message_rx + .lock() + .map_err(|_| MigratableError::MigrateSend(anyhow!("message_rx mutex is poisoned"))) + .inspect_err(|_| { + worker_error.store(true, Ordering::Relaxed); + // We ignore errors during error handling. + notify_tx.send(SendMemoryThreadNotify::Error).ok(); + })? + .recv() + .context("Error receiving message from main thread") + .map_err(MigratableError::MigrateSend) + .inspect_err(|_| { + worker_error.store(true, Ordering::Relaxed); + notify_tx.send(SendMemoryThreadNotify::Error).ok(); + })?; + match message { + SendMemoryThreadMessage::Memory(table) => { + send_memory_ranges(guest_memory, &table, socket) + .inspect_err(|_| { + worker_error.store(true, Ordering::Relaxed); + notify_tx.send(SendMemoryThreadNotify::Error).ok(); + }) + .context("Error sending memory to receiver side") + .map_err(MigratableError::MigrateSend)?; + } + SendMemoryThreadMessage::Gate(gate) => { + notify_tx + .send(SendMemoryThreadNotify::Gate) + .context("Error sending gate notification to main thread") + .map_err(MigratableError::MigrateSend) + .inspect_err(|_| { + // Sending via `notify_tx` just failed, so we don't try to send another + // message via it. + worker_error.store(true, Ordering::Relaxed); + })?; + gate.wait(); + } + SendMemoryThreadMessage::Disconnect => { + return Ok(()); + } + } + } + } + + /// Send memory via all connections that we have. `socket` is the original socket + /// that was used to connect to the destination. Returns Ok(true) if memory was + /// sent, Ok(false) if the given table was empty. + /// + /// When this function returns, all memory has been sent and acknowledged. + pub(crate) fn send_memory( + &mut self, + table: MemoryRangeTable, + socket: &mut SocketStream, + ) -> Result { + if table.regions().is_empty() { + return Ok(false); + } + + // If we use only one connection, we send the memory directly. + if self.threads.is_empty() { + send_memory_ranges(&self.guest_memory, &table, socket)?; + return Ok(true); + } + + // The chunk size is chosen to be big enough so that even very fast links need some + // milliseconds to send it. + for chunk in table.partition(Self::CHUNK_SIZE) { + self.send_chunk(chunk)?; + } + + self.wait_for_pending_data()?; + Ok(true) + } + + fn send_chunk(&mut self, chunk: MemoryRangeTable) -> Result<(), MigratableError> { + let mut chunk = SendMemoryThreadMessage::Memory(chunk); + // [`Self::message_tx`] has a limited size, so we may have to retry sending the chunk + loop { + if self.worker_error.load(Ordering::Relaxed) { + return self.cleanup(); + } + + // Use try_send() so we can keep checking worker_error while the + // workers catch up. A blocking send() could wait forever if a + // worker failed and stopped making progress. + match self.message_tx.try_send(chunk) { + Ok(()) => { + return Ok(()); + } + Err(TrySendError::Full(unsent_chunk)) => { + // The channel is full. We wait for a short time and retry. + thread::sleep(Duration::from_millis(10)); + chunk = unsent_chunk; + } + Err(TrySendError::Disconnected(_)) => { + // The workers didn't disconnect for no reason, thus we do a cleanup. + return Err(self.cleanup().err().unwrap_or(MigratableError::MigrateSend( + anyhow!("All sending threads disconnected, but none returned an error?"), + ))); + } + } + } + } + + /// Wait until all data that is in-flight has actually been sent and acknowledged. + fn wait_for_pending_data(&mut self) -> Result<(), MigratableError> { + let gate = Arc::new(Gate::new()); + for _ in 0..self.threads.len() { + self.message_tx + .send(SendMemoryThreadMessage::Gate(gate.clone())) + .context("Error sending gate message to workers") + .map_err(MigratableError::MigrateSend)?; + } + + // We cannot simply wait at the gate, otherwise we might miss it when a sender + // thread encounters an error. Thus we wait for the workers to notify us that + // they arrived at the gate. + let mut seen_threads = 0; + loop { + match self + .notify_rx + .recv() + .context("Error receiving message from workers") + .map_err(MigratableError::MigrateSend)? + { + SendMemoryThreadNotify::Gate => { + seen_threads += 1; + if seen_threads == self.threads.len() { + gate.open(); + return Ok(()); + } + } + SendMemoryThreadNotify::Error => { + // If an error occurred in one of the worker threads, we open + // the gate to make sure that no thread hangs. After that, we + // receive the error from Self::cleanup() and return it. + gate.open(); + return self.cleanup(); + } + } + } + } + + /// Sends disconnect messages to all workers and joins them. + pub(crate) fn cleanup(&mut self) -> Result<(), MigratableError> { + // Send disconnect messages to all workers. + for _ in 0..self.threads.len() { + // All threads may have terminated, leading to a dropped receiver. Thus we ignore + // errors here. + self.message_tx + .try_send(SendMemoryThreadMessage::Disconnect) + .ok(); + } + + let mut first_err = Ok(()); + self.threads.drain(..).for_each(|thread| { + let err = match thread.join() { + Ok(Ok(())) => None, + Ok(Err(e)) => Some(e), + Err(panic) => Some(MigratableError::MigrateSend(anyhow!( + "send-memory thread panicked: {panic:?}" + ))), + }; + + if let Some(e) = err { + warn!("Error in send-memory thread: {e}"); + + if first_err.is_ok() { + first_err = Err(e); + } + } + }); + + first_err + } +} + +impl Drop for SendAdditionalConnections { + fn drop(&mut self) { + if !self.threads.is_empty() { + warn!( + "SendAdditionalConnections was not cleaned up! Either cleanup() was never called (programming error) or it failed before completing." + ); + } + } +} + /// Extract a UNIX socket path from a "unix:" migration URL. fn socket_url_to_path(url: &str) -> Result { url.strip_prefix("unix:") From 55e6971c47f39c80ee7a0016f98e16aad75fff46 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Thu, 12 Mar 2026 14:30:43 +0100 Subject: [PATCH 1071/1893] 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 --- vmm/src/lib.rs | 111 ++++++++++++++++++++++++--------- vmm/src/migration_transport.rs | 16 +++++ 2 files changed, 97 insertions(+), 30 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 60ec64b617..a93dd1bdd0 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use std::fs::File; use std::io::{Read, Write, stdout}; +use std::num::NonZero; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::panic::AssertUnwindSafe; #[cfg(feature = "guest_debug")] @@ -59,7 +60,9 @@ use crate::memory_manager::MemoryManager; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] use crate::migration::get_vm_snapshot; use crate::migration::{recv_vm_config, recv_vm_state}; -use crate::migration_transport::SocketStream; +use crate::migration_transport::{ + ReceiveAdditionalConnections, ReceiveListener, SendAdditionalConnections, SocketStream, +}; use crate::seccomp_filters::{Thread, get_seccomp_filter}; use crate::vm::{Error as VmError, Vm, VmState}; use crate::vm_config::{ @@ -613,6 +616,13 @@ pub struct Vmm { console_info: Option, } +/// Just a wrapper for the data that goes into +/// [`ReceiveMigrationState::Configured`] +struct ReceiveMigrationConfiguredData { + memory_manager: Arc>, + guest_memory: GuestMemoryAtomic, + connections: ReceiveAdditionalConnections, +} /// The receiver's state machine behind the migration protocol. enum ReceiveMigrationState { /// The connection is established and we haven't received any commands yet. @@ -630,10 +640,7 @@ enum ReceiveMigrationState { /// /// We keep the memory manager around to pass it into the next state. From this point /// on, the sender can start sending memory updates. - Configured( - Arc>, - GuestMemoryAtomic, - ), + Configured(ReceiveMigrationConfiguredData), /// Memory is populated and we received the state. The VM is ready to go. StateReceived, @@ -833,6 +840,7 @@ impl Vmm { fn vm_receive_migration_step( &mut self, socket: &mut SocketStream, + listener: &ReceiveListener, state: ReceiveMigrationState, req: &Request, _receive_data_migration: &VmReceiveMigrationData, @@ -845,19 +853,25 @@ impl Vmm { ))) }; - let mut configure_vm = |socket: &mut SocketStream, - memory_files: HashMap| - -> std::result::Result< - ( - Arc>, - GuestMemoryAtomic, - ), - MigratableError, - > { - let memory_manager = self.vm_receive_config(req, socket, memory_files)?; - let guest_memory = memory_manager.lock().unwrap().guest_memory(); - Ok((memory_manager, guest_memory)) - }; + let mut configure_vm = + |socket: &mut SocketStream, + memory_files: HashMap| + -> std::result::Result { + let memory_manager = self.vm_receive_config(req, socket, memory_files)?; + let guest_memory = memory_manager.lock().unwrap().guest_memory(); + // Create the additional-connection receiver even in the single-connection case. + // At this point the receiver does not know whether the sender will use extra TCP + // connections. If it does not, no worker connections are accepted and memory + // requests continue to arrive on the main connection. + let connections = listener + .try_clone() + .and_then(|l| ReceiveAdditionalConnections::new(l, guest_memory.clone()))?; + Ok(ReceiveMigrationConfiguredData { + memory_manager, + guest_memory, + connections, + }) + }; let recv_memory_fd = |socket: &mut SocketStream, mut memory_files: Vec<(u32, File)>| @@ -880,24 +894,42 @@ impl Vmm { }, Started => match req.command() { Command::MemoryFd => recv_memory_fd(socket, Vec::new()).map(MemoryFdsReceived), - Command::Config => { - configure_vm(socket, Default::default()).map(|res| Configured(res.0, res.1)) - } + Command::Config => configure_vm(socket, Default::default()).map(Configured), _ => invalid_command(), }, MemoryFdsReceived(memory_files) => match req.command() { Command::MemoryFd => recv_memory_fd(socket, memory_files).map(MemoryFdsReceived), - Command::Config => configure_vm(socket, HashMap::from_iter(memory_files)) - .map(|res| Configured(res.0, res.1)), + Command::Config => { + configure_vm(socket, HashMap::from_iter(memory_files)).map(Configured) + } _ => invalid_command(), }, - Configured(memory_manager, guest_memory) => match req.command() { + Configured(mut config_data) => match req.command() { + // Memory commands use the main connection only in the single-connection case. + // When multiple TCP connections are configured, the worker connections carry + // all memory commands and the main connection is used only for control traffic. Command::Memory => { - migration_transport::receive_memory_ranges(&guest_memory, req, socket)?; - Ok(Configured(memory_manager, guest_memory)) + migration_transport::receive_memory_ranges( + &config_data.guest_memory, + req, + socket, + ) + .inspect_err(|_| { + // connections.cleanup() already logs all errors that occurred in one of the + // threads. Furthermore, this path is only taken in the single-connection case, + // thus we do not expect any errors during this cleanup. The warning should + // reflect that. + if let Err(e) = config_data.connections.cleanup() { + warn!( + "Unexpected error while cleaning up migration connections after a main-connection memory receive failure: {e}" + ); + } + })?; + Ok(Configured(config_data)) } Command::State => { - self.vm_receive_state(req, socket, memory_manager)?; + config_data.connections.cleanup()?; + self.vm_receive_state(req, socket, config_data.memory_manager)?; Ok(StateReceived) } _ => invalid_command(), @@ -1082,6 +1114,7 @@ impl Vmm { socket: &mut SocketStream, ctx: &mut MemoryMigrationContext, is_converged: impl Fn(&MemoryMigrationContext) -> result::Result, + mem_send: &mut SendAdditionalConnections, ) -> result::Result { loop { let iteration_begin = Instant::now(); @@ -1101,7 +1134,7 @@ impl Vmm { // Send the current dirty pages let transfer_begin = Instant::now(); - migration_transport::send_memory_ranges(&vm.guest_memory(), &iteration_table, socket)?; + mem_send.send_memory(iteration_table, socket)?; let transfer_duration = transfer_begin.elapsed(); ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); @@ -1221,6 +1254,7 @@ impl Vmm { vm: &mut Vm, socket: &mut SocketStream, send_data_migration: &VmSendMigrationData, + mem_send: &mut SendAdditionalConnections, ) -> result::Result<(), MigratableError> { let mut ctx = MemoryMigrationContext::new(); @@ -1231,6 +1265,7 @@ impl Vmm { &mut ctx, // We bind send_data_migration to the callback |ctx| Self::is_precopy_converged(ctx, send_data_migration), + mem_send, )?; vm.pause()?; @@ -1243,7 +1278,7 @@ impl Vmm { ctx.update_metrics_before_transfer(iteration_begin, &final_table); let transfer_begin = Instant::now(); - migration_transport::send_memory_ranges(&vm.guest_memory(), &final_table, socket)?; + mem_send.send_memory(final_table, socket)?; let transfer_duration = transfer_begin.elapsed(); ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); ctx.iteration += 1; @@ -1330,7 +1365,22 @@ impl Vmm { // Now pause VM vm.pause()?; } else { - Self::do_memory_migration(vm, &mut socket, send_data_migration)?; + let mut mem_send = migration_transport::SendAdditionalConnections::new( + &send_data_migration.destination_url, + NonZero::new(1).unwrap(), + &vm.guest_memory(), + )?; + + Self::do_memory_migration(vm, &mut socket, send_data_migration, &mut mem_send) + .inspect_err(|_| { + // Calling cleanup multiple times is fine, thus here we just make sure + // that it is called. + if let Err(e) = mem_send.cleanup() { + warn!("Error cleaning up migration connections: {e}"); + } + })?; + + mem_send.cleanup()?; } // We release the locks early to enable locking them on the destination host. @@ -2302,6 +2352,7 @@ impl RequestHandler for Vmm { let (response, new_state) = match self.vm_receive_migration_step( &mut socket, + &listener, state, &req, &receive_data_migration, diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 4568de9aa9..1ee71506d8 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -72,6 +72,22 @@ impl ReceiveListener { Ok(None) } } + + /// Tries to clone a [`ReceiveListener`]. + pub(crate) fn try_clone(&self) -> Result { + match self { + ReceiveListener::Tcp(listener) => listener + .try_clone() + .map(ReceiveListener::Tcp) + .context("Failed to clone TCP listener") + .map_err(MigratableError::MigrateReceive), + ReceiveListener::Unix(listener) => listener + .try_clone() + .map(ReceiveListener::Unix) + .context("Failed to clone Unix listener") + .map_err(MigratableError::MigrateReceive), + } + } } impl AsFd for ReceiveListener { From fb19881918a43544bdf7d58a6297dba07918d06f Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Thu, 12 Mar 2026 15:08:07 +0100 Subject: [PATCH 1072/1893] 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 On-behalf-of: SAP sebastian.eydam@sap.com Signed-off-by: Sebastian Eydam --- vmm/src/api/mod.rs | 42 +++++++++++++++++++---- vmm/src/api/openapi/cloud-hypervisor.yaml | 5 +++ vmm/src/lib.rs | 3 +- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 4b51c7eb3d..10457d7d38 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -34,7 +34,7 @@ pub mod dbus; pub mod http; use std::io; -use std::num::NonZeroU64; +use std::num::{NonZeroU32, NonZeroU64}; use std::str::FromStr; use std::sync::mpsc::{RecvError, SendError, Sender, channel}; use std::time::Duration; @@ -302,7 +302,7 @@ pub struct VmSendMigrationParseError(#[source] OptionParserError); #[derive(Clone, Deserialize, Serialize, Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct VmSendMigrationData { - /// URL to migrate the VM to + /// Migration destination, e.g. `tcp::` or `unix:/path/to/socket`. pub destination_url: String, /// Send memory across socket without copying #[serde(default)] @@ -318,13 +318,17 @@ pub struct VmSendMigrationData { /// The timeout strategy for the migration. #[serde(default)] pub timeout_strategy: TimeoutStrategy, + + /// The number of parallel connections for migration. + #[serde(default = "VmSendMigrationData::default_connections")] + pub connections: NonZeroU32, } impl VmSendMigrationData { pub const SYNTAX: &'static str = "VM send migration parameters \ \"destination_url=[,local=on|off,\ downtime_ms=,timeout_s=,\ - timeout_strategy=cancel|ignore]\""; + timeout_strategy=cancel|ignore,connections=]\""; // Same as QEMU. pub const DEFAULT_DOWNTIME: Duration = Duration::from_millis(300); @@ -339,6 +343,11 @@ impl VmSendMigrationData { NonZeroU64::new(Self::DEFAULT_TIMEOUT.as_secs()).unwrap() } + // Use a single connection as default for backward compatibility. + fn default_connections() -> NonZeroU32 { + NonZeroU32::new(1).unwrap() + } + pub fn parse(migration: &str) -> Result { let mut parser = OptionParser::new(); parser @@ -346,7 +355,8 @@ impl VmSendMigrationData { .add("local") .add("downtime_ms") .add("timeout_s") - .add("timeout_strategy"); + .add("timeout_strategy") + .add("connections"); parser.parse(migration).map_err(VmSendMigrationParseError)?; let destination_url = parser.get("destination_url").ok_or_else(|| { @@ -385,6 +395,17 @@ impl VmSendMigrationData { .convert("timeout_strategy") .map_err(VmSendMigrationParseError)? .unwrap_or_default(); + let connections = match parser + .convert::("connections") + .map_err(VmSendMigrationParseError)? + { + Some(v) => NonZeroU32::new(v).ok_or_else(|| { + VmSendMigrationParseError(OptionParserError::InvalidValue( + "connections must be non-zero".to_string(), + )) + })?, + None => Self::default_connections(), + }; Ok(Self { destination_url, @@ -392,6 +413,7 @@ impl VmSendMigrationData { downtime_ms, timeout_s, timeout_strategy, + connections, }) } @@ -1679,13 +1701,14 @@ mod unit_tests { fn test_vm_send_migration_data_parse() { // Fully specified let data = VmSendMigrationData::parse( - "destination_url=tcp://192.168.1.1:8080,local=on,downtime_ms=200,timeout_s=3600,timeout_strategy=cancel" + "destination_url=tcp://192.168.1.1:8080,local=on,downtime_ms=200,timeout_s=3600,timeout_strategy=cancel,connections=2" ).expect("valid migration string should parse"); assert_eq!(data.destination_url, "tcp://192.168.1.1:8080"); assert!(data.local); assert_eq!(data.downtime_ms.get(), 200); assert_eq!(data.timeout_s.get(), 3600); assert_eq!(data.timeout_strategy, TimeoutStrategy::Cancel); + assert_eq!(data.connections.get(), 2); // Defaults applied when optional fields are omitted let data = VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080") @@ -1695,6 +1718,7 @@ mod unit_tests { assert_eq!(data.downtime_ms, VmSendMigrationData::default_downtime_ms()); assert_eq!(data.timeout_s, VmSendMigrationData::default_timeout_s()); assert_eq!(data.timeout_strategy, TimeoutStrategy::default()); + assert_eq!(data.connections, VmSendMigrationData::default_connections()); // Missing destination_url is an error VmSendMigrationData::parse("local=on,downtime_ms=200").unwrap_err(); @@ -1708,6 +1732,10 @@ mod unit_tests { let _data = VmSendMigrationData::parse("destination_url=unix:/tmp/sock,timeout_s=0") .expect_err("zero timeout_s should be rejected"); + // Zero connections is rejected + let _data = VmSendMigrationData::parse("destination_url=unix:/tmp/sock,connections=0") + .expect_err("zero connections should be rejected"); + // Unknown option is an error VmSendMigrationData::parse("destination_url=unix:/tmp/sock,unknown_field=foo").unwrap_err(); @@ -1732,12 +1760,13 @@ mod unit_tests { downtime_ms: NonZeroU64::new(150).unwrap(), timeout_s: VmSendMigrationData::default_timeout_s(), timeout_strategy: Default::default(), + connections: VmSendMigrationData::default_connections(), } ); // Happy path, fully specified let data = - VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,downtime_ms=150,timeout_s=900,timeout_strategy=ignore") + VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,downtime_ms=150,timeout_s=900,timeout_strategy=ignore,connections=4") .unwrap(); assert_eq!( data, @@ -1747,6 +1776,7 @@ mod unit_tests { downtime_ms: NonZeroU64::new(150).unwrap(), timeout_s: NonZeroU64::new(900).unwrap(), timeout_strategy: TimeoutStrategy::Ignore, + connections: NonZeroU32::new(4).unwrap(), } ); } diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 14f74018ea..c2fe5af4b9 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -1405,6 +1405,11 @@ components: Defaults to 3600s (one hour). timeout_strategy: $ref: "#/components/schemas/TimeoutStrategy" + connections: + type: integer + format: int64 + default: 1 + minimum: 1 VmAddUserDevice: required: diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index a93dd1bdd0..f1226090c9 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -6,7 +6,6 @@ use std::collections::HashMap; use std::fs::File; use std::io::{Read, Write, stdout}; -use std::num::NonZero; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::panic::AssertUnwindSafe; #[cfg(feature = "guest_debug")] @@ -1367,7 +1366,7 @@ impl Vmm { } else { let mut mem_send = migration_transport::SendAdditionalConnections::new( &send_data_migration.destination_url, - NonZero::new(1).unwrap(), + send_data_migration.connections, &vm.guest_memory(), )?; From a9a832f392447101762f6a4f6937603a06ed8156 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Mon, 30 Mar 2026 15:14:43 +0200 Subject: [PATCH 1073/1893] 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 --- cloud-hypervisor/src/bin/ch-remote.rs | 2 +- vmm/src/api/mod.rs | 116 ++++++++++++++++++++------ vmm/src/lib.rs | 7 +- 3 files changed, 96 insertions(+), 29 deletions(-) diff --git a/cloud-hypervisor/src/bin/ch-remote.rs b/cloud-hypervisor/src/bin/ch-remote.rs index afc41e7e96..236e7438e0 100644 --- a/cloud-hypervisor/src/bin/ch-remote.rs +++ b/cloud-hypervisor/src/bin/ch-remote.rs @@ -72,7 +72,7 @@ enum Error { #[error("Invalid disk size")] InvalidDiskSize(#[source] ByteSizedParseError), #[error("Error parsing send migration configuration")] - SendMigrationConfig(#[from] vmm::api::VmSendMigrationParseError), + SendMigrationConfig(#[from] vmm::api::VmSendMigrationConfigError), } enum TargetApi<'a> { diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 10457d7d38..f66fbe9ab0 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -295,8 +295,13 @@ impl FromStr for TimeoutStrategy { } #[derive(Debug, Error)] -#[error("Error parsing send migration parameters")] -pub struct VmSendMigrationParseError(#[source] OptionParserError); +pub enum VmSendMigrationConfigError { + #[error("Error parsing send migration parameters")] + ParseError(#[source] OptionParserError), + + #[error("Error validating send migration parameters")] + ValidationError(String), +} /// Configuration for an outgoing migration. #[derive(Clone, Deserialize, Serialize, Debug)] @@ -348,7 +353,7 @@ impl VmSendMigrationData { NonZeroU32::new(1).unwrap() } - pub fn parse(migration: &str) -> Result { + pub fn parse(migration: &str) -> Result { let mut parser = OptionParser::new(); parser .add("destination_url") @@ -357,24 +362,26 @@ impl VmSendMigrationData { .add("timeout_s") .add("timeout_strategy") .add("connections"); - parser.parse(migration).map_err(VmSendMigrationParseError)?; + parser + .parse(migration) + .map_err(VmSendMigrationConfigError::ParseError)?; let destination_url = parser.get("destination_url").ok_or_else(|| { - VmSendMigrationParseError(OptionParserError::InvalidSyntax( + VmSendMigrationConfigError::ParseError(OptionParserError::InvalidSyntax( "destination_url is required".to_string(), )) })?; let local = parser .convert::("local") - .map_err(VmSendMigrationParseError)? + .map_err(VmSendMigrationConfigError::ParseError)? .unwrap_or(Toggle(false)) .0; let downtime_ms = match parser .convert::("downtime_ms") - .map_err(VmSendMigrationParseError)? + .map_err(VmSendMigrationConfigError::ParseError)? { Some(v) => NonZeroU64::new(v).ok_or_else(|| { - VmSendMigrationParseError(OptionParserError::InvalidValue( + VmSendMigrationConfigError::ParseError(OptionParserError::InvalidValue( "downtime_ms must be non-zero".to_string(), )) })?, @@ -382,10 +389,10 @@ impl VmSendMigrationData { }; let timeout_s = match parser .convert::("timeout_s") - .map_err(VmSendMigrationParseError)? + .map_err(VmSendMigrationConfigError::ParseError)? { Some(v) => NonZeroU64::new(v).ok_or_else(|| { - VmSendMigrationParseError(OptionParserError::InvalidValue( + VmSendMigrationConfigError::ParseError(OptionParserError::InvalidValue( "timeout_s must be non-zero".to_string(), )) })?, @@ -393,28 +400,32 @@ impl VmSendMigrationData { }; let timeout_strategy = parser .convert("timeout_strategy") - .map_err(VmSendMigrationParseError)? + .map_err(VmSendMigrationConfigError::ParseError)? .unwrap_or_default(); let connections = match parser .convert::("connections") - .map_err(VmSendMigrationParseError)? + .map_err(VmSendMigrationConfigError::ParseError)? { Some(v) => NonZeroU32::new(v).ok_or_else(|| { - VmSendMigrationParseError(OptionParserError::InvalidValue( + VmSendMigrationConfigError::ParseError(OptionParserError::InvalidValue( "connections must be non-zero".to_string(), )) })?, None => Self::default_connections(), }; - Ok(Self { + let data = Self { destination_url, local, downtime_ms, timeout_s, timeout_strategy, connections, - }) + }; + + data.validate()?; + + Ok(data) } pub fn downtime(&self) -> Duration { @@ -424,6 +435,47 @@ impl VmSendMigrationData { pub fn timeout(&self) -> Duration { Duration::from_secs(self.timeout_s.get()) } + + pub fn validate(&self) -> Result<(), VmSendMigrationConfigError> { + match self.destination_url.as_str() { + url if url + .strip_prefix("tcp:") + .is_some_and(|addr| !addr.is_empty()) => {} + url if url + .strip_prefix("unix:") + .is_some_and(|path| !path.is_empty()) => + { + if self.connections.get() > 1 { + return Err(VmSendMigrationConfigError::ValidationError( + "UNIX sockets and connections option cannot be used at the same time." + .to_string(), + )); + } + } + _ => { + return Err(VmSendMigrationConfigError::ValidationError( + "destination_url must use tcp:: or unix:.".to_string(), + )); + } + } + + if self.local { + if !self.destination_url.starts_with("unix:") { + return Err(VmSendMigrationConfigError::ValidationError( + "local option is only supported with UNIX sockets.".to_string(), + )); + } + + if self.connections.get() > 1 { + return Err(VmSendMigrationConfigError::ValidationError( + "local option and connections option cannot be used at the same time." + .to_string(), + )); + } + } + + Ok(()) + } } pub enum ApiResponsePayload { @@ -1701,19 +1753,19 @@ mod unit_tests { fn test_vm_send_migration_data_parse() { // Fully specified let data = VmSendMigrationData::parse( - "destination_url=tcp://192.168.1.1:8080,local=on,downtime_ms=200,timeout_s=3600,timeout_strategy=cancel,connections=2" + "destination_url=unix:/tmp/migrate.sock,local=on,downtime_ms=200,timeout_s=3600,timeout_strategy=cancel" ).expect("valid migration string should parse"); - assert_eq!(data.destination_url, "tcp://192.168.1.1:8080"); + assert_eq!(data.destination_url, "unix:/tmp/migrate.sock"); assert!(data.local); assert_eq!(data.downtime_ms.get(), 200); assert_eq!(data.timeout_s.get(), 3600); assert_eq!(data.timeout_strategy, TimeoutStrategy::Cancel); - assert_eq!(data.connections.get(), 2); + assert_eq!(data.connections.get(), 1); // Defaults applied when optional fields are omitted - let data = VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080") + let data = VmSendMigrationData::parse("destination_url=tcp:192.168.1.1:8080") .expect("minimal migration string should parse"); - assert_eq!(data.destination_url, "tcp://192.168.1.1:8080"); + assert_eq!(data.destination_url, "tcp:192.168.1.1:8080"); assert!(!data.local); assert_eq!(data.downtime_ms, VmSendMigrationData::default_downtime_ms()); assert_eq!(data.timeout_s, VmSendMigrationData::default_timeout_s()); @@ -1725,7 +1777,7 @@ mod unit_tests { // Zero downtime_ms is rejected let _data = - VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,downtime_ms=0") + VmSendMigrationData::parse("destination_url=tcp:192.168.1.1:8080,downtime_ms=0") .expect_err("zero downtime_ms should be rejected"); // Zero timeout_s is rejected @@ -1744,18 +1796,28 @@ mod unit_tests { // Timeout strategy let _data = VmSendMigrationData::parse( - "destination_url=tcp://192.168.1.1:8080,timeout_strategy=invalid", + "destination_url=tcp:192.168.1.1:8080,timeout_strategy=invalid", ) - .expect_err("zero downtime_ms should be rejected"); + .expect_err("invalid timeout strategy should be rejected"); + + // Invalid destination URL scheme is rejected + VmSendMigrationData::parse("destination_url=file:///tmp/migration").unwrap_err(); + + // Local migration requires a UNIX socket destination + VmSendMigrationData::parse("destination_url=tcp:192.168.1.1:8080,local=yes").unwrap_err(); + + // Local migration cannot use multiple connections + VmSendMigrationData::parse("destination_url=unix:/tmp/sock,local=yes,connections=2") + .unwrap_err(); // Happy path with some defaults let data = - VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,downtime_ms=150") + VmSendMigrationData::parse("destination_url=tcp:192.168.1.1:8080,downtime_ms=150") .unwrap(); assert_eq!( data, VmSendMigrationData { - destination_url: "tcp://192.168.1.1:8080".to_string(), + destination_url: "tcp:192.168.1.1:8080".to_string(), local: false, downtime_ms: NonZeroU64::new(150).unwrap(), timeout_s: VmSendMigrationData::default_timeout_s(), @@ -1766,12 +1828,12 @@ mod unit_tests { // Happy path, fully specified let data = - VmSendMigrationData::parse("destination_url=tcp://192.168.1.1:8080,downtime_ms=150,timeout_s=900,timeout_strategy=ignore,connections=4") + VmSendMigrationData::parse("destination_url=tcp:192.168.1.1:8080,downtime_ms=150,timeout_s=900,timeout_strategy=ignore,connections=4") .unwrap(); assert_eq!( data, VmSendMigrationData { - destination_url: "tcp://192.168.1.1:8080".to_string(), + destination_url: "tcp:192.168.1.1:8080".to_string(), local: false, downtime_ms: NonZeroU64::new(150).unwrap(), timeout_s: NonZeroU64::new(900).unwrap(), diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index f1226090c9..ce5664249b 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -17,7 +17,7 @@ use std::time::Duration; use std::time::Instant; use std::{io, result, thread}; -use anyhow::anyhow; +use anyhow::{Context, anyhow}; #[cfg(feature = "dbus_api")] use api::dbus::{DBusApiOptions, DBusApiShutdownChannels}; use api::http::HttpApiHandle; @@ -2387,6 +2387,11 @@ impl RequestHandler for Vmm { &mut self, send_data_migration: VmSendMigrationData, ) -> result::Result<(), MigratableError> { + send_data_migration + .validate() + .context("Invalid send migration configuration") + .map_err(MigratableError::MigrateSend)?; + info!( "Sending migration: destination_url={},local={},downtime={}ms,timeout={}s,timeout_strategy={:?}", send_data_migration.destination_url, From ecddc6f84297484b60704feed1bde9b71aa51115 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Wed, 1 Apr 2026 16:10:19 +0200 Subject: [PATCH 1074/1893] 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 --- docs/live_migration.md | 4 +++ vmm/src/api/mod.rs | 21 +++++++++++-- vmm/src/api/openapi/cloud-hypervisor.yaml | 5 +++ vmm/src/migration_transport.rs | 38 ++++++++++++++++++++--- 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/docs/live_migration.md b/docs/live_migration.md index ac842d3172..81eed06665 100644 --- a/docs/live_migration.md +++ b/docs/live_migration.md @@ -207,3 +207,7 @@ migration process. Via the API or `ch-remote`, you may specify: Cancel will abort the migration and keep the VM running on the source. Ignore will proceed with the migration regardless of the downtime requirement. Defaults to `cancel`. +- `connections `: \ + The number of parallel TCP connections to use for migration. + Must be between `1` and `128`. Defaults to `1`. + Multiple connections are not supported with local UNIX-socket migration. diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index f66fbe9ab0..e4ee7235ad 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -53,6 +53,7 @@ pub use self::http::{start_http_fd_thread, start_http_path_thread}; use crate::Error as VmmError; use crate::config::RestoreConfig; use crate::device_tree::DeviceTree; +use crate::migration_transport::MAX_MIGRATION_CONNECTIONS; use crate::vm::{Error as VmError, VmState}; use crate::vm_config::{ DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig, @@ -324,7 +325,9 @@ pub struct VmSendMigrationData { #[serde(default)] pub timeout_strategy: TimeoutStrategy, - /// The number of parallel connections for migration. + /// The number of parallel TCP connections for migration. + /// + /// Must be between 1 and `MAX_MIGRATION_CONNECTIONS` inclusive. #[serde(default = "VmSendMigrationData::default_connections")] pub connections: NonZeroU32, } @@ -459,6 +462,12 @@ impl VmSendMigrationData { } } + if self.connections.get() > MAX_MIGRATION_CONNECTIONS { + return Err(VmSendMigrationConfigError::ValidationError(format!( + "connections must not exceed {MAX_MIGRATION_CONNECTIONS}." + ))); + } + if self.local { if !self.destination_url.starts_with("unix:") { return Err(VmSendMigrationConfigError::ValidationError( @@ -1785,8 +1794,14 @@ mod unit_tests { .expect_err("zero timeout_s should be rejected"); // Zero connections is rejected - let _data = VmSendMigrationData::parse("destination_url=unix:/tmp/sock,connections=0") - .expect_err("zero connections should be rejected"); + let _data = + VmSendMigrationData::parse("destination_url=tcp:192.168.1.1:8080,connections=0") + .expect_err("zero connections should be rejected"); + + // Excessive numbers of parallel connections are rejected + let _data = + VmSendMigrationData::parse("destination_url=tcp:192.168.1.1:8080,connections=129") + .expect_err("too many connections should be rejected"); // Unknown option is an error VmSendMigrationData::parse("destination_url=unix:/tmp/sock,unknown_field=foo").unwrap_err(); diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index c2fe5af4b9..01106a257b 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -1410,6 +1410,11 @@ components: format: int64 default: 1 minimum: 1 + maximum: 128 + description: > + The number of parallel TCP connections to use for migration. + Must be between 1 and 128. Multiple connections are not supported + with local UNIX-socket migration. VmAddUserDevice: required: diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index 1ee71506d8..6440dc8fd6 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -32,6 +32,10 @@ use vmm_sys_util::eventfd::EventFd; use crate::sync_utils::Gate; use crate::{GuestMemoryMmap, VmMigrationConfig}; +/// Hard upper bound for migration worker connections on both the sender and +/// receiver side. +pub(crate) const MAX_MIGRATION_CONNECTIONS: u32 = 128; + /// Transport-agnostic listener used to receive connections. #[derive(Debug)] pub(crate) enum ReceiveListener { @@ -288,12 +292,30 @@ impl ReceiveAdditionalConnections { guest_memory: &GuestMemoryAtomic, ) -> Result<(), MigratableError> { let mut threads: Vec>> = Vec::new(); - while let Some(mut socket) = listener.abortable_accept(terminate_fd)? { + let mut first_err = loop { + let socket = match listener.abortable_accept(terminate_fd) { + Ok(socket) => socket, + Err(e) => break Err(e), + }; + let Some(mut socket) = socket else { + break Ok(()); + }; + + if threads.len() >= MAX_MIGRATION_CONNECTIONS as usize { + break Err(MigratableError::MigrateReceive(anyhow!( + "Received more than {MAX_MIGRATION_CONNECTIONS} additional migration connections." + ))); + } + let guest_memory = guest_memory.clone(); - let terminate_fd = terminate_fd + let terminate_fd = match terminate_fd .try_clone() .context("Error cloning terminate fd") - .map_err(MigratableError::MigrateReceive)?; + .map_err(MigratableError::MigrateReceive) + { + Ok(terminate_fd) => terminate_fd, + Err(e) => break Err(e), + }; match thread::Builder::new() .name(format!("migrate-receive-memory-{}", threads.len()).to_owned()) @@ -303,15 +325,21 @@ impl ReceiveAdditionalConnections { Ok(t) => threads.push(t), Err(e) => { error!("Error spawning receive-memory thread: {e}"); - break; + break Err(MigratableError::MigrateReceive( + anyhow!(e).context("Error spawning receive-memory thread"), + )); } } + }; + + if first_err.is_err() { + warn!("Signaling termination due to an error while accepting connections."); + let _ = terminate_fd.write(1); } info!("Stopped accepting additional connections. Cleaning up threads."); // We only return the first error we encounter here. - let mut first_err = Ok(()); for thread in threads { let err = match thread.join() { Ok(Ok(())) => None, From 17919a7a8c5cb1b498045083f3bdb96a07178f01 Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Wed, 1 Apr 2026 16:58:06 +0200 Subject: [PATCH 1075/1893] tests: add integration test for migration with multiple TCP connections On-behalf-of: SAP sebastian.eydam@sap.com Signed-off-by: Sebastian Eydam --- cloud-hypervisor/tests/integration.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index bd4168d771..fded5727ed 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -8528,6 +8528,8 @@ mod vfio { } mod live_migration { + use std::num::NonZeroU32; + use vmm::api::TimeoutStrategy; use crate::*; @@ -9701,7 +9703,11 @@ mod live_migration { .port() } - fn start_live_migration_tcp(src_api_socket: &str, dest_api_socket: &str) -> bool { + fn start_live_migration_tcp( + src_api_socket: &str, + dest_api_socket: &str, + connections: NonZeroU32, + ) -> bool { // Get an available TCP port let migration_port = get_available_port(); let host_ip = "127.0.0.1"; @@ -9723,11 +9729,14 @@ mod live_migration { thread::sleep(Duration::from_secs(1)); // Start the 'send-migration' command on the source + let connections = connections.get(); let mut send_migration = Command::new(clh_command("ch-remote")) .args([ &format!("--api-socket={src_api_socket}"), "send-migration", - &format!("destination_url=tcp:{host_ip}:{migration_port}"), + &format!( + "destination_url=tcp:{host_ip}:{migration_port},connections={connections}" + ), ]) .stdin(Stdio::null()) .stderr(Stdio::piped()) @@ -9778,7 +9787,7 @@ mod live_migration { send_success && receive_success } - fn _test_live_migration_tcp() { + fn _test_live_migration_tcp(connections: NonZeroU32) { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -9860,7 +9869,7 @@ mod live_migration { } // Start TCP live migration assert!( - start_live_migration_tcp(&src_api_socket, &dest_api_socket), + start_live_migration_tcp(&src_api_socket, &dest_api_socket, connections), "Unsuccessful command: 'send-migration' or 'receive-migration'." ); }); @@ -10249,7 +10258,12 @@ mod live_migration { #[test] fn test_live_migration_tcp() { - _test_live_migration_tcp(); + _test_live_migration_tcp(NonZeroU32::new(1).unwrap()); + } + + #[test] + fn test_live_migration_tcp_parallel_connections() { + _test_live_migration_tcp(NonZeroU32::new(8).unwrap()); } #[test] From 8248650e799457fee3068b0bf3b54ffcade4bce1 Mon Sep 17 00:00:00 2001 From: Damian Barabonkov Date: Thu, 2 Apr 2026 10:15:31 -0700 Subject: [PATCH 1076/1893] 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 --- pci/src/vfio.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 53c77a95c1..0fa3ae8365 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -158,6 +158,10 @@ impl VfioMsix { // Update "Message Control" word if offset == 2 && data.len() == 2 { self.bar.set_msg_ctl(LittleEndian::read_u16(data)); + } else if offset == 0 && data.len() == 4 { + // Some guests update MSI-X control through the dword config write path. + self.bar + .set_msg_ctl((LittleEndian::read_u32(data) >> 16) as u16); } let new_enabled = self.bar.enabled(); From 676c0d320bf47549121c33df4848ac68449b574b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 2 Apr 2026 19:40:10 -0700 Subject: [PATCH 1077/1893] 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 --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- fuzz/Cargo.lock | 4 ++-- fuzz/Cargo.toml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac44003adb..f60d47d317 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1336,9 +1336,9 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mshv-bindings" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbfd4f32d185152003679339751839da77c17e18fa8882a11051a236f841426" +checksum = "a94fc3871dd23738188e5bc76a1d1a5930ebcaf9308c560a7274aa62b1770594" dependencies = [ "libc", "num_enum", @@ -1350,9 +1350,9 @@ dependencies = [ [[package]] name = "mshv-ioctls" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f035616abe1e4cbc026a1a8094ff8d3900f5063fe6608309098bc745926fdfd8" +checksum = "1339723fe3a26baf4041459de20ad923e89d312c3bb25dbf9f60738c22a47f5e" dependencies = [ "libc", "mshv-bindings", diff --git a/Cargo.toml b/Cargo.toml index 67d5398f01..dab7e6c727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,8 +56,8 @@ acpi_tables = "0.2.0" kvm-bindings = "0.14.0" kvm-ioctls = "0.24.0" linux-loader = "0.13.2" -mshv-bindings = "0.6.7" -mshv-ioctls = "0.6.7" +mshv-bindings = "0.6.8" +mshv-ioctls = "0.6.8" seccompiler = "0.5.0" vfio-bindings = { version = "0.6.2", default-features = false } vfio-ioctls = { version = "0.6.0", default-features = false } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 8741ab8591..dd07d241c8 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -789,9 +789,9 @@ dependencies = [ [[package]] name = "mshv-bindings" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbfd4f32d185152003679339751839da77c17e18fa8882a11051a236f841426" +checksum = "a94fc3871dd23738188e5bc76a1d1a5930ebcaf9308c560a7274aa62b1770594" dependencies = [ "libc", "num_enum", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 80c59be630..410a4968d7 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -25,7 +25,7 @@ libc = "0.2.183" libfuzzer-sys = "0.4.12" linux-loader = { version = "0.13.2", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } -mshv-bindings = "0.6.7" +mshv-bindings = "0.6.8" net_util = { path = "../net_util" } seccompiler = "0.5.0" virtio-devices = { path = "../virtio-devices" } From f7f9895d579bcb5cccc8248a28ed20671d4216a4 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Fri, 20 Mar 2026 13:07:10 +0100 Subject: [PATCH 1078/1893] 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 --- hypervisor/src/arch/x86/mod.rs | 30 ++++++++++++++++++++++++++++ hypervisor/src/arch/x86/msr_index.rs | 16 +++++++++++++++ hypervisor/src/kvm/mod.rs | 19 +++++++++++++++--- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/hypervisor/src/arch/x86/mod.rs b/hypervisor/src/arch/x86/mod.rs index 78e4d7cc5d..45f820cde7 100644 --- a/hypervisor/src/arch/x86/mod.rs +++ b/hypervisor/src/arch/x86/mod.rs @@ -28,6 +28,36 @@ pub mod msr_index; // MTRR constants pub const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11 pub const MTRR_MEM_TYPE_WB: u64 = 0x6; +pub const MTRR_MSR_INDICES: [u32; 28] = [ + msr_index::MSR_MTRRdefType, + msr_index::MSR_IA32_MTRR_PHYSBASE0, + msr_index::MSR_IA32_MTRR_PHYSMASK0, + msr_index::MSR_IA32_MTRR_PHYSBASE1, + msr_index::MSR_IA32_MTRR_PHYSMASK1, + msr_index::MSR_IA32_MTRR_PHYSBASE2, + msr_index::MSR_IA32_MTRR_PHYSMASK2, + msr_index::MSR_IA32_MTRR_PHYSBASE3, + msr_index::MSR_IA32_MTRR_PHYSMASK3, + msr_index::MSR_IA32_MTRR_PHYSBASE4, + msr_index::MSR_IA32_MTRR_PHYSMASK4, + msr_index::MSR_IA32_MTRR_PHYSBASE5, + msr_index::MSR_IA32_MTRR_PHYSMASK5, + msr_index::MSR_IA32_MTRR_PHYSBASE6, + msr_index::MSR_IA32_MTRR_PHYSMASK6, + msr_index::MSR_IA32_MTRR_PHYSBASE7, + msr_index::MSR_IA32_MTRR_PHYSMASK7, + msr_index::MSR_MTRRfix64K_00000, + msr_index::MSR_MTRRfix16K_80000, + msr_index::MSR_MTRRfix16K_A0000, + msr_index::MSR_MTRRfix4K_C0000, + msr_index::MSR_MTRRfix4K_C8000, + msr_index::MSR_MTRRfix4K_D0000, + msr_index::MSR_MTRRfix4K_D8000, + msr_index::MSR_MTRRfix4K_E0000, + msr_index::MSR_MTRRfix4K_E8000, + msr_index::MSR_MTRRfix4K_F0000, + msr_index::MSR_MTRRfix4K_F8000, +]; // IOAPIC pins pub const NUM_IOAPIC_PINS: usize = 24; diff --git a/hypervisor/src/arch/x86/msr_index.rs b/hypervisor/src/arch/x86/msr_index.rs index 810fe08b9a..607ee3b2c1 100644 --- a/hypervisor/src/arch/x86/msr_index.rs +++ b/hypervisor/src/arch/x86/msr_index.rs @@ -85,6 +85,22 @@ pub const MSR_IA32_RTIT_ADDR3_B: ::std::os::raw::c_uint = 0x00000587; pub const MSR_IA32_RTIT_CR3_MATCH: ::std::os::raw::c_uint = 0x00000572; pub const MSR_IA32_RTIT_OUTPUT_BASE: ::std::os::raw::c_uint = 0x00000560; pub const MSR_IA32_RTIT_OUTPUT_MASK: ::std::os::raw::c_uint = 0x00000561; +pub const MSR_IA32_MTRR_PHYSBASE0: ::std::os::raw::c_uint = 0x00000200; +pub const MSR_IA32_MTRR_PHYSMASK0: ::std::os::raw::c_uint = 0x00000201; +pub const MSR_IA32_MTRR_PHYSBASE1: ::std::os::raw::c_uint = 0x00000202; +pub const MSR_IA32_MTRR_PHYSMASK1: ::std::os::raw::c_uint = 0x00000203; +pub const MSR_IA32_MTRR_PHYSBASE2: ::std::os::raw::c_uint = 0x00000204; +pub const MSR_IA32_MTRR_PHYSMASK2: ::std::os::raw::c_uint = 0x00000205; +pub const MSR_IA32_MTRR_PHYSBASE3: ::std::os::raw::c_uint = 0x00000206; +pub const MSR_IA32_MTRR_PHYSMASK3: ::std::os::raw::c_uint = 0x00000207; +pub const MSR_IA32_MTRR_PHYSBASE4: ::std::os::raw::c_uint = 0x00000208; +pub const MSR_IA32_MTRR_PHYSMASK4: ::std::os::raw::c_uint = 0x00000209; +pub const MSR_IA32_MTRR_PHYSBASE5: ::std::os::raw::c_uint = 0x0000020a; +pub const MSR_IA32_MTRR_PHYSMASK5: ::std::os::raw::c_uint = 0x0000020b; +pub const MSR_IA32_MTRR_PHYSBASE6: ::std::os::raw::c_uint = 0x0000020c; +pub const MSR_IA32_MTRR_PHYSMASK6: ::std::os::raw::c_uint = 0x0000020d; +pub const MSR_IA32_MTRR_PHYSBASE7: ::std::os::raw::c_uint = 0x0000020e; +pub const MSR_IA32_MTRR_PHYSMASK7: ::std::os::raw::c_uint = 0x0000020f; pub const MSR_MTRRfix64K_00000: ::std::os::raw::c_uint = 0x00000250; pub const MSR_MTRRfix16K_80000: ::std::os::raw::c_uint = 0x00000258; pub const MSR_MTRRfix16K_A0000: ::std::os::raw::c_uint = 0x00000259; diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index d18785fd90..8b21002d1d 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -70,7 +70,8 @@ pub use x86_64::{CpuId, ExtendedControlRegisters, MsrEntries, VcpuKvmState}; use crate::ClockData; #[cfg(target_arch = "x86_64")] use crate::arch::x86::{ - CpuIdEntry, FpuState, LapicState, MsrEntry, NUM_IOAPIC_PINS, SpecialRegisters, XsaveState, + CpuIdEntry, FpuState, LapicState, MTRR_MSR_INDICES, MsrEntry, NUM_IOAPIC_PINS, + SpecialRegisters, XsaveState, }; use crate::{CpuState, IoEventAddress, IrqRoutingEntry, MpState, StandardRegisters}; // aarch64 dependencies @@ -1128,9 +1129,21 @@ impl KvmHypervisor { /// Retrieve the list of MSRs supported by the hypervisor. /// fn get_msr_list(&self) -> hypervisor::Result { - self.kvm + let mut indices = self + .kvm .get_msr_index_list() - .map_err(|e| hypervisor::HypervisorError::GetMsrList(e.into())) + .map_err(|e| hypervisor::HypervisorError::GetMsrList(e.into()))? + .as_slice() + .to_vec(); + + // KVM_GET_MSR_INDEX_LIST does not include MTRR MSRs, but firmware may update them before an early boot snapshot. + indices.extend(MTRR_MSR_INDICES); + + let mut msr_list = MsrList::new(indices.len()) + .map_err(|e| hypervisor::HypervisorError::GetMsrList(e.into()))?; + msr_list.as_mut_slice().copy_from_slice(&indices); + + Ok(msr_list) } } From f56c8392ea48b43620be238c6650f3ed15184cb0 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 2 Apr 2026 20:52:21 +0000 Subject: [PATCH 1079/1893] virtio-devices: vsock: RST vsocks on snapshot restore Otherwise guest connections just hang. Signed-off-by: Peter Oskolkov --- virtio-devices/src/vsock/device.rs | 8 +++++++- virtio-devices/src/vsock/mod.rs | 7 ++++++- virtio-devices/src/vsock/unix/muxer.rs | 18 +++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 6d38ecf398..25412503af 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -323,6 +323,8 @@ pub struct Vsock { pub struct VsockState { pub avail_features: u64, pub acked_features: u64, + #[serde(default)] + pub connections: Vec<(u32, u32)>, } impl Vsock @@ -336,7 +338,7 @@ where id: String, cid: u32, path: PathBuf, - backend: B, + mut backend: B, iommu: bool, seccomp_action: SeccompAction, exit_evt: EventFd, @@ -344,6 +346,9 @@ where ) -> io::Result> { let (avail_features, acked_features, paused) = if let Some(state) = state { info!("Restoring virtio-vsock {id}"); + // Instead of letting the guest connection hang/timeout, proactively let + // the guest know the connection is gone. + backend.queue_rst_for_connections(state.connections.clone()); (state.avail_features, state.acked_features, true) } else { let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_F_IN_ORDER); @@ -378,6 +383,7 @@ where VsockState { avail_features: self.common.avail_features, acked_features: self.common.acked_features, + connections: self.backend.read().unwrap().connections(), } } diff --git a/virtio-devices/src/vsock/mod.rs b/virtio-devices/src/vsock/mod.rs index 34561f5d46..cc1ef1ad23 100644 --- a/virtio-devices/src/vsock/mod.rs +++ b/virtio-devices/src/vsock/mod.rs @@ -158,7 +158,12 @@ pub trait VsockChannel { /// It that needs to be sendable through a mpsc channel (the latter due to how `vmm::EpollContext` works). /// Currently, the only implementation we have is `crate::virtio::unix::muxer::VsockMuxer`, which /// translates guest-side vsock connections to host-side Unix domain socket connections. -pub trait VsockBackend: VsockChannel + VsockEpollListener + Send {} +pub trait VsockBackend: VsockChannel + VsockEpollListener + Send { + fn connections(&self) -> Vec<(u32, u32)> { + Vec::new() + } + fn queue_rst_for_connections(&mut self, _conns: Vec<(u32, u32)>) {} +} #[cfg(any(test, fuzzing))] pub mod unit_tests { diff --git a/virtio-devices/src/vsock/unix/muxer.rs b/virtio-devices/src/vsock/unix/muxer.rs index 1a8570b75d..499c68eac1 100644 --- a/virtio-devices/src/vsock/unix/muxer.rs +++ b/virtio-devices/src/vsock/unix/muxer.rs @@ -345,7 +345,23 @@ impl VsockEpollListener for VsockMuxer { } } -impl VsockBackend for VsockMuxer {} +impl VsockBackend for VsockMuxer { + fn connections(&self) -> Vec<(u32, u32)> { + self.conn_map + .keys() + .map(|k| (k.local_port, k.peer_port)) + .collect() + } + + fn queue_rst_for_connections(&mut self, conns: Vec<(u32, u32)>) { + for (local_port, peer_port) in conns { + self.rxq.push(MuxerRx::RstPkt { + local_port, + peer_port, + }); + } + } +} impl VsockMuxer { /// Muxer constructor. From aef0a43b52c2c6a688e2235fc18cddfc604d51eb Mon Sep 17 00:00:00 2001 From: Max Makarov Date: Sat, 4 Apr 2026 20:32:49 +0000 Subject: [PATCH 1080/1893] vdpa: fix RX failure after device reset by always using base 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- virtio-devices/src/vdpa.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 4773ef55fa..7cd3415181 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -217,7 +217,7 @@ impl Vdpa { fn activate_vdpa( &mut self, - mem: &GuestMemoryMmap, + _mem: &GuestMemoryMmap, virtio_interrupt: &dyn VirtioInterrupt, queues: &[(usize, Queue, EventFd)], ) -> Result<()> { @@ -269,13 +269,7 @@ impl Vdpa { self.vhost .as_ref() .unwrap() - .set_vring_base( - *queue_index, - queue - .avail_idx(mem, Ordering::Acquire) - .map_err(Error::GetAvailableIndex)? - .0, - ) + .set_vring_base(*queue_index, 0) .map_err(Error::SetVringBase)?; if let Some(eventfd) = From 77ce3f6cbfa08a2a695de1760575f35670418347 Mon Sep 17 00:00:00 2001 From: Jared White Date: Sun, 5 Apr 2026 21:36:07 -0700 Subject: [PATCH 1081/1893] 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 --- virtio-devices/src/mem.rs | 4 ++++ vmm/src/config.rs | 31 +++++++++++++++++-------------- vmm/src/lib.rs | 4 +++- vmm/src/memory_manager.rs | 13 +++++++++++++ vmm/src/vm.rs | 8 ++++++++ 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index aed8ed48d2..067100164e 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -834,6 +834,10 @@ impl Mem { }) } + pub fn plugged_size(&self) -> u64 { + self.config.lock().unwrap().plugged_size + } + pub fn resize(&mut self, size: u64) -> result::Result<(), Error> { let mut config = self.config.lock().unwrap(); config.resize(size).map_err(|e| { diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 93ec6c8915..fc7eb1b8d1 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1039,21 +1039,24 @@ impl MemoryConfig { } pub fn total_size(&self) -> u64 { - let mut size = self.size; - if let Some(hotplugged_size) = self.hotplugged_size { - size += hotplugged_size; - } - - if let Some(zones) = &self.zones { - for zone in zones.iter() { - size += zone.size; - if let Some(hotplugged_size) = zone.hotplugged_size { - size += hotplugged_size; - } - } - } + self.size + + self + .zones + .iter() + .flatten() + .map(|zone| zone.size) + .sum::() + + self.hotplugged_size() + } - size + pub fn hotplugged_size(&self) -> u64 { + self.hotplugged_size.unwrap_or(0) + + self + .zones + .iter() + .flatten() + .filter_map(|zone| zone.hotplugged_size) + .sum::() } } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index ce5664249b..92bf4c6b70 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1915,9 +1915,11 @@ impl RequestHandler for Vmm { }; let config = vm_config.lock().unwrap().clone(); - let mut memory_actual_size = config.memory.total_size(); + let mut memory_actual_size = + config.memory.total_size() - config.memory.hotplugged_size(); if let Some(vm) = &self.vm { memory_actual_size = memory_actual_size.saturating_sub(vm.balloon_size()); + memory_actual_size += vm.virtio_mem_plugged_size(); } let device_tree = self diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 62b4522cc2..b2058435b4 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -2420,6 +2420,19 @@ impl MemoryManager { unsafe { (*stat.as_ptr()).st_nlink as usize > 0 } } + pub fn virtio_mem_plugged_size(&self) -> u64 { + self.memory_zones + .values() + .filter_map(|zone| { + zone.virtio_mem_zone + .as_ref()? + .virtio_device + .as_ref() + .map(|dev| dev.lock().unwrap().plugged_size()) + }) + .sum() + } + pub fn memory_zones(&self) -> &MemoryZones { &self.memory_zones } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index c0eaba7ed6..1f3bcfeebd 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2822,6 +2822,14 @@ impl Vm { self.device_manager.lock().unwrap().balloon_size() } + /// Get the actual size of the virtio_mem regions + pub fn virtio_mem_plugged_size(&self) -> u64 { + self.memory_manager + .lock() + .unwrap() + .virtio_mem_plugged_size() + } + pub fn send_memory_fds( &mut self, socket: &mut UnixStream, From fa949678d1c82362e23da3d8959cfa0ab26e6f64 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Mon, 6 Apr 2026 14:21:58 +0000 Subject: [PATCH 1082/1893] 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 --- scripts/run_integration_tests_aarch64.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index b4c3482d0e..9dac761e98 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -285,7 +285,7 @@ fi # Run tests on dbus_api if [ $RES -eq 0 ]; then - cargo build --features "dbus_api" --all --release --target "$BUILD_TARGET" + cargo build --features "mshv,dbus_api" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 # integration tests now do not reply on build feature "dbus_api" time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} @@ -294,14 +294,14 @@ fi # Run tests on fw_cfg if [ $RES -eq 0 ]; then - cargo build --features "fw_cfg" --all --release --target "$BUILD_TARGET" + cargo build --features "mshv,fw_cfg" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} RES=$? fi if [ $RES -eq 0 ]; then - cargo build --features "ivshmem" --all --release --target "$BUILD_TARGET" + cargo build --features "mshv,ivshmem" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} From 84c5e48bdb0dfc5028ac34a2ec80e2076c1088ea Mon Sep 17 00:00:00 2001 From: Thomas Prescher Date: Thu, 9 Apr 2026 14:40:18 +0200 Subject: [PATCH 1083/1893] 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 --- scripts/sha1sums-x86_64 | 2 +- scripts/test-util.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/sha1sums-x86_64 b/scripts/sha1sums-x86_64 index c49f00b266..1f7500e8b4 100644 --- a/scripts/sha1sums-x86_64 +++ b/scripts/sha1sums-x86_64 @@ -1,3 +1,3 @@ d4a44acc6014d5f83dea1c625c43d677a95fa75f alpine-minirootfs-x86_64.tar.gz 540ac358429305d7aa94e15363665d1c9d845982 hypervisor-fw -4e96fd0914a44005d40707b2b0c7e829e4086bd5 CLOUDHV.fd +cf89e3e052c8ef0b6192abee6128eef943393307 CLOUDHV.fd diff --git a/scripts/test-util.sh b/scripts/test-util.sh index 5b414f8583..2ad10d12fb 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -199,7 +199,7 @@ prepare_linux() { } download_ovmf() { - OVMF_FW_TAG="ch-a54f262b09" + OVMF_FW_TAG="ch-13b4963ec4" OVMF_FW_URL="https://github.com/cloud-hypervisor/edk2/releases/download/$OVMF_FW_TAG/CLOUDHV.fd" OVMF_FW="$WORKLOADS_DIR/CLOUDHV.fd" pushd "$WORKLOADS_DIR" || exit From 474106a067627b04b2d379856e113c6d97ba3802 Mon Sep 17 00:00:00 2001 From: JP Kobryn Date: Wed, 8 Apr 2026 17:36:00 -0700 Subject: [PATCH 1084/1893] 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 --- vmm/src/memory_manager.rs | 4 ++-- vmm/src/vm.rs | 14 +++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index b2058435b4..3a57ea60a4 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -1372,9 +1372,9 @@ impl MemoryManager { Ok(()) } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub fn add_uefi_flash(&mut self) -> Result<(), Error> { - // On AArch64, the UEFI binary requires a flash device at address 0. + // The UEFI binary requires a flash device at address 0. // 4 MiB memory is mapped to simulate the flash. let uefi_mem_slot = self.allocate_memory_slot(); let uefi_region = GuestRegionMmap::new( diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 1f3bcfeebd..04bd2d595a 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -899,14 +899,6 @@ impl Vm { .allocate_address_space() .map_err(Error::MemoryManager)?; - // Add UEFI flash for aarch64 - #[cfg(target_arch = "aarch64")] - memory_manager - .lock() - .unwrap() - .add_uefi_flash() - .map_err(Error::MemoryManager)?; - // Load payload asynchronously let load_payload_handle = if snapshot.is_none() { Self::load_payload_async( @@ -1405,7 +1397,11 @@ impl Vm { mut firmware: &File, memory_manager: Arc>, ) -> Result { - let uefi_flash = memory_manager.lock().as_ref().unwrap().uefi_flash(); + let mut memory_manager = memory_manager.lock().unwrap(); + memory_manager + .add_uefi_flash() + .map_err(Error::MemoryManager)?; + let uefi_flash = memory_manager.uefi_flash(); let mem = uefi_flash.memory(); arch::uefi::load_uefi(mem.deref(), arch::layout::UEFI_START, &mut firmware) .map_err(Error::UefiLoad)?; From de601c5eaa7651762ddba4b32772768eb23a2800 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 00:04:48 +0000 Subject: [PATCH 1085/1893] 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] --- Cargo.lock | 46 +++++++++++++++++++++---------------------- Cargo.toml | 4 ++-- fuzz/Cargo.lock | 42 +++++++++++++++++++-------------------- fuzz/Cargo.toml | 2 +- hypervisor/Cargo.toml | 2 +- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f60d47d317..bc927af8c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] @@ -370,9 +370,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.58" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -970,9 +970,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -1076,12 +1076,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -1219,9 +1219,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libredox" @@ -1248,9 +1248,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.25" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", @@ -1909,9 +1909,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -2001,9 +2001,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" dependencies = [ "libc", "signal-hook-registry", @@ -2147,18 +2147,18 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", "toml_datetime", @@ -2168,9 +2168,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow 1.0.0", ] diff --git a/Cargo.toml b/Cargo.toml index dab7e6c727..6ce471501d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,9 +91,9 @@ env_logger = "0.11.10" epoll = "4.4.0" flume = "0.12.0" itertools = "0.14.0" -libc = "0.2.183" +libc = "0.2.184" log = "0.4.29" -signal-hook = "0.4.3" +signal-hook = "0.4.4" thiserror = "2.0.18" uuid = { version = "1.23.0" } wait-timeout = "0.2.1" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index dd07d241c8..7906fd5cd4 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -81,9 +81,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] @@ -173,9 +173,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.58" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -559,9 +559,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -619,12 +619,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -718,9 +718,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libfuzzer-sys" @@ -1033,9 +1033,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -1114,9 +1114,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" dependencies = [ "libc", "signal-hook-registry", @@ -1192,18 +1192,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", "toml_datetime", @@ -1213,9 +1213,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 410a4968d7..83dead0c9e 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.4.0" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.183" +libc = "0.2.184" libfuzzer-sys = "0.4.12" linux-loader = { version = "0.13.2", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 1ffaa46b78..19a9ca794d 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -15,7 +15,7 @@ tdx = [] [dependencies] anyhow = { workspace = true } -arc-swap = "1.9.0" +arc-swap = "1.9.1" bitfield-struct = "0.12.0" byteorder = { workspace = true } cfg-if = { workspace = true } From 7f377eadd92b8c935c2354f2521c0ca3385e31de Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Tue, 19 Aug 2025 21:35:22 +0000 Subject: [PATCH 1086/1893] 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 --- cloud-hypervisor/src/main.rs | 6 +++--- vmm/src/config.rs | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 9966711820..415e7ed922 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -33,8 +33,8 @@ use vmm::vm_config::FwCfgConfig; use vmm::vm_config::IvshmemConfig; use vmm::vm_config::{ BalloonConfig, DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, LandlockConfig, - NetConfig, NumaConfig, PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, TpmConfig, - UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, + NetConfig, NumaConfig, PciSegmentConfig, PlatformConfig, PmemConfig, RateLimiterGroupConfig, + TpmConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::signal::block_signal; @@ -388,7 +388,7 @@ fn get_cli_options_sorted( Arg::new("platform") .long("platform") .help( - "num_pci_segments=,iommu_segments=,iommu_address_width=,serial_number=,uuid=,oem_strings=" + PlatformConfig::syntax() ) .num_args(1) .group("vm-config"), diff --git a/vmm/src/config.rs b/vmm/src/config.rs index fc7eb1b8d1..e37809745c 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -9,6 +9,7 @@ use std::fs; use std::path::PathBuf; use std::result; use std::str::FromStr; +use std::sync::LazyLock; use block::ImageType; use clap::ArgMatches; @@ -798,6 +799,30 @@ impl PciSegmentConfig { } impl PlatformConfig { + pub fn syntax() -> &'static str { + static SYNTAX: LazyLock = LazyLock::new(|| { + let mut syntax = "Platform configuration parameters \ + \"num_pci_segments=,iommu_segments=,\ + iommu_address_width=,serial_number=,\ + uuid=,oem_strings=" + .to_string(); + + if cfg!(feature = "tdx") { + syntax.push_str(",tdx=on|off"); + } + + if cfg!(feature = "sev_snp") { + syntax.push_str(",sev_snp=on|off"); + } + + syntax.push('"'); + + syntax + }); + + &SYNTAX + } + pub fn parse(platform: &str) -> Result { let mut parser = OptionParser::new(); parser From 13972a0edf56abc1bf92f6ab2b76d7488264acf4 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Tue, 19 Aug 2025 22:01:03 +0000 Subject: [PATCH 1087/1893] 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 --- vmm/src/api/openapi/cloud-hypervisor.yaml | 3 +++ vmm/src/config.rs | 12 ++++++++++-- vmm/src/vm_config.rs | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 01106a257b..7055c9788b 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -794,6 +794,9 @@ components: sev_snp: type: boolean default: false + iommufd: + type: boolean + default: false MemoryZoneConfig: required: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index e37809745c..b04ba2749c 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -804,7 +804,7 @@ impl PlatformConfig { let mut syntax = "Platform configuration parameters \ \"num_pci_segments=,iommu_segments=,\ iommu_address_width=,serial_number=,\ - uuid=,oem_strings=" + uuid=,oem_strings=,iommufd=on|off" .to_string(); if cfg!(feature = "tdx") { @@ -831,7 +831,8 @@ impl PlatformConfig { .add("iommu_address_width") .add("serial_number") .add("uuid") - .add("oem_strings"); + .add("oem_strings") + .add("iommufd"); #[cfg(feature = "tdx")] parser.add("tdx"); #[cfg(feature = "sev_snp")] @@ -858,6 +859,11 @@ impl PlatformConfig { .convert::("oem_strings") .map_err(Error::ParsePlatform)? .map(|v| v.0); + let iommufd = parser + .convert::("iommufd") + .map_err(Error::ParsePlatform)? + .unwrap_or(Toggle(false)) + .0; #[cfg(feature = "tdx")] let tdx = parser .convert::("tdx") @@ -877,6 +883,7 @@ impl PlatformConfig { serial_number, uuid, oem_strings, + iommufd, #[cfg(feature = "tdx")] tdx, #[cfg(feature = "sev_snp")] @@ -4824,6 +4831,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" serial_number: None, uuid: None, oem_strings: None, + iommufd: false, #[cfg(feature = "tdx")] tdx: false, #[cfg(feature = "sev_snp")] diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 88f8af4acf..cca72dde4b 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -133,6 +133,8 @@ pub struct PlatformConfig { #[cfg(feature = "sev_snp")] #[serde(default)] pub sev_snp: bool, + #[serde(default)] + pub iommufd: bool, } pub const DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT: u32 = 1; From fe5f991c3765bc5e3abeee5b8eac8b2b5f3441a2 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 9 Apr 2026 04:50:54 +0000 Subject: [PATCH 1088/1893] 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 --- Cargo.lock | 20 +++++++++++++++++++ Cargo.toml | 1 + vmm/Cargo.toml | 3 +++ vmm/src/device_manager.rs | 41 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc927af8c4..7c03c08766 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1097,6 +1097,23 @@ dependencies = [ "libc", ] +[[package]] +name = "iommufd-bindings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7de3a04f6fd55f171a6682852f7aa360bb848a85e0c610513349e006b3c139" + +[[package]] +name = "iommufd-ioctls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eabd3414d9c4e716c9a198fbfac484625f088c075605372daf037edfe336e18" +dependencies = [ + "iommufd-bindings", + "thiserror", + "vmm-sys-util", +] + [[package]] name = "ipnetwork" version = "0.20.0" @@ -2291,6 +2308,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4b1d98dff7f0d219278e406323e7eda4d426447bd203c7828189baf0d8c07b7" dependencies = [ "byteorder", + "iommufd-bindings", + "iommufd-ioctls", "kvm-bindings", "kvm-ioctls", "libc", @@ -2521,6 +2540,7 @@ dependencies = [ "hypervisor", "igvm", "igvm_defs", + "iommufd-ioctls", "landlock", "libc", "linux-loader", diff --git a/Cargo.toml b/Cargo.toml index 6ce471501d..92a52f81a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ resolver = "3" [workspace.dependencies] # rust-vmm crates acpi_tables = "0.2.0" +iommufd-ioctls = "0.1.0" kvm-bindings = "0.14.0" kvm-ioctls = "0.24.0" linux-loader = "0.13.2" diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index ab0278e6d1..1fe5e0e47b 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -17,8 +17,10 @@ ivshmem = ["devices/ivshmem"] kvm = [ "arch/kvm", "hypervisor/kvm", + "iommufd-ioctls", "pci/kvm", "vfio-ioctls/kvm", + "vfio-ioctls/vfio_cdev", "virtio-devices/kvm", "vm-device/kvm", ] @@ -55,6 +57,7 @@ hex = { version = "0.4.3", optional = true } hypervisor = { path = "../hypervisor" } igvm = { workspace = true, optional = true } igvm_defs = { workspace = true, optional = true } +iommufd-ioctls = { workspace = true, optional = true } landlock = "0.4.4" libc = { workspace = true } linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index edb8b76af0..a797a7bc84 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -76,6 +76,8 @@ use event_monitor::event; use hypervisor::IoEventAddress; #[cfg(target_arch = "aarch64")] use hypervisor::arch::aarch64::regs::AARCH64_PMU_IRQ; +#[cfg(feature = "kvm")] +use iommufd_ioctls::IommuFd; use libc::{ MAP_NORESERVE, MAP_PRIVATE, MAP_SHARED, O_TMPFILE, PROT_READ, PROT_WRITE, TCSANOW, tcsetattr, termios, @@ -90,6 +92,8 @@ use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracer::trace_scoped; +#[cfg(feature = "kvm")] +use vfio_ioctls::VfioIommufd; use vfio_ioctls::{VfioContainer, VfioDevice, VfioDeviceFd, VfioOps}; use virtio_devices::transport::{VirtioPciDevice, VirtioPciDeviceActivator, VirtioTransport}; use virtio_devices::vhost_user::VhostUserConfig; @@ -361,6 +365,15 @@ pub enum DeviceManagerError { #[error("Error getting pty peer")] GetPtyPeer(#[source] vmm_sys_util::errno::Error), + /// Cannot create iommufd + #[cfg(feature = "kvm")] + #[error("Cannot create iommufd")] + IommufdCreate(#[source] iommufd_ioctls::IommufdError), + + /// iommufd is not supported + #[error("iommufd is not supported without the kvm feature")] + IommufdNotSupported, + /// Cannot create a VFIO device #[error("Cannot create a VFIO device")] VfioCreate(#[source] vfio_ioctls::VfioError), @@ -3803,9 +3816,31 @@ impl DeviceManager { .try_clone() .map_err(DeviceManagerError::VfioCreate)?; - Ok(Arc::new( - VfioContainer::new(Some(Arc::new(dup))).map_err(DeviceManagerError::VfioCreate)?, - )) + let iommufd = self + .config + .lock() + .unwrap() + .platform + .as_ref() + .is_some_and(|p| p.iommufd); + + if iommufd { + #[cfg(feature = "kvm")] + { + info!("Using vfio cdev mode with iommufd."); + let iommufd = IommuFd::new().map_err(DeviceManagerError::IommufdCreate)?; + let vfio_iommufd = VfioIommufd::new(Arc::new(iommufd), None, Some(Arc::new(dup))) + .map_err(DeviceManagerError::VfioCreate)?; + Ok(Arc::new(vfio_iommufd)) + } + #[cfg(not(feature = "kvm"))] + Err(DeviceManagerError::IommufdNotSupported) + } else { + info!("Using vfio legacy mode with vfio container/group."); + Ok(Arc::new( + VfioContainer::new(Some(Arc::new(dup))).map_err(DeviceManagerError::VfioCreate)?, + )) + } } fn add_vfio_device( From 32c459c3dc140263d948adacbb11c48062721f86 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Fri, 5 Sep 2025 18:59:03 +0000 Subject: [PATCH 1089/1893] virtio-devices, vmm: Add seccomp rules for iommufd and vfio cdev Signed-off-by: Bo Chen --- virtio-devices/src/seccomp_filters.rs | 8 ++++ vmm/src/seccomp_filters.rs | 56 ++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs index f44fdc1b92..37c444999a 100644 --- a/virtio-devices/src/seccomp_filters.rs +++ b/virtio-devices/src/seccomp_filters.rs @@ -53,6 +53,10 @@ macro_rules! or { const VFIO_IOMMU_MAP_DMA: u64 = 0x3b71; const VFIO_IOMMU_UNMAP_DMA: u64 = 0x3b72; +// See include/uapi/linux/iommufd.h in the kernel code. +const IOMMU_IOAS_MAP: u64 = 0x3b85; +const IOMMU_IOAS_UNMAP: u64 = 0x3b86; + #[cfg(feature = "sev_snp")] fn mshv_sev_snp_ioctl_seccomp_rule() -> SeccompRule { and![ @@ -83,6 +87,8 @@ fn create_virtio_iommu_ioctl_seccomp_rule() -> Vec { or![ and![Cond::new(1, ArgLen::Dword, Eq, VFIO_IOMMU_MAP_DMA).unwrap()], and![Cond::new(1, ArgLen::Dword, Eq, VFIO_IOMMU_UNMAP_DMA).unwrap()], + and![Cond::new(1, ArgLen::Dword, Eq, IOMMU_IOAS_MAP).unwrap()], + and![Cond::new(1, ArgLen::Dword, Eq, IOMMU_IOAS_UNMAP).unwrap()], ] } @@ -90,6 +96,8 @@ fn create_virtio_mem_ioctl_seccomp_rule() -> Vec { or![ and![Cond::new(1, ArgLen::Dword, Eq, VFIO_IOMMU_MAP_DMA).unwrap()], and![Cond::new(1, ArgLen::Dword, Eq, VFIO_IOMMU_UNMAP_DMA).unwrap()], + and![Cond::new(1, ArgLen::Dword, Eq, IOMMU_IOAS_MAP).unwrap()], + and![Cond::new(1, ArgLen::Dword, Eq, IOMMU_IOAS_UNMAP).unwrap()], ] } diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 97f020e650..25da7f9c9a 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -110,6 +110,18 @@ mod kvm { pub const KVM_SET_NESTED_STATE: u64 = 1082175167; } +mod iommufd { + // See include/uapi/linux/iommufd.h in the kernel code. + pub const IOMMU_IOAS_ALLOC: u64 = 0x3b81; + pub const IOMMU_IOAS_MAP: u64 = 0x3b85; + pub const IOMMU_IOAS_UNMAP: u64 = 0x3b86; + + // See include/uapi/linux/vfio.h in the kernel code. + pub const VFIO_DEVICE_BIND_IOMMUFD: u64 = 0x3b76; + pub const VFIO_DEVICE_ATTACH_IOMMUFD_PT: u64 = 0x3b77; + pub const VFIO_DEVICE_DETACH_IOMMUFD_PT: u64 = 0x3b78; +} + // Block device ioctls (not exported by libc) const BLKDISCARD: u64 = 0x1277; // _IO(0x12, 119) const BLKZEROOUT: u64 = 0x127f; // _IO(0x12, 127) @@ -247,6 +259,28 @@ fn create_vmm_ioctl_seccomp_rule_common_kvm() -> Result, Backen ]) } +fn create_vmm_ioctl_seccomp_rule_iommufd() -> Result, BackendError> { + use iommufd::*; + Ok(or![ + and![Cond::new(1, ArgLen::Dword, Eq, IOMMU_IOAS_ALLOC)?], + and![Cond::new(1, ArgLen::Dword, Eq, IOMMU_IOAS_MAP)?], + and![Cond::new(1, ArgLen::Dword, Eq, IOMMU_IOAS_UNMAP)?], + and![Cond::new(1, ArgLen::Dword, Eq, VFIO_DEVICE_BIND_IOMMUFD)?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + VFIO_DEVICE_ATTACH_IOMMUFD_PT + )?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + VFIO_DEVICE_DETACH_IOMMUFD_PT + )?], + ]) +} + fn create_vmm_ioctl_seccomp_rule_hypervisor( hypervisor_type: HypervisorType, ) -> Result, BackendError> { @@ -373,9 +407,11 @@ fn create_vmm_ioctl_seccomp_rule_common( ]; let hypervisor_rules = create_vmm_ioctl_seccomp_rule_hypervisor(hypervisor_type)?; - common_rules.extend(hypervisor_rules); + let iommufd_rules = create_vmm_ioctl_seccomp_rule_iommufd()?; + common_rules.extend(iommufd_rules); + Ok(common_rules) } @@ -764,6 +800,20 @@ fn create_vcpu_ioctl_seccomp_rule_hypervisor( } } +fn create_vcpu_ioctl_seccomp_rule_iommufd() -> Result, BackendError> { + use iommufd::*; + Ok(or![ + and![Cond::new(1, ArgLen::Dword, Eq, IOMMU_IOAS_MAP)?], + and![Cond::new(1, ArgLen::Dword, Eq, IOMMU_IOAS_UNMAP)?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + VFIO_DEVICE_DETACH_IOMMUFD_PT + )?], + ]) +} + fn create_vcpu_ioctl_seccomp_rule( hypervisor_type: HypervisorType, ) -> Result, BackendError> { @@ -784,9 +834,11 @@ fn create_vcpu_ioctl_seccomp_rule( ]; let hypervisor_rules = create_vcpu_ioctl_seccomp_rule_hypervisor(hypervisor_type)?; - rules.extend(hypervisor_rules); + let iommufd_rules = create_vcpu_ioctl_seccomp_rule_iommufd()?; + rules.extend(iommufd_rules); + Ok(rules) } From 87992c77c112a0ecb72375623c451dc29470502e Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 9 Apr 2026 04:26:31 +0000 Subject: [PATCH 1090/1893] 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 --- pci/src/vfio.rs | 16 ++++++- vmm/src/api/openapi/cloud-hypervisor.yaml | 3 ++ vmm/src/config.rs | 58 ++++++++++++++++++++++- vmm/src/device_manager.rs | 9 ++++ vmm/src/vm_config.rs | 6 +++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 0fa3ae8365..7dc6f9c3b1 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -1471,6 +1471,9 @@ pub struct VfioPciDevice { vfio_ops: Arc, common: VfioCommon, iommu_attached: bool, + // Whether to map VFIO device MMIO BARs into the host IOMMU address space. + // Required for peer-to-peer DMA between VFIO devices. + p2p_dma: bool, memory_slot_allocator: MemorySlotAllocator, bdf: PciBdf, device_path: PathBuf, @@ -1487,6 +1490,7 @@ impl VfioPciDevice { msi_interrupt_manager: Arc>, legacy_interrupt_group: Option>, iommu_attached: bool, + p2p_dma: bool, bdf: PciBdf, memory_slot_allocator: MemorySlotAllocator, snapshot: Option<&Snapshot>, @@ -1515,6 +1519,7 @@ impl VfioPciDevice { vfio_ops, common, iommu_attached, + p2p_dma, memory_slot_allocator, bdf, device_path, @@ -1719,7 +1724,9 @@ impl VfioPciDevice { } .map_err(VfioPciError::CreateUserMemoryRegion)?; - if !self.iommu_attached { + // Map the MMIO BAR into the host IOMMU address space via VfioOps + // Only needed if p2p_dma is enabled. + if !self.iommu_attached && self.p2p_dma { // vfio_dma_map should be unsafe but isn't. #[allow(unused_unsafe)] // SAFETY: MmapRegion invariants guarantee that @@ -1749,7 +1756,9 @@ impl VfioPciDevice { let len = user_memory_region.mapping.len(); let host_addr = user_memory_region.mapping.addr(); // Unmap MMIO region from the host IOMMU address space via VfioOps + // Only needed if p2p_dma is enabled. if !self.iommu_attached + && self.p2p_dma && let Err(e) = self .vfio_ops .vfio_dma_unmap(user_memory_region.start, len) @@ -1907,7 +1916,9 @@ impl PciDevice for VfioPciDevice { let len = user_memory_region.mapping.len(); let host_addr = user_memory_region.mapping.addr(); // Unmap the old MMIO region from the host IOMMU address space via VfioOps + // Only needed if p2p_dma is enabled. if !self.iommu_attached + && self.p2p_dma && let Err(e) = self .vfio_ops .vfio_dma_unmap(user_memory_region.start, len) @@ -1961,7 +1972,8 @@ iova 0x{:x}, size 0x{:x}: {}, ", .map_err(io::Error::other)?; // Map the moved MMIO region into the host IOMMU address space via VfioOps - if !self.iommu_attached { + // Only needed if p2p_dma is enabled. + if !self.iommu_attached && self.p2p_dma { // vfio_dma_map is unsound and ought to be marked as unsafe #[allow(unused_unsafe)] // SAFETY: MmapRegion invariants guarantee that diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 7055c9788b..5be55560e6 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -797,6 +797,9 @@ components: iommufd: type: boolean default: false + vfio_p2p_dma: + type: boolean + default: true MemoryZoneConfig: required: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index b04ba2749c..405cb1da76 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -315,6 +315,9 @@ pub enum ValidationError { /// On a IOMMU segment but not behind IOMMU #[error("Device is on an IOMMU PCI segment ({0}) but not placed behind IOMMU")] OnIommuSegment(u16), + /// GPUDirect clique requires P2P DMA + #[error("Device with x_nv_gpudirect_clique requires vfio_p2p_dma=on")] + GpuDirectCliqueRequiresP2pDma, // On a IOMMU segment but IOMMU not supported #[error( "Device is on an IOMMU PCI segment ({0}) but does not support being placed behind IOMMU" @@ -804,7 +807,8 @@ impl PlatformConfig { let mut syntax = "Platform configuration parameters \ \"num_pci_segments=,iommu_segments=,\ iommu_address_width=,serial_number=,\ - uuid=,oem_strings=,iommufd=on|off" + uuid=,oem_strings=,iommufd=on|off,\ + vfio_p2p_dma=on|off" .to_string(); if cfg!(feature = "tdx") { @@ -832,7 +836,8 @@ impl PlatformConfig { .add("serial_number") .add("uuid") .add("oem_strings") - .add("iommufd"); + .add("iommufd") + .add("vfio_p2p_dma"); #[cfg(feature = "tdx")] parser.add("tdx"); #[cfg(feature = "sev_snp")] @@ -864,6 +869,11 @@ impl PlatformConfig { .map_err(Error::ParsePlatform)? .unwrap_or(Toggle(false)) .0; + let vfio_p2p_dma = parser + .convert::("vfio_p2p_dma") + .map_err(Error::ParsePlatform)? + .unwrap_or(Toggle(true)) + .0; #[cfg(feature = "tdx")] let tdx = parser .convert::("tdx") @@ -884,6 +894,7 @@ impl PlatformConfig { uuid, oem_strings, iommufd, + vfio_p2p_dma, #[cfg(feature = "tdx")] tdx, #[cfg(feature = "sev_snp")] @@ -2277,6 +2288,13 @@ impl DeviceConfig { } } + if self.x_nv_gpudirect_clique.is_some() { + let vfio_p2p_dma = vm_config.platform.as_ref().is_none_or(|p| p.vfio_p2p_dma); + if !vfio_p2p_dma { + return Err(ValidationError::GpuDirectCliqueRequiresP2pDma); + } + } + Ok(()) } } @@ -4832,6 +4850,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" uuid: None, oem_strings: None, iommufd: false, + vfio_p2p_dma: default_platformconfig_vfio_p2p_dma(), #[cfg(feature = "tdx")] tdx: false, #[cfg(feature = "sev_snp")] @@ -5572,6 +5591,41 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" config_with_invalid_host_data.validate().unwrap_err(); } + // x_nv_gpudirect_clique with vfio_p2p_dma=off should fail + let mut invalid_config = valid_config.clone(); + invalid_config.platform = Some(PlatformConfig { + vfio_p2p_dma: false, + ..platform_fixture() + }); + invalid_config.devices = Some(vec![DeviceConfig { + x_nv_gpudirect_clique: Some(0), + ..device_fixture() + }]); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::GpuDirectCliqueRequiresP2pDma) + ); + + // x_nv_gpudirect_clique with vfio_p2p_dma=on should pass + let mut still_valid_config = valid_config.clone(); + still_valid_config.platform = Some(PlatformConfig { + vfio_p2p_dma: true, + ..platform_fixture() + }); + still_valid_config.devices = Some(vec![DeviceConfig { + x_nv_gpudirect_clique: Some(0), + ..device_fixture() + }]); + still_valid_config.validate().unwrap(); + + // x_nv_gpudirect_clique with no platform config (default p2p_dma=on) should pass + let mut still_valid_config = valid_config.clone(); + still_valid_config.devices = Some(vec![DeviceConfig { + x_nv_gpudirect_clique: Some(0), + ..device_fixture() + }]); + still_valid_config.validate().unwrap(); + let mut still_valid_config = valid_config; // SAFETY: Safe as the file was just opened let fd1 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) }; diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index a797a7bc84..0ae8526448 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3963,6 +3963,14 @@ impl DeviceManager { let memory_manager = self.memory_manager.clone(); + let vfio_p2p_dma = self + .config + .lock() + .unwrap() + .platform + .as_ref() + .is_none_or(|p| p.vfio_p2p_dma); + let vfio_pci_device = VfioPciDevice::new( vfio_name.clone(), self.address_manager.vm.clone(), @@ -3971,6 +3979,7 @@ impl DeviceManager { self.msi_interrupt_manager.clone(), legacy_interrupt_group, device_cfg.iommu, + vfio_p2p_dma, pci_device_bdf, memory_manager.lock().unwrap().memory_slot_allocator(), vm_migration::snapshot_from_id(self.snapshot.as_ref(), vfio_name.as_str()), diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index cca72dde4b..94b1c011c8 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -113,6 +113,10 @@ pub fn default_platformconfig_iommu_address_width_bits() -> u8 { DEFAULT_IOMMU_ADDRESS_WIDTH_BITS } +pub fn default_platformconfig_vfio_p2p_dma() -> bool { + true +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct PlatformConfig { #[serde(default = "default_platformconfig_num_pci_segments")] @@ -135,6 +139,8 @@ pub struct PlatformConfig { pub sev_snp: bool, #[serde(default)] pub iommufd: bool, + #[serde(default = "default_platformconfig_vfio_p2p_dma")] + pub vfio_p2p_dma: bool, } pub const DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT: u32 = 1; From 9d47769bc25e6b9aaae1aa10d28a4d3a1d5b113b Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Tue, 7 Apr 2026 21:22:21 +0000 Subject: [PATCH 1091/1893] 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 --- cloud-hypervisor/tests/integration.rs | 82 +++++++++++++++++++++++---- scripts/run_integration_tests_vfio.sh | 7 +++ 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index fded5727ed..15516e84e9 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -8240,7 +8240,15 @@ mod vfio { use crate::*; const NVIDIA_VFIO_DEVICE: &str = "/sys/bus/pci/devices/0002:00:01.0"; - fn test_nvidia_card_memory_hotplug(hotplug_method: &str) { + fn platform_cfg(iommufd: bool) -> String { + if iommufd { + "iommufd=on,vfio_p2p_dma=off".to_string() + } else { + "iommufd=off".to_string() + } + } + + fn test_nvidia_card_memory_hotplug(hotplug_method: &str, iommufd: bool) { let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -8252,6 +8260,7 @@ mod vfio { format!("size=4G,hotplug_size=4G,hotplug_method={hotplug_method}").as_str(), ]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) + .args(["--platform", &platform_cfg(iommufd)]) .args(["--device", format!("path={NVIDIA_VFIO_DEVICE}").as_str()]) .args(["--api-socket", &api_socket]) .default_disks() @@ -8285,16 +8294,25 @@ mod vfio { #[test] fn test_nvidia_card_memory_hotplug_acpi() { - test_nvidia_card_memory_hotplug("acpi"); + test_nvidia_card_memory_hotplug("acpi", false); } #[test] fn test_nvidia_card_memory_hotplug_virtio_mem() { - test_nvidia_card_memory_hotplug("virtio-mem"); + test_nvidia_card_memory_hotplug("virtio-mem", false); } #[test] - fn test_nvidia_card_pci_hotplug() { + fn test_iommufd_nvidia_card_memory_hotplug_acpi() { + test_nvidia_card_memory_hotplug("acpi", true); + } + + #[test] + fn test_iommufd_nvidia_card_memory_hotplug_virtio_mem() { + test_nvidia_card_memory_hotplug("virtio-mem", true); + } + + fn test_nvidia_card_pci_hotplug_common(iommufd: bool) { let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -8303,6 +8321,7 @@ mod vfio { .args(["--cpus", "boot=4"]) .args(["--memory", "size=4G"]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) + .args(["--platform", &platform_cfg(iommufd)]) .args(["--api-socket", &api_socket]) .default_disks() .default_net() @@ -8338,7 +8357,16 @@ mod vfio { } #[test] - fn test_nvidia_card_reboot() { + fn test_nvidia_card_pci_hotplug() { + test_nvidia_card_pci_hotplug_common(false); + } + + #[test] + fn test_iommufd_nvidia_card_pci_hotplug() { + test_nvidia_card_pci_hotplug_common(true); + } + + fn test_nvidia_card_reboot_common(iommufd: bool) { let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); @@ -8346,6 +8374,7 @@ mod vfio { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) .args(["--memory", "size=4G"]) + .args(["--platform", &platform_cfg(iommufd)]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args([ "--device", @@ -8377,20 +8406,31 @@ mod vfio { } #[test] - fn test_nvidia_card_iommu_address_width() { + fn test_nvidia_card_reboot() { + test_nvidia_card_reboot_common(false); + } + + #[test] + fn test_iommufd_nvidia_card_reboot() { + test_nvidia_card_reboot_common(true); + } + + fn test_nvidia_card_iommu_address_width_common(iommufd: bool) { let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); + let platform = format!( + "num_pci_segments=2,iommu_segments=1,iommu_address_width=42,{}", + platform_cfg(iommufd) + ); + let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) .args(["--memory", "size=4G"]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args(["--device", format!("path={NVIDIA_VFIO_DEVICE}").as_str()]) - .args([ - "--platform", - "num_pci_segments=2,iommu_segments=1,iommu_address_width=42", - ]) + .args(["--platform", &platform]) .args(["--api-socket", &api_socket]) .default_disks() .default_net() @@ -8416,7 +8456,16 @@ mod vfio { } #[test] - fn test_nvidia_guest_numa_generic_initiator() { + fn test_nvidia_card_iommu_address_width() { + test_nvidia_card_iommu_address_width_common(false); + } + + #[test] + fn test_iommufd_nvidia_card_iommu_address_width() { + test_nvidia_card_iommu_address_width_common(true); + } + + fn test_nvidia_guest_numa_generic_initiator_common(iommufd: bool) { // Skip test if VFIO device is not available or not ready if !std::path::Path::new(NVIDIA_VFIO_DEVICE).exists() { println!("SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} not found"); @@ -8453,6 +8502,7 @@ mod vfio { "guest_numa_id=1,cpus=[2-3],distances=[0@20,2@30],memory_zones=mem1", "guest_numa_id=2,device_id=vfio0,distances=[0@25,1@30]", ]) + .args(["--platform", &platform_cfg(iommufd)]) .args([ "--device", &format!("id=vfio0,path={NVIDIA_VFIO_DEVICE},iommu=on"), @@ -8525,6 +8575,16 @@ mod vfio { handle_child_output(r, &output); } + + #[test] + fn test_nvidia_guest_numa_generic_initiator() { + test_nvidia_guest_numa_generic_initiator_common(false); + } + + #[test] + fn test_iommufd_nvidia_guest_numa_generic_initiator() { + test_nvidia_guest_numa_generic_initiator_common(true); + } } mod live_migration { diff --git a/scripts/run_integration_tests_vfio.sh b/scripts/run_integration_tests_vfio.sh index b32afe5a23..eecd3111b8 100755 --- a/scripts/run_integration_tests_vfio.sh +++ b/scripts/run_integration_tests_vfio.sh @@ -30,7 +30,14 @@ cargo build --features mshv --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 export RUSTFLAGS="$RUSTFLAGS" +# Run VFIO tests using legacy vfio interface with container/group time cargo nextest run --no-tests=pass --test-threads=1 "vfio::test_nvidia" -- ${test_binary_args[*]} RES=$? +# Run VFIO tests using vfio cdev interface backed by iommufd +if [ $RES -eq 0 ]; then + time cargo nextest run --no-tests=pass --test-threads=1 "vfio::test_iommufd" -- ${test_binary_args[*]} + RES=$? +fi + exit $RES From 23e139c0f86b3cf088d7cc797fced9805e22cabb Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 9 Apr 2026 13:00:24 -0400 Subject: [PATCH 1092/1893] ci: Double-quote variables in GitHub Actions This is best practice for shell scripts. Signed-off-by: Demi Marie Obenour --- .github/workflows/gitlint.yaml | 2 +- .github/workflows/integration-arm64.yaml | 2 +- .github/workflows/integration-vfio.yaml | 2 +- .github/workflows/mshv-infra.yaml | 44 ++++++++++++------------ .github/workflows/mshv-integration.yaml | 14 ++++---- 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/.github/workflows/gitlint.yaml b/.github/workflows/gitlint.yaml index 6fd0ec4ab9..178f15aa40 100644 --- a/.github/workflows/gitlint.yaml +++ b/.github/workflows/gitlint.yaml @@ -22,4 +22,4 @@ jobs: pip install --upgrade gitlint - name: Lint git commit messages run: | - gitlint --commits origin/$GITHUB_BASE_REF.. + gitlint --commits "origin/$GITHUB_BASE_REF.." diff --git a/.github/workflows/integration-arm64.yaml b/.github/workflows/integration-arm64.yaml index d0d6966482..fc545c2499 100644 --- a/.github/workflows/integration-arm64.yaml +++ b/.github/workflows/integration-arm64.yaml @@ -52,7 +52,7 @@ jobs: 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 + gzip -d "$IMG_GZ_PATH" - name: Run Windows guest integration tests if: ${{ github.event_name != 'pull_request' }} timeout-minutes: 30 diff --git a/.github/workflows/integration-vfio.yaml b/.github/workflows/integration-vfio.yaml index 19d5cadba6..b4f2ca2f94 100644 --- a/.github/workflows/integration-vfio.yaml +++ b/.github/workflows/integration-vfio.yaml @@ -13,7 +13,7 @@ jobs: steps: - name: Fix workspace permissions if: ${{ github.event_name != 'pull_request' }} - run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE} + run: sudo chown -R github-runner:github-runner "${GITHUB_WORKSPACE}" - name: Code checkout if: ${{ github.event_name != 'pull_request' }} uses: actions/checkout@v6 diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index ad71f69041..22f5ddbe6b 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -68,7 +68,7 @@ jobs: fi az --version echo "Logging into Azure CLI using Managed Identity" - az login --identity --client-id ${MI_CLIENT_ID} + az login --identity --client-id "${MI_CLIENT_ID}" - name: Get Location id: get-location @@ -118,7 +118,7 @@ jobs: echo "Creating Resource Group: $RG" # Create the resource group echo "Creating resource group in location: ${LOCATION}" - az group create --name ${RG} --location ${LOCATION} + az group create --name "${RG}" --location "${LOCATION}" echo "RG_NAME=${RG}" >> $GITHUB_OUTPUT echo "Resource group created successfully." @@ -130,7 +130,7 @@ jobs: set -e echo "Generating SSH key: $KEY" mkdir -p ~/.ssh - ssh-keygen -t rsa -b 4096 -f ~/.ssh/${KEY} -N "" + ssh-keygen -t rsa -b 4096 -f ~/.ssh/"${KEY}" -N "" - name: Create VM id: vm-setup @@ -150,7 +150,7 @@ jobs: # 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") + 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 @@ -158,7 +158,7 @@ jobs: # 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) + 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 @@ -166,24 +166,24 @@ jobs: # 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} \ + --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 \ + --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} + az vm boot-diagnostics enable --name "${VM_NAME}" --resource-group "${RG}" - echo "VM_NAME=${VM_NAME}" >> $GITHUB_OUTPUT + echo "VM_NAME=${VM_NAME}" >> "$GITHUB_OUTPUT" echo "VM creation process completed successfully." - name: Get VM Private IP @@ -195,12 +195,12 @@ jobs: 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) + 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 + echo "PRIVATE_IP=$PRIVATE_IP" >> "$GITHUB_OUTPUT" - name: Wait for SSH availability env: @@ -209,7 +209,7 @@ jobs: 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' + 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 @@ -218,7 +218,7 @@ jobs: run: | set -e echo "Removing the old host key" - ssh-keygen -R $PRIVATE_IP + ssh-keygen -R "$PRIVATE_IP" - name: SSH into VM and Install Dependencies env: @@ -227,7 +227,7 @@ jobs: USERNAME: ${{ secrets.USERNAME }} run: | set -e - ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF + ssh -i ~/.ssh/"${KEY}" -o StrictHostKeyChecking=no -- "${USERNAME}@${PRIVATE_IP}" << EOF set -e echo "Logged in successfully." echo "Installing dependencies..." @@ -243,6 +243,6 @@ jobs: sudo systemctl enable containerd.service sudo systemctl start docker sudo groupadd -f docker - sudo usermod -a -G docker ${USERNAME} + sudo usermod -a -G docker "${USERNAME}" sudo systemctl restart docker EOF diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index ad544249eb..1b83b5d292 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -38,7 +38,7 @@ jobs: run: | set -e echo "Connecting to the VM via SSH..." - ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF + ssh -i ~/.ssh/"${KEY}" -o StrictHostKeyChecking=no -- "${USERNAME}@${PRIVATE_IP}" << EOF set -e echo "Logged in successfully." export PATH="\$HOME/.cargo/bin:\$PATH" @@ -87,9 +87,7 @@ jobs: 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 + ssh -i ~/.ssh/"${KEY}" -o StrictHostKeyChecking=no -- "${USERNAME}@${PRIVATE_IP}" sudo dmesg - name: Dump serial console logs if: always() @@ -111,8 +109,8 @@ jobs: env: RG: MSHV-INTEGRATION-${{ github.run_id }} run: | - if az group exists --name ${RG}; then - az group delete --name ${RG} --yes --no-wait + 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 @@ -122,8 +120,8 @@ jobs: env: KEY: azure_key_${{ github.run_id }} run: | - if [ -f ~/.ssh/${KEY} ]; then - rm -f ~/.ssh/${KEY} ~/.ssh/${KEY}.pub + 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." From 5b67b8994a3686225c35186bc9ee4b7d0171356c Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 9 Apr 2026 13:17:16 -0400 Subject: [PATCH 1093/1893] ci: Use set -eufo pipefail Most scripts can use it and it is good at catching errors. Signed-off-by: Demi Marie Obenour --- .github/workflows/dco.yaml | 1 + .github/workflows/integration-arm64.yaml | 2 ++ .github/workflows/integration-windows.yaml | 3 +++ .github/workflows/integration-x86-64.yaml | 1 + .github/workflows/lychee.yaml | 1 + .github/workflows/mshv-infra.yaml | 18 +++++++++--------- .github/workflows/mshv-integration.yaml | 4 ++-- .github/workflows/package-consistency.yaml | 1 + .github/workflows/quality.yaml | 2 +- 9 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/dco.yaml b/.github/workflows/dco.yaml index 655c0b5e2f..67dfadd5c4 100644 --- a/.github/workflows/dco.yaml +++ b/.github/workflows/dco.yaml @@ -16,5 +16,6 @@ jobs: 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" diff --git a/.github/workflows/integration-arm64.yaml b/.github/workflows/integration-arm64.yaml index fc545c2499..873daaa747 100644 --- a/.github/workflows/integration-arm64.yaml +++ b/.github/workflows/integration-arm64.yaml @@ -31,6 +31,7 @@ jobs: - name: Install Azure CLI if: ${{ github.event_name != 'pull_request' }} 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 @@ -40,6 +41,7 @@ jobs: if: ${{ github.event_name != 'pull_request' }} 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 diff --git a/.github/workflows/integration-windows.yaml b/.github/workflows/integration-windows.yaml index 51877aa476..bb1f68158c 100644 --- a/.github/workflows/integration-windows.yaml +++ b/.github/workflows/integration-windows.yaml @@ -17,6 +17,7 @@ jobs: - name: Install Docker if: ${{ github.event_name != 'pull_request' }} 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 @@ -27,6 +28,7 @@ jobs: - name: Install Azure CLI if: ${{ github.event_name != 'pull_request' }} 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 @@ -35,6 +37,7 @@ jobs: - name: Download Windows image if: ${{ github.event_name != 'pull_request' }} run: | + set -eufo pipefail 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 diff --git a/.github/workflows/integration-x86-64.yaml b/.github/workflows/integration-x86-64.yaml index b620954d04..98e2a8dcdc 100644 --- a/.github/workflows/integration-x86-64.yaml +++ b/.github/workflows/integration-x86-64.yaml @@ -23,6 +23,7 @@ jobs: - name: Install Docker if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }} 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 diff --git a/.github/workflows/lychee.yaml b/.github/workflows/lychee.yaml index e77c595ed3..105e2e9a6e 100644 --- a/.github/workflows/lychee.yaml +++ b/.github/workflows/lychee.yaml @@ -20,6 +20,7 @@ jobs: # NEW STEP: Print all changed-files outputs for verification - 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 }}" diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index 22f5ddbe6b..e067e9ea80 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -59,7 +59,7 @@ jobs: env: MI_CLIENT_ID: ${{ secrets.MI_CLIENT_ID }} run: | - set -e + set -eufo pipefail echo "Installing Azure CLI if not already installed" if ! command -v az &>/dev/null; then curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash @@ -76,7 +76,7 @@ jobs: SKU: ${{ inputs.VM_SKU }} STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }} run: | - set -e + set -eufo pipefail # 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 @@ -114,7 +114,7 @@ jobs: RG: ${{ inputs.RG }} STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }} run: | - set -e + set -eufo pipefail echo "Creating Resource Group: $RG" # Create the resource group echo "Creating resource group in location: ${LOCATION}" @@ -127,7 +127,7 @@ jobs: env: KEY: ${{ inputs.KEY }} run: | - set -e + set -eufo pipefail echo "Generating SSH key: $KEY" mkdir -p ~/.ssh ssh-keygen -t rsa -b 4096 -f ~/.ssh/"${KEY}" -N "" @@ -145,7 +145,7 @@ jobs: 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 + set -eufo pipefail echo "Creating $VM_SKU VM: $VM_NAME" # Extract subnet ID from the runner VM @@ -192,7 +192,7 @@ jobs: RG: ${{ inputs.RG }} VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }} run: | - set -e + set -eufo pipefail 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) @@ -216,7 +216,7 @@ jobs: env: PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }} run: | - set -e + set -eufo pipefail echo "Removing the old host key" ssh-keygen -R "$PRIVATE_IP" @@ -226,9 +226,9 @@ jobs: PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }} USERNAME: ${{ secrets.USERNAME }} run: | - set -e + set -eufo pipefail ssh -i ~/.ssh/"${KEY}" -o StrictHostKeyChecking=no -- "${USERNAME}@${PRIVATE_IP}" << EOF - set -e + set -eufo pipefail echo "Logged in successfully." echo "Installing dependencies..." sudo tdnf install -y git moby-engine moby-cli clang llvm pkg-config make gcc glibc-devel diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index 1b83b5d292..d4ecea4ed1 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -36,7 +36,7 @@ jobs: RG: MSHV-${{ github.run_id }} USERNAME: ${{ secrets.MSHV_USERNAME }} run: | - set -e + set -eufo pipefail echo "Connecting to the VM via SSH..." ssh -i ~/.ssh/"${KEY}" -o StrictHostKeyChecking=no -- "${USERNAME}@${PRIVATE_IP}" << EOF set -e @@ -96,7 +96,7 @@ jobs: RG_NAME: ${{ needs.infra-setup.outputs.RG_NAME }} VM_NAME: ${{ needs.infra-setup.outputs.VM_NAME }} run: | - set -e + set -eufo pipefail az vm boot-diagnostics get-boot-log --name "${VM_NAME}" --resource-group "${RG_NAME}" | jq -r cleanup: diff --git a/.github/workflows/package-consistency.yaml b/.github/workflows/package-consistency.yaml index df7f01b8af..7f7808c882 100644 --- a/.github/workflows/package-consistency.yaml +++ b/.github/workflows/package-consistency.yaml @@ -27,6 +27,7 @@ jobs: - 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 diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index de6391186a..73b385811b 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -44,7 +44,7 @@ jobs: - name: Bisectability Check (default features) if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }} run: | - set -e + 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 }} From 295a76ea28fb15034def7f1a9a9bfd55147c0dc3 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 9 Apr 2026 13:18:09 -0400 Subject: [PATCH 1094/1893] 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 --- .github/workflows/mshv-infra.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index e067e9ea80..18e419f050 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -13,7 +13,7 @@ on: OS_DISK_SIZE: description: 'OS Disk Size in GB' required: true - type: string + type: number RG: description: 'Resource Group Name' required: true From dcdf16b8ff80e37da460b15107ccac94c2ad2099 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 9 Apr 2026 13:19:49 -0400 Subject: [PATCH 1095/1893] ci: Use bash regex instead of sed Easier to read and more reliable. Signed-off-by: Demi Marie Obenour --- .github/workflows/mshv-infra.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index 18e419f050..24ee2a59cd 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -78,11 +78,11 @@ jobs: run: | set -eufo pipefail # 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" + if ! [[ "$SKU" =~ ^Standard_[A-Za-z]+([1-9][0-9]*) ]]; then + printf 'Cannot extract vCPU count from SKU: %q\n' "$SKU" exit 1 fi + vcpu=${BASH_REMATCH[1]} SUPPORTED_LOCATIONS=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[] | .key') From b8a61da06af84bd441c963bdff019aa093ffb684 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 9 Apr 2026 13:39:55 -0400 Subject: [PATCH 1096/1893] ci: Use jq instead of bash arithmetic jq's arithmetic is much more robust. Signed-off-by: Demi Marie Obenour --- .github/workflows/mshv-infra.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index 24ee2a59cd..6e15c212e4 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -93,11 +93,9 @@ jobs: 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 + remaining=$(az vm list-usage --location "$location" --query "[?name.value=='$family'] | [0]" -o json | + jq '.limit + 0 - .currentValue >= $ARGS.positional[0]' --jsonargs "$vcpu") + if [[ "$remaining" = true ]]; then echo "Sufficient quota found in $location" echo "location=$location" >> "$GITHUB_OUTPUT" exit 0 From 3a23e2f84112a9bd275f662a75404372eae044dd Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Sat, 11 Apr 2026 06:13:18 +0000 Subject: [PATCH 1097/1893] ci: fix jq usage in mshv-infra.yaml Workflow runs fail in the "Get Location" step with: jq: error (at :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 --- .github/workflows/mshv-infra.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mshv-infra.yaml b/.github/workflows/mshv-infra.yaml index 6e15c212e4..89cb5f6fbc 100644 --- a/.github/workflows/mshv-infra.yaml +++ b/.github/workflows/mshv-infra.yaml @@ -94,7 +94,7 @@ jobs: fi remaining=$(az vm list-usage --location "$location" --query "[?name.value=='$family'] | [0]" -o json | - jq '.limit + 0 - .currentValue >= $ARGS.positional[0]' --jsonargs "$vcpu") + jq '(.limit | tonumber) - (.currentValue | tonumber) >= ($ARGS.positional[0] | tonumber)' --jsonargs "$vcpu") if [[ "$remaining" = true ]]; then echo "Sufficient quota found in $location" echo "location=$location" >> "$GITHUB_OUTPUT" From bdc7a6947d6d175b65d23f45712ce55bcf4e2052 Mon Sep 17 00:00:00 2001 From: JP Kobryn Date: Wed, 8 Apr 2026 18:52:01 -0700 Subject: [PATCH 1098/1893] 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 --- docs/memory.md | 31 +++++++++++++- vmm/src/config.rs | 90 ++++++++++++++++++++++++++++++++++++++- vmm/src/memory_manager.rs | 31 ++++++++++---- vmm/src/vm_config.rs | 4 +- 4 files changed, 145 insertions(+), 11 deletions(-) diff --git a/docs/memory.md b/docs/memory.md index 75179e0e07..fb42e89374 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -214,11 +214,12 @@ struct MemoryZoneConfig { hotplug_size: Option, hotplugged_size: Option, prefault: bool, + mergeable: bool, } ``` ``` ---memory-zone User defined memory zone parameters "size=,file=,shared=on|off,hugepages=on|off,hugepage_size=,host_numa_node=,id=,hotplug_size=,hotplugged_size=,prefault=on|off" +--memory-zone User defined memory zone parameters "size=,file=,shared=on|off,hugepages=on|off,hugepage_size=,host_numa_node=,id=,hotplug_size=,hotplugged_size=,prefault=on|off,mergeable=on|off" ``` This parameter expects one or more occurrences, allowing for a list of memory @@ -422,6 +423,34 @@ _Example_ --memory-zone id=mem0,size=1G,prefault=on ``` +### `mergeable` + +Specifies if the pages from this memory zone must be marked as _mergeable_, +enabling Kernel Same-page Merging (KSM) for this zone. + +This is the per-zone equivalent of the top-level `--memory mergeable=on` option. +It allows KSM to be enabled selectively — for example, enabling it only on a +hotplug zone while leaving boot memory unaffected: + +``` +--memory size=2G,mergeable=off +--memory-zone id=hotplug,size=0,hotplug_size=8G,mergeable=on +``` + +For KSM to have any effect, the host kernel must have KSM enabled: +``` +echo 1 > /sys/kernel/mm/ksm/run +``` + +By default this option is turned off. + +_Example_ + +``` +--memory size=0 +--memory-zone id=mem0,size=1G,mergeable=on +``` + ## NUMA settings `NumaConfig` or what is known as `--numa` from the CLI perspective has been diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 405cb1da76..b2ae29a84f 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1006,7 +1006,8 @@ impl MemoryConfig { .add("host_numa_node") .add("hotplug_size") .add("hotplugged_size") - .add("prefault"); + .add("prefault") + .add("mergeable"); parser.parse(memory_zone).map_err(Error::ParseMemoryZone)?; let id = parser.get("id").ok_or(Error::ParseMemoryZoneIdMissing)?; @@ -1047,6 +1048,11 @@ impl MemoryConfig { .map_err(Error::ParseMemoryZone)? .unwrap_or(Toggle(false)) .0; + let mergeable = parser + .convert::("mergeable") + .map_err(Error::ParseMemoryZone)? + .unwrap_or(Toggle(mergeable)) + .0; zones.push(MemoryZoneConfig { id, @@ -1059,6 +1065,7 @@ impl MemoryConfig { hotplug_size, hotplugged_size, prefault, + mergeable, }); } Some(zones) @@ -3750,6 +3757,87 @@ mod unit_tests { Ok(()) } + #[test] + fn test_mem_zone_parsing() -> Result<()> { + // mergeable defaults to false + assert_eq!( + MemoryConfig::parse("size=0", Some(vec!["id=mem0,size=1G"]))?, + MemoryConfig { + size: 0, + zones: Some(vec![MemoryZoneConfig { + id: "mem0".to_string(), + size: 1 << 30, + ..Default::default() + }]), + ..Default::default() + } + ); + // mergeable=on + assert_eq!( + MemoryConfig::parse("size=0", Some(vec!["id=mem0,size=1G,mergeable=on"]))?, + MemoryConfig { + size: 0, + zones: Some(vec![MemoryZoneConfig { + id: "mem0".to_string(), + size: 1 << 30, + mergeable: true, + ..Default::default() + }]), + ..Default::default() + } + ); + // mergeable=off is explicit false + assert_eq!( + MemoryConfig::parse("size=0", Some(vec!["id=mem0,size=1G,mergeable=off"]))?, + MemoryConfig { + size: 0, + zones: Some(vec![MemoryZoneConfig { + id: "mem0".to_string(), + size: 1 << 30, + mergeable: false, + ..Default::default() + }]), + ..Default::default() + } + ); + // per-zone mergeable independent of global mergeable + assert_eq!( + MemoryConfig::parse( + "size=1G,mergeable=off", + Some(vec!["id=hotplug,size=0,hotplug_size=4G,mergeable=on"]) + )?, + MemoryConfig { + size: 1 << 30, + mergeable: false, + hotplug_method: HotplugMethod::Acpi, + zones: Some(vec![MemoryZoneConfig { + id: "hotplug".to_string(), + size: 0, + hotplug_size: Some(4 << 30), + mergeable: true, + ..Default::default() + }]), + ..Default::default() + } + ); + // global mergeable=on inherited by zone with no explicit mergeable + assert_eq!( + MemoryConfig::parse("size=0,mergeable=on", Some(vec!["id=mem0,size=1G"]))?, + MemoryConfig { + size: 0, + mergeable: true, + zones: Some(vec![MemoryZoneConfig { + id: "mem0".to_string(), + size: 1 << 30, + mergeable: true, + ..Default::default() + }]), + ..Default::default() + } + ); + Ok(()) + } + #[test] fn test_mem_parsing() -> Result<()> { assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default()); diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 3a57ea60a4..a01949b0fc 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -138,16 +138,18 @@ pub struct MemoryZone { shared: bool, hugepages: bool, backing_page_size: u64, + mergeable: bool, } impl MemoryZone { - fn new(shared: bool, hugepages: bool, backing_page_size: u64) -> Self { + fn new(shared: bool, hugepages: bool, backing_page_size: u64, mergeable: bool) -> Self { Self { regions: Vec::new(), virtio_mem_zone: None, shared, hugepages, backing_page_size, + mergeable, } } @@ -607,7 +609,7 @@ impl MemoryManager { // Add zone id to the list of memory zones. memory_zones.insert( zone.id.clone(), - MemoryZone::new(zone.shared, zone.hugepages, zone_align_size), + MemoryZone::new(zone.shared, zone.hugepages, zone_align_size, zone.mergeable), ); for ram_region in ram_regions.iter() { @@ -701,7 +703,12 @@ impl MemoryManager { } memory_zones.insert( zone.id.clone(), - MemoryZone::new(zone.shared, zone.hugepages, zone_align_size), + MemoryZone::new( + zone.shared, + zone.hugepages, + zone_align_size, + zone.mergeable, + ), ); } @@ -733,7 +740,12 @@ impl MemoryManager { let zone_page_size = memory_zone_get_align_size(zone_config)?; memory_zones.insert( zone_config.id.clone(), - MemoryZone::new(zone_config.shared, zone_config.hugepages, zone_page_size), + MemoryZone::new( + zone_config.shared, + zone_config.hugepages, + zone_page_size, + zone_config.mergeable, + ), ); } @@ -1295,6 +1307,7 @@ impl MemoryManager { hotplug_size: config.hotplug_size, hotplugged_size: config.hotplugged_size, prefault: config.prefault, + mergeable: config.mergeable, }]; Ok((config.size, zones, allow_mem_hotplug)) @@ -1316,10 +1329,10 @@ impl MemoryManager { regions.push((virtio_mem_zone.region().clone(), true)); } - list.push((zone_id.clone(), regions)); + list.push((zone_id.clone(), regions, memory_zone.mergeable)); } - for (zone_id, regions) in list { + for (zone_id, regions, zone_mergeable) in list { for (region, virtio_mem) in regions { // SAFETY: guaranteed by GuestRegionMmap invariants let slot = unsafe { @@ -1327,7 +1340,7 @@ impl MemoryManager { region.start_addr().raw_value(), region.len().try_into().unwrap(), region.as_ptr(), - self.mergeable, + zone_mergeable, false, self.log_dirty, ) @@ -2087,7 +2100,9 @@ impl MemoryManager { region.start_addr().0, region.len().try_into().unwrap(), region.as_ptr(), - self.mergeable, + self.memory_zones + .get(DEFAULT_MEMORY_ZONE) + .map_or(self.mergeable, |z| z.mergeable), false, self.log_dirty, ) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 94b1c011c8..541f6f21b0 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -159,7 +159,7 @@ pub struct PciSegmentConfig { pub mmio64_aperture_weight: u32, } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] pub struct MemoryZoneConfig { pub id: String, pub size: u64, @@ -179,6 +179,8 @@ pub struct MemoryZoneConfig { pub hotplugged_size: Option, #[serde(default)] pub prefault: bool, + #[serde(default)] + pub mergeable: bool, } impl ApplyLandlock for MemoryZoneConfig { From 490e338e16453f15b8be247d80ef0b01c1ce2e37 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Fri, 10 Apr 2026 11:49:05 -0700 Subject: [PATCH 1099/1893] 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 --- pci/src/vfio.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 7dc6f9c3b1..51e3aa9271 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -157,11 +157,14 @@ impl VfioMsix { // Update "Message Control" word if offset == 2 && data.len() == 2 { - self.bar.set_msg_ctl(LittleEndian::read_u16(data)); + let data = LittleEndian::read_u16(data); + self.bar.set_msg_ctl(data); + self.cap.set_msg_ctl(data); } else if offset == 0 && data.len() == 4 { // Some guests update MSI-X control through the dword config write path. - self.bar - .set_msg_ctl((LittleEndian::read_u32(data) >> 16) as u16); + let data = (LittleEndian::read_u32(data) >> 16) as u16; + self.bar.set_msg_ctl(data); + self.cap.set_msg_ctl(data); } let new_enabled = self.bar.enabled(); From 2ddbc5abbbbae8b9c0e7b8a10a54c6be7060707f Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Tue, 7 Apr 2026 06:49:28 +0000 Subject: [PATCH 1100/1893] 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 --- scripts/run_integration_tests_aarch64.sh | 6 +++--- scripts/run_integration_tests_windows_aarch64.sh | 4 ++-- scripts/run_integration_tests_windows_x86_64.sh | 3 ++- scripts/run_integration_tests_x86_64.sh | 2 +- scripts/sha1sums-aarch64-common | 1 + scripts/sha1sums-x86_64 | 2 +- scripts/test-util.sh | 14 ++++++++++++-- 7 files changed, 22 insertions(+), 10 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 9dac761e98..5a1dce52b9 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -136,6 +136,9 @@ update_workloads() { popd || exit fi + # Download aarch64 ovmf + download_aarch64_ovmf + pushd "$WORKLOADS_DIR" || exit if ! sha1sum sha1sums-aarch64-common --check; then @@ -202,9 +205,6 @@ update_workloads() { echo "foo" >"$SHARED_DIR/file1" echo "bar" >"$SHARED_DIR/file3" || exit 1 fi - - # Checkout and build EDK2 - build_edk2 } process_common_args "$@" diff --git a/scripts/run_integration_tests_windows_aarch64.sh b/scripts/run_integration_tests_windows_aarch64.sh index 8f12a2740a..58523e2098 100755 --- a/scripts/run_integration_tests_windows_aarch64.sh +++ b/scripts/run_integration_tests_windows_aarch64.sh @@ -18,9 +18,9 @@ fi WIN_IMAGE_BASENAME="windows-11-iot-enterprise-aarch64.raw" WIN_IMAGE_FILE="$WORKLOADS_DIR/$WIN_IMAGE_BASENAME" -# Checkout and build EDK2 +# Download aarch64 OVMF OVMF_FW="$WORKLOADS_DIR/CLOUDHV_EFI.fd" -build_edk2 +download_aarch64_ovmf # Check if the images are present if [[ ! -f ${WIN_IMAGE_FILE} || ! -f ${OVMF_FW} ]]; then diff --git a/scripts/run_integration_tests_windows_x86_64.sh b/scripts/run_integration_tests_windows_x86_64.sh index d8f6861497..52b7796b83 100755 --- a/scripts/run_integration_tests_windows_x86_64.sh +++ b/scripts/run_integration_tests_windows_x86_64.sh @@ -17,7 +17,8 @@ WIN_IMAGE_FILE="/root/workloads/windows-server-2022-amd64-2.raw" WORKLOADS_DIR="/root/workloads" -download_ovmf +# Download amd64 ovmf +download_amd64_ovmf CFLAGS="" if [[ "${BUILD_TARGET}" == "x86_64-unknown-linux-musl" ]]; then diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 858bd2f872..f12fa9aaaa 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -25,7 +25,7 @@ if [ ! -f "$WORKLOADS_DIR/hypervisor-fw" ]; then fi if [ ! -f "$WORKLOADS_DIR/CLOUDHV.fd" ]; then - download_ovmf + download_amd64_ovmf fi download_x86_guest_images diff --git a/scripts/sha1sums-aarch64-common b/scripts/sha1sums-aarch64-common index 4585509712..d955f98bec 100644 --- a/scripts/sha1sums-aarch64-common +++ b/scripts/sha1sums-aarch64-common @@ -3,3 +3,4 @@ e4addb6e212a298144f9eb0eb6e36019d013f0e7 alpine-minirootfs-aarch64.tar.gz 9953b31bb1923cdd8d91b1b7cc9ad3a9be1e0a59 focal-server-cloudimg-arm64-custom-20210929-0.raw 7118f4d4cad18c8357bc2ad9824a50f9a82a860a jammy-server-cloudimg-arm64-custom-20220329-0.qcow2 1f2b71be43b8f748f01306c4454e5c921343faa4 jammy-server-cloudimg-arm64-custom-20220329-0.raw +ce3656987f9e4238ef8afbd65fca219460c1f767 CLOUDHV_EFI.fd diff --git a/scripts/sha1sums-x86_64 b/scripts/sha1sums-x86_64 index 1f7500e8b4..e719bcc316 100644 --- a/scripts/sha1sums-x86_64 +++ b/scripts/sha1sums-x86_64 @@ -1,3 +1,3 @@ d4a44acc6014d5f83dea1c625c43d677a95fa75f alpine-minirootfs-x86_64.tar.gz 540ac358429305d7aa94e15363665d1c9d845982 hypervisor-fw -cf89e3e052c8ef0b6192abee6128eef943393307 CLOUDHV.fd +fb2e6834cc482c80a45766f6dcf12474f4fcb74e CLOUDHV.fd diff --git a/scripts/test-util.sh b/scripts/test-util.sh index 2ad10d12fb..3ba2474a49 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -198,8 +198,8 @@ prepare_linux() { fi } -download_ovmf() { - OVMF_FW_TAG="ch-13b4963ec4" +download_amd64_ovmf() { + OVMF_FW_TAG="ch-1e1b96f126" OVMF_FW_URL="https://github.com/cloud-hypervisor/edk2/releases/download/$OVMF_FW_TAG/CLOUDHV.fd" OVMF_FW="$WORKLOADS_DIR/CLOUDHV.fd" pushd "$WORKLOADS_DIR" || exit @@ -208,6 +208,16 @@ download_ovmf() { popd || exit } +download_aarch64_ovmf() { + OVMF_FW_TAG="ch-1e1b96f126" + OVMF_FW_URL="https://github.com/cloud-hypervisor/edk2/releases/download/$OVMF_FW_TAG/CLOUDHV_EFI.fd" + OVMF_FW="$WORKLOADS_DIR/CLOUDHV_EFI.fd" + pushd "$WORKLOADS_DIR" || exit + rm -f "$OVMF_FW" + download_with_retries $OVMF_FW_URL || exit 1 + popd || exit +} + # Function to mount image partition, execute commands, and cleanup. # Arguments: $1: Image file path, $2: Mount directory, $3+: Commands to execute. mount_and_exec() { From 23a980cd54136a68d8ca56f1c5392eb92a686879 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Tue, 31 Mar 2026 06:17:43 +0000 Subject: [PATCH 1101/1893] 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 --- pci/src/vfio.rs | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index 51e3aa9271..b49ed51989 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -21,7 +21,8 @@ use thiserror::Error; use vfio_bindings::bindings::vfio::*; use vfio_ioctls::{VfioDevice, VfioIrq, VfioOps, VfioRegionInfoCap, VfioRegionSparseMmapArea}; use vm_allocator::page_size::{ - align_page_size_down, align_page_size_up, is_4k_aligned, is_4k_multiple, is_page_size_aligned, + align_page_size_down, align_page_size_up, get_page_size, is_4k_aligned, is_4k_multiple, + is_page_size_aligned, }; use vm_allocator::{AddressAllocator, MemorySlotAllocator, SystemAllocator}; use vm_device::dma_mapping::ExternalDmaMapping; @@ -1686,9 +1687,40 @@ impl VfioPciDevice { self.common.interrupt.msix.as_ref(), )?; + let page_size = get_page_size(); for area in sparse_areas.iter() { + // KVM_SET_USER_MEMORY_REGION requires memory_size to be a + // multiple of the host page size. On aarch64 with 64K pages + // a device BAR can be smaller than a page (e.g. 16K NVMe + // BAR). + // + // The kernel only sets VFIO_REGION_INFO_FLAG_MMAP on sub-page + // BARs after verifying the physical BAR start is page-aligned + // and reserving the rest of the page. Expansion is only safe + // at offset 0 where the kernel reservation applies. + // + // fixup_msix_region() ensures MSI-X relocation at >= page_size + // offset, so the expanded mmap cannot overlap the trap region. + let mmap_len = if area.size < page_size { + if area.offset != 0 { + error!( + "BAR {}: sub-page sparse area at non-zero offset 0x{:x} \ + cannot be safely expanded to page size", + region.index, area.offset, + ); + return Err(VfioPciError::MmapArea); + } + info!( + "BAR {}: expanding sub-page sparse area mmap from 0x{:x} to \ + page size 0x{:x}", + region.index, area.size, page_size, + ); + page_size + } else { + area.size + }; let mapping = match MmapRegion::mmap( - area.size, + mmap_len, prot, fd, mmap_offset, @@ -1699,7 +1731,7 @@ impl VfioPciDevice { error!( "Could not mmap sparse area (offset = 0x{:x}, size = 0x{:x}): {}", mmap_offset, - area.size, + mmap_len, std::io::Error::last_os_error() ); return Err(VfioPciError::MmapArea); From 8b212aafc085619381788bdcc71eb40109fdbef4 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sun, 12 Apr 2026 05:58:50 -0400 Subject: [PATCH 1102/1893] 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: 5b67b8994a36 ("ci: Use set -eufo pipefail") it becomes an error. Fixes: 5b67b8994a36 ("ci: Use set -eufo pipefail") Fixes: #7996 Signed-off-by: Demi Marie Obenour --- .github/workflows/mshv-integration.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/mshv-integration.yaml b/.github/workflows/mshv-integration.yaml index d4ecea4ed1..437cf44f6c 100644 --- a/.github/workflows/mshv-integration.yaml +++ b/.github/workflows/mshv-integration.yaml @@ -67,12 +67,12 @@ jobs: 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" + 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" + echo "Warning: Device \$dev not found" fi done From 6adc4f2c90d1afa3cc9a6204e291772eb383cde4 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Sat, 11 Apr 2026 04:42:44 +0000 Subject: [PATCH 1103/1893] tests: vfio: Add more checks on the Nvidia GPU from the guest Signed-off-by: Bo Chen --- cloud-hypervisor/tests/integration.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 15516e84e9..8b16c909e3 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -8274,6 +8274,9 @@ mod vfio { assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + // Verify the VFIO device works before memory hotplug + guest.check_nvidia_gpu(); + guest.enable_memory_hotplug(); // Add RAM to the VM @@ -8447,6 +8450,9 @@ mod vfio { .unwrap() .contains("input address: 42 bits") ); + + // Check the VFIO device works after boot + guest.check_nvidia_gpu(); }); let _ = child.kill(); From e8b6fe054859b3631ec030330a4999a3d9ef4d9c Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Sat, 11 Apr 2026 04:51:22 +0000 Subject: [PATCH 1104/1893] 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 --- test_infra/src/lib.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 248e4efa34..47d022cd6e 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1316,11 +1316,18 @@ impl Guest { #[cfg(target_arch = "x86_64")] pub fn check_nvidia_gpu(&self) { - assert!( - self.ssh_command("nvidia-smi") - .unwrap() - .contains("NVIDIA L40S") - ); + let output = self.ssh_command("nvidia-smi").unwrap(); + if !output.contains("NVIDIA L40S") { + let dmesg = self + .ssh_command("sudo dmesg") + .unwrap_or_else(|e| format!("Failed to get dmesg: {e:?}")); + eprintln!( + "\n\n==== Guest dmesg (nvidia-smi check failed) ====\n\n\ + {dmesg}\n\ + \n==== End guest dmesg ====\n\n" + ); + panic!("nvidia-smi output did not contain 'NVIDIA L40S': {output}"); + } } pub fn reboot_linux(&self, current_reboot_count: u32) { From e48b14dcacd128a2bf5301bdc9e568a66795d694 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Sat, 11 Apr 2026 04:58:26 +0000 Subject: [PATCH 1105/1893] scripts: Add retries for vfio integration tests Signed-off-by: Bo Chen --- scripts/run_integration_tests_vfio.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run_integration_tests_vfio.sh b/scripts/run_integration_tests_vfio.sh index eecd3111b8..3f1b267787 100755 --- a/scripts/run_integration_tests_vfio.sh +++ b/scripts/run_integration_tests_vfio.sh @@ -31,12 +31,12 @@ export RUST_BACKTRACE=1 export RUSTFLAGS="$RUSTFLAGS" # Run VFIO tests using legacy vfio interface with container/group -time cargo nextest run --no-tests=pass --test-threads=1 "vfio::test_nvidia" -- ${test_binary_args[*]} +time cargo nextest run --retries 3 --no-tests=pass --test-threads=1 "vfio::test_nvidia" -- ${test_binary_args[*]} RES=$? # Run VFIO tests using vfio cdev interface backed by iommufd if [ $RES -eq 0 ]; then - time cargo nextest run --no-tests=pass --test-threads=1 "vfio::test_iommufd" -- ${test_binary_args[*]} + time cargo nextest run --retries 3 --no-tests=pass --test-threads=1 "vfio::test_iommufd" -- ${test_binary_args[*]} RES=$? fi From a3899a9783683053ed841a0b0b8bdb4fc9ba888f Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Sat, 11 Apr 2026 05:20:12 +0000 Subject: [PATCH 1106/1893] tests: Add retries for windows integration tests Signed-off-by: Bo Chen --- scripts/run_integration_tests_windows_aarch64.sh | 2 +- scripts/run_integration_tests_windows_x86_64.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run_integration_tests_windows_aarch64.sh b/scripts/run_integration_tests_windows_aarch64.sh index 58523e2098..69537d7769 100755 --- a/scripts/run_integration_tests_windows_aarch64.sh +++ b/scripts/run_integration_tests_windows_aarch64.sh @@ -44,7 +44,7 @@ cargo build --all --release --target "$BUILD_TARGET" # Only run with 1 thread to avoid tests interfering with one another because # Windows has a static IP configured -time cargo nextest run --no-tests=pass "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} +time cargo nextest run --retries 3 --no-tests=pass "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} RES=$? dmsetup remove_all -f diff --git a/scripts/run_integration_tests_windows_x86_64.sh b/scripts/run_integration_tests_windows_x86_64.sh index 52b7796b83..56d41c166e 100755 --- a/scripts/run_integration_tests_windows_x86_64.sh +++ b/scripts/run_integration_tests_windows_x86_64.sh @@ -48,7 +48,7 @@ export RUSTFLAGS="$RUSTFLAGS" # Only run with 1 thread to avoid tests interfering with one another because # Windows has a static IP configured -time cargo nextest run --no-tests=pass $test_features "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} +time cargo nextest run --retries 3 --no-tests=pass $test_features "windows::$test_filter" --target "$BUILD_TARGET" -- ${test_binary_args[*]} RES=$? dmsetup remove_all -f From 87ba83bb01d2e2a6c24c23dbfb10381731b5d265 Mon Sep 17 00:00:00 2001 From: Max Makarov Date: Fri, 10 Apr 2026 17:17:01 +0000 Subject: [PATCH 1107/1893] 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 --- vmm/src/pci_segment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index b334ddb5d6..81f11063ee 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -360,7 +360,7 @@ impl Aml for PciSegment { pci_dsdt_inner_data.push(&adr); let seg = aml::Name::new("_SEG".into(), &self.id); pci_dsdt_inner_data.push(&seg); - let uid = aml::Name::new("_UID".into(), &aml::ZERO); + let uid = aml::Name::new("_UID".into(), &self.id); pci_dsdt_inner_data.push(&uid); let cca = aml::Name::new("_CCA".into(), &aml::ONE); pci_dsdt_inner_data.push(&cca); From c99ed77d1a8f6126f22402da206a604a97dc7db8 Mon Sep 17 00:00:00 2001 From: Max Makarov Date: Sat, 11 Apr 2026 08:11:51 +0000 Subject: [PATCH 1108/1893] 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 --- cloud-hypervisor/tests/integration.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 8b16c909e3..22d32852e6 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -489,6 +489,20 @@ mod common_parallel { .unwrap_or_default(), TEST_DISK_NODE ); + + // Each PNP0A08 host bridge in the DSDT must expose a unique + // _UID matching its PCI segment id. Linux surfaces the + // evaluated _UID via /sys/bus/acpi/devices/PNP0A08:*/uid. + // This test uses firmware boot on aarch64, so ACPI is + // available on both supported architectures. + let mut uids: Vec = guest + .ssh_command("cat /sys/bus/acpi/devices/PNP0A08:*/uid") + .unwrap() + .lines() + .filter_map(|l| l.trim().parse::().ok()) + .collect(); + uids.sort(); + assert_eq!(uids, vec![0u16, 1u16]); }); kill_child(&mut child); From b9c3cfb14d5d8a6503f34b06447974d15668eb5c Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 8 Apr 2026 17:16:19 +0200 Subject: [PATCH 1109/1893] 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 --- vm-migration/src/context.rs | 297 ++++++++++++++++++------------------ 1 file changed, 150 insertions(+), 147 deletions(-) diff --git a/vm-migration/src/context.rs b/vm-migration/src/context.rs index d7680821cc..69f9f3730c 100644 --- a/vm-migration/src/context.rs +++ b/vm-migration/src/context.rs @@ -238,169 +238,172 @@ impl Display for MemoryMigrationContext { #[cfg(test)] mod unit_tests { - use std::time::{Duration, Instant}; - use super::*; - use crate::protocol::MemoryRange; - - fn make_table(bytes: u64) -> MemoryRangeTable { - let mut table = MemoryRangeTable::default(); - if bytes > 0 { - table.push(MemoryRange { - gpa: 0, - length: bytes, - }); - } - table - } - - /// A controlled migration scenario with fixed timing offsets. - /// - /// ```text - /// migration_begin - /// + 1.0s -> iteration_begin - /// + 1.1s -> transfer_begin - /// + 2.0s -> transfer ends (transfer_duration = 0.9s) - /// + 2.1s -> iteration ends (iteration_duration = 1.1s, overhead = 0.2s) - /// ``` - struct Scenario { - migration_begin: Instant, - iteration_begin: Instant, - transfer_begin: Instant, - transfer_duration: Duration, - } - - impl Scenario { - /// We use a fixed point in the past so all offsets are in the past too, - /// meaning elapsed() calls in the code under test will be >= our durations. - const FIXPOINT_PAST: Duration = Duration::from_secs(10); - - fn new() -> Self { - // Use a fixed point in the past so all offsets are in the past too, - // meaning elapsed() calls in the code under test will be >= our durations. - let migration_begin = Instant::now() - Self::FIXPOINT_PAST; - Self { - migration_begin, - iteration_begin: migration_begin + Duration::from_millis(1000), - transfer_begin: migration_begin + Duration::from_millis(1100), - transfer_duration: Duration::from_millis(900), + mod memory_migration_ctx_tests { + use std::time::{Duration, Instant}; + + use super::*; + use crate::protocol::MemoryRange; + + fn make_table(bytes: u64) -> MemoryRangeTable { + let mut table = MemoryRangeTable::default(); + if bytes > 0 { + table.push(MemoryRange { + gpa: 0, + length: bytes, + }); } + table } - fn make_ctx(&self) -> MemoryMigrationContext { - let mut ctx = MemoryMigrationContext::new(); - // Override migration_begin with our controlled value. - ctx.migration_begin = self.migration_begin; - ctx + /// A controlled migration scenario with fixed timing offsets. + /// + /// ```text + /// migration_begin + /// + 1.0s -> iteration_begin + /// + 1.1s -> transfer_begin + /// + 2.0s -> transfer ends (transfer_duration = 0.9s) + /// + 2.1s -> iteration ends (iteration_duration = 1.1s, overhead = 0.2s) + /// ``` + struct Scenario { + migration_begin: Instant, + iteration_begin: Instant, + transfer_begin: Instant, + transfer_duration: Duration, } - } - #[test] - fn before_transfer_updates_begin_and_bytes() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); - - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(4096)); - - assert_eq!(ctx.iteration_begin, s.iteration_begin); - assert_eq!(ctx.current_iteration_total_bytes, 4096); - } + impl Scenario { + /// We use a fixed point in the past so all offsets are in the past too, + /// meaning elapsed() calls in the code under test will be >= our durations. + const FIXPOINT_PAST: Duration = Duration::from_secs(10); + + fn new() -> Self { + // Use a fixed point in the past so all offsets are in the past too, + // meaning elapsed() calls in the code under test will be >= our durations. + let migration_begin = Instant::now() - Self::FIXPOINT_PAST; + Self { + migration_begin, + iteration_begin: migration_begin + Duration::from_millis(1000), + transfer_begin: migration_begin + Duration::from_millis(1100), + transfer_duration: Duration::from_millis(900), + } + } - #[test] - fn before_transfer_estimated_downtime() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); + fn make_ctx(&self) -> MemoryMigrationContext { + let mut ctx = MemoryMigrationContext::new(); + // Override migration_begin with our controlled value. + ctx.migration_begin = self.migration_begin; + ctx + } + } - // Empty table -> zero downtime regardless of bandwidth - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(0)); - assert_eq!(ctx.estimated_downtime, Some(Duration::ZERO)); + #[test] + fn before_transfer_updates_begin_and_bytes() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); - // No bandwidth yet -> None - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - assert_eq!(ctx.estimated_downtime, None); + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(4096)); - // 1024 B/s, 1024 bytes -> 1s - ctx.bandwidth_bytes_per_second = 1024.0; - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - assert_eq!(ctx.estimated_downtime, Some(Duration::from_secs(1))); - } + assert_eq!(ctx.iteration_begin, s.iteration_begin); + assert_eq!(ctx.current_iteration_total_bytes, 4096); + } - #[test] - fn after_transfer_updates_timing_and_bandwidth() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); - - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); - - assert_eq!(ctx.transfer_begin, s.transfer_begin); - assert_eq!(ctx.transfer_duration, Some(s.transfer_duration)); - // 1024 bytes / 0.9s - assert_eq!(ctx.bandwidth_bytes_per_second, 1024.0 / 0.9); - // iteration_duration = time from iteration_begin until now (>= transfer_duration) - assert!(ctx.iteration_duration.unwrap() >= s.transfer_duration); - // Zero transfer_duration -> bandwidth is 0.0, no division by zero - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - ctx.update_metrics_after_transfer(s.transfer_begin, Duration::ZERO); - assert_eq!(ctx.bandwidth_bytes_per_second, 0.0); - - // Check finalize() sets migration duration - assert_eq!(ctx.migration_duration, None); - ctx.finalize(); - assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); - } + #[test] + fn before_transfer_estimated_downtime() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); - #[test] - fn two_iterations_accumulate_bytes_and_feed_downtime_estimate() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); - - // Iteration 0: no bandwidth yet -> downtime is None - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - assert_eq!(ctx.estimated_downtime, None); - ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); - assert_eq!(ctx.total_sent_bytes, 1024); - - // Iteration 1: bandwidth now known -> downtime is Some - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(2048)); - assert!(ctx.estimated_downtime.is_some()); - ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); - assert_eq!(ctx.total_sent_bytes, 1024 + 2048); - - // Check finalize() sets migration duration - assert_eq!(ctx.migration_duration, None); - ctx.finalize(); - assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); - } + // Empty table -> zero downtime regardless of bandwidth + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(0)); + assert_eq!(ctx.estimated_downtime, Some(Duration::ZERO)); - #[test] - /// The display format is specifically crafted to be very insightful in logs. - /// Therefore, we have a dedicated test for that format. - fn display_format() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); + // No bandwidth yet -> None + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + assert_eq!(ctx.estimated_downtime, None); - // Iteration 0: 1 MiB in 1s - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024 * 1024)); - ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); - ctx.iteration += 1; - - // Iteration 1: 512 KiB in 1s; fix migration_duration for deterministic elapsed/avg_bw - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(512 * 1024)); - ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); + // 1024 B/s, 1024 bytes -> 1s + ctx.bandwidth_bytes_per_second = 1024.0; + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + assert_eq!(ctx.estimated_downtime, Some(Duration::from_secs(1))); + } - ctx.migration_duration = Some(Duration::from_secs(2)); - let out = ctx.to_string(); + #[test] + fn after_transfer_updates_timing_and_bandwidth() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); + + assert_eq!(ctx.transfer_begin, s.transfer_begin); + assert_eq!(ctx.transfer_duration, Some(s.transfer_duration)); + // 1024 bytes / 0.9s + assert_eq!(ctx.bandwidth_bytes_per_second, 1024.0 / 0.9); + // iteration_duration = time from iteration_begin until now (>= transfer_duration) + assert!(ctx.iteration_duration.unwrap() >= s.transfer_duration); + // Zero transfer_duration -> bandwidth is 0.0, no division by zero + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, Duration::ZERO); + assert_eq!(ctx.bandwidth_bytes_per_second, 0.0); + + // Check finalize() sets migration duration + assert_eq!(ctx.migration_duration, None); + ctx.finalize(); + assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); + } - assert_eq!( - out, - "iter=1 curr=1MiB total=2MiB bw=0.50MiB/s transfer=1.00s overhead=8000ms est_downtime=500ms elapsed=2.00s avg_bw=0.15MiB/s" - ); + #[test] + fn two_iterations_accumulate_bytes_and_feed_downtime_estimate() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + // Iteration 0: no bandwidth yet -> downtime is None + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + assert_eq!(ctx.estimated_downtime, None); + ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); + assert_eq!(ctx.total_sent_bytes, 1024); + + // Iteration 1: bandwidth now known -> downtime is Some + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(2048)); + assert!(ctx.estimated_downtime.is_some()); + ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); + assert_eq!(ctx.total_sent_bytes, 1024 + 2048); + + // Check finalize() sets migration duration + assert_eq!(ctx.migration_duration, None); + ctx.finalize(); + assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); + } - // Should change elapsed() time! - // Since this is at least 10s, we never face timing issues in CI! - ctx.finalize(); - let out2 = ctx.to_string(); - assert_ne!(out2, out, "elapsed time should have changed! is={out2}"); + #[test] + /// The display format is specifically crafted to be very insightful in logs. + /// Therefore, we have a dedicated test for that format. + fn display_format() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + // Iteration 0: 1 MiB in 1s + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024 * 1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); + ctx.iteration += 1; + + // Iteration 1: 512 KiB in 1s; fix migration_duration for deterministic elapsed/avg_bw + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(512 * 1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); + + ctx.migration_duration = Some(Duration::from_secs(2)); + let out = ctx.to_string(); + + assert_eq!( + out, + "iter=1 curr=1MiB total=2MiB bw=0.50MiB/s transfer=1.00s overhead=8000ms est_downtime=500ms elapsed=2.00s avg_bw=0.15MiB/s" + ); + + // Should change elapsed() time! + // Since this is at least 10s, we never face timing issues in CI! + ctx.finalize(); + let out2 = ctx.to_string(); + assert_ne!(out2, out, "elapsed time should have changed! is={out2}"); + } } } From f21184c325fc78ecd64f27e3c3076972f7664238 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 9 Apr 2026 09:29:52 +0200 Subject: [PATCH 1110/1893] 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 --- vm-migration/src/context.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/vm-migration/src/context.rs b/vm-migration/src/context.rs index 69f9f3730c..0bb9ea190f 100644 --- a/vm-migration/src/context.rs +++ b/vm-migration/src/context.rs @@ -54,13 +54,13 @@ pub struct MemoryMigrationContext { /// This includes the transmission, all logging, and update of any metrics. /// /// This is only `None` for iteration 0. - iteration_duration: Option, + pub iteration_duration: Option, /// Begin of the current transfer. transfer_begin: Instant, /// Duration of the current transfer. /// /// This is only `None` for iteration 0. - transfer_duration: Option, + pub transfer_duration: Option, } impl MemoryMigrationContext { @@ -178,6 +178,22 @@ impl MemoryMigrationContext { bytes as f64 / duration.as_secs_f64() } } + + /// Calculates the overhead of an iteration. + /// + /// This is the additional time next to the transfer time and includes + /// fetching and parsing the dirty log, for example. + fn iteration_overhead(&self) -> Duration { + self.iteration_duration + .and_then(|iter| { + self.transfer_duration.map(|tr| { + // This is guaranteed by update_metrics_after_transfer() + assert!(iter >= tr); + iter - tr + }) + }) + .unwrap_or_default() + } } impl Default for MemoryMigrationContext { @@ -207,16 +223,7 @@ impl Display for MemoryMigrationContext { // Transfer duration and iteration overhead let transfer_s = self.transfer_duration.map_or(0.0, |d| d.as_secs_f64()); - let iteration_overhead_ms = self - .iteration_duration - .and_then(|iter| { - self.transfer_duration.map(|tr| { - // This is guaranteed by update_metrics_after_transfer() - assert!(iter >= tr); - (iter - tr).as_millis() - }) - }) - .unwrap_or(0); + let iteration_overhead_ms = self.iteration_overhead().as_millis(); let est_downtime_ms = self.estimated_downtime.map_or(0, |d| d.as_millis()); From 94f78e5a96f4abd25a10a7b2822dc17e210ead0a Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 9 Apr 2026 09:30:26 +0200 Subject: [PATCH 1111/1893] 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 --- vm-migration/src/context.rs | 272 ++++++++++++++++++++++++++++++++++- vm-migration/src/lib.rs | 5 +- vm-migration/src/protocol.rs | 1 + 3 files changed, 275 insertions(+), 3 deletions(-) diff --git a/vm-migration/src/context.rs b/vm-migration/src/context.rs index 0bb9ea190f..338dad9edd 100644 --- a/vm-migration/src/context.rs +++ b/vm-migration/src/context.rs @@ -3,14 +3,213 @@ // SPDX-License-Identifier: Apache-2.0 // -//! Module for [`MemoryMigrationContext`]. +//! Module for context and metrics of migrations. +//! +//! Main exports: +//! - [`OngoingMigrationContext`] +//! - [`CompletedMigrationContext`] +//! - [`MemoryMigrationContext`] use std::fmt; -use std::fmt::Display; +use std::fmt::{Display, Formatter}; use std::time::{Duration, Instant}; +use thiserror::Error; + use crate::protocol::MemoryRangeTable; +/// Metrics of the VM downtime during a migration. +/// +/// By downtime, we mean the time between the VM pause() and the corresponding +/// resume() on the destination. This downtime covers the time when the vCPUs +/// didn't execute a single instruction. The network downtime might be longer +/// and is not covered by this type. +/// +/// This metric is only relevant for the migration of running VMs. +#[derive(Debug, PartialEq)] +pub struct DowntimeContext { + /// The effective downtime Cloud Hypervisor observed (from the migration sender). + /// + /// This is roughly the sum of all the other durations. + pub effective_downtime: Duration, + /// The time of the final memory iteration. + pub final_memory_iteration_dur: Duration, + /// The time needed to aggregate the final VM state (i.e., snapshotting it). + pub state_dur: Duration, + /// The time needed to send the final VM state including deserializing it on + /// the destination + pub send_state_dur: Duration, + /// The time of the completion request. This includes resuming the VM (if it + /// was running before the migration). + pub complete_dur: Duration, +} + +impl Display for DowntimeContext { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + // Caution: This format is specifically crafted for the VMM log + "{}ms (final_iter:{}ms state:{}ms send_state:{}ms complete:{}ms)", + self.effective_downtime.as_millis(), + self.final_memory_iteration_dur.as_millis(), + self.state_dur.as_millis(), + self.send_state_dur.as_millis(), + self.complete_dur.as_millis() + ) + } +} + +/// The internal metrics of a completed migration. +/// +/// The properties of this type help to investigate timings of the migration, +/// with specific focus on the VM downtime. +/// +/// This type is static once it was created and should not change. +#[derive(Debug, PartialEq)] +pub struct CompletedMigrationContext { + /// Total duration of the migration. + pub migration_dur: Duration, + pub downtime_ctx: DowntimeContext, + /// The finalized context of the memory migration. + pub memory_ctx: MemoryMigrationContext, +} + +impl CompletedMigrationContext { + fn new( + migration_dur: Duration, + effective_downtime: Duration, + state_dur: Duration, + send_state_dur: Duration, + complete_dur: Duration, + memory_ctx: MemoryMigrationContext, + ) -> Self { + Self { + migration_dur, + downtime_ctx: DowntimeContext { + effective_downtime, + final_memory_iteration_dur: memory_ctx.iteration_duration.unwrap_or_default(), + state_dur, + send_state_dur, + complete_dur, + }, + memory_ctx, + } + } +} + +/// Error returned when the migration context is advanced in an invalid order. +#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)] +pub enum MigrationContextError { + /// The memory migration context was not finalized before transition. + #[error("memory migration context should be finalized before pausing the VM")] + MemoryContextNotFinalized, + /// The transition to `VmPaused` was attempted from an invalid state. + #[error("memory migration should only advance from the Begin state")] + InvalidVmPausedTransition, + /// Finalization was attempted before memory migration completed. + #[error("migration should only finalize after memory migration completed")] + InvalidFinalizeTransition, +} + +/// Holds context and metrics about the current ongoing migration. +/// +/// This is a state-machine to properly reflect the intermediate states and +/// their properties. This machine does not have a `Completed` variant in favor +/// of [`CompletedMigrationContext`], which is easier to work with. +#[derive(Debug, PartialEq)] +pub enum OngoingMigrationContext { + /// Migration started. + Begin { + /// Begin of the migration. + migration_begin: Instant, + }, + /// VM memory fully transferred to the destination and the VM is paused. + VmPaused { + /// Begin of the migration. + migration_begin: Instant, + /// Downtime begin of the migration. + downtime_begin: Instant, + /// The finalized context of the memory migration. + finalized_memory_ctx: MemoryMigrationContext, + }, +} + +impl OngoingMigrationContext { + /// Creates a new context. + pub fn new() -> Self { + Self::Begin { + migration_begin: Instant::now(), + } + } + + /// Marks the memory migration as completed and records when downtime + /// started. The VM is now in paused state. + pub fn set_vm_paused( + &mut self, + downtime_begin: Instant, + finalized_memory_ctx: MemoryMigrationContext, + ) -> Result<(), MigrationContextError> { + if finalized_memory_ctx.migration_duration.is_none() { + return Err(MigrationContextError::MemoryContextNotFinalized); + } + let migration_begin = match self { + Self::Begin { migration_begin } => *migration_begin, + _ => return Err(MigrationContextError::InvalidVmPausedTransition), + }; + *self = Self::VmPaused { + migration_begin, + downtime_begin, + finalized_memory_ctx, + }; + Ok(()) + } + + /// Finalizes the metrics and returns a [`CompletedMigrationContext`]. + /// + /// This should be called right after the completed migration was + /// acknowledged by the receiver. From now on, the metrics are considered + /// finalized and should not be modified. They can be stored for further + /// analysis. + /// + /// # Arguments + /// - `state_dur`: The time needed to aggregate the final VM state (i.e., + /// snapshotting it). + /// - `send_state_dur`: The time needed to send the final VM state + /// including deserializing it on the destination. + /// - `complete_dur`: The time of the completion request. This includes + /// resuming the VM (if it was running before the migration). + pub fn finalize( + self, + state_dur: Duration, + send_state_dur: Duration, + complete_dur: Duration, + ) -> Result { + let (migration_begin, downtime_begin, finalized_memory_ctx) = match self { + Self::VmPaused { + migration_begin, + downtime_begin, + finalized_memory_ctx, + } => (migration_begin, downtime_begin, finalized_memory_ctx), + _ => return Err(MigrationContextError::InvalidFinalizeTransition), + }; + + Ok(CompletedMigrationContext::new( + migration_begin.elapsed(), + downtime_begin.elapsed(), + state_dur, + send_state_dur, + complete_dur, + finalized_memory_ctx, + )) + } +} + +impl Default for OngoingMigrationContext { + fn default() -> Self { + Self::new() + } +} + /// Internal metrics for the precopy migration phase. /// /// The context aggregates runtime statistics such as iteration count, @@ -246,6 +445,75 @@ impl Display for MemoryMigrationContext { #[cfg(test)] mod unit_tests { use super::*; + + /// Tests for [`CompletedMigrationContext`] and [`OngoingMigrationContext`]. + mod migration_ctx_tests { + use super::*; + + #[test] + fn memory_migrated_and_vm_paused_records_transition() { + let mut ctx = OngoingMigrationContext::new(); + let downtime_begin = Instant::now(); + + let mut memory_ctx = MemoryMigrationContext::new(); + memory_ctx.finalize(); + + ctx.set_vm_paused(downtime_begin, memory_ctx) + .expect("migration context should transition to VmPaused after memory migration"); + + assert!(matches!( + ctx, + OngoingMigrationContext::VmPaused { + downtime_begin: recorded_downtime_begin, + .. + } if recorded_downtime_begin == downtime_begin + )); + } + + #[test] + fn finalize_returns_completed_context() { + let mut ctx = OngoingMigrationContext::new(); + let downtime_begin = Instant::now() - Duration::from_millis(10); + + let mut memory_ctx = MemoryMigrationContext::new(); + memory_ctx.finalize(); + + ctx.set_vm_paused(downtime_begin, memory_ctx) + .expect("migration context should transition to VmPaused after memory migration"); + + let completed = ctx + .finalize( + Duration::from_millis(1), + Duration::from_millis(2), + Duration::from_millis(3), + ) + .expect("migration context should finalize after memory migration completed"); + + assert_eq!(completed.downtime_ctx.state_dur, Duration::from_millis(1)); + assert_eq!( + completed.downtime_ctx.send_state_dur, + Duration::from_millis(2) + ); + assert_eq!( + completed.downtime_ctx.complete_dur, + Duration::from_millis(3) + ); + assert!(completed.downtime_ctx.effective_downtime >= Duration::from_millis(10)); + assert!(completed.migration_dur > Duration::ZERO); + assert!(completed.memory_ctx.migration_duration.is_some()); + } + + #[test] + fn finalize_errors_before_memory_migration_completed() { + let err = OngoingMigrationContext::new() + .finalize(Duration::ZERO, Duration::ZERO, Duration::ZERO) + .unwrap_err(); + + assert_eq!(err, MigrationContextError::InvalidFinalizeTransition); + } + } + + /// Tests for [`MemoryMigrationContext`]. mod memory_migration_ctx_tests { use std::time::{Duration, Instant}; diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 2283ff8bed..0faedf2858 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -4,7 +4,10 @@ // use anyhow::anyhow; -pub use context::MemoryMigrationContext; +pub use context::{ + CompletedMigrationContext, DowntimeContext, MemoryMigrationContext, MigrationContextError, + OngoingMigrationContext, +}; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index 0a62375f54..f927d7a36e 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -115,6 +115,7 @@ pub enum Command { Config, State, Memory, + /// Finalizes the migration and resumes the VM on the guest. Complete, Abandon, MemoryFd, From 6a3024c13d89c143cbc600985f8c915d2bfe2ef1 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 9 Apr 2026 10:42:55 +0200 Subject: [PATCH 1112/1893] 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 --- vm-migration/src/context.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vm-migration/src/context.rs b/vm-migration/src/context.rs index 338dad9edd..21801c0290 100644 --- a/vm-migration/src/context.rs +++ b/vm-migration/src/context.rs @@ -286,6 +286,15 @@ impl MemoryMigrationContext { } } + /// Returns an empty finalized block. + /// + /// This can be used if no memory was transferred (e.g., local migration). + pub fn empty_finalized() -> Self { + let mut this = Self::new(); + this.finalize(); + this + } + /// Updates the metrics right before the transfer over the wire. /// /// Supposed to be called once per precopy memory iteration. From f32506447fae80edf8987a2ef3264a29de33b998 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 9 Apr 2026 09:32:14 +0200 Subject: [PATCH 1113/1893] 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 --- vmm/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 92bf4c6b70..c64abac20d 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -563,6 +563,17 @@ pub fn start_vmm_thread( }) } +/// Measures the time of the callback, in case it returns `Ok`. +fn measure_ok(f: F) -> result::Result<(T, Duration), E> +where + F: FnOnce() -> result::Result, +{ + let begin = Instant::now(); + let value = f()?; + let duration = begin.elapsed(); + Ok((value, duration)) +} + #[derive(Clone, Deserialize, Serialize)] struct VmMigrationConfig { vm_config: Arc>, From f15999823dee2de36e70c3304e38d69296d0afc5 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 9 Apr 2026 09:32:21 +0200 Subject: [PATCH 1114/1893] 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: INFO:vmm/src/lib.rs:1494 -- Migration completed after 2.2s with a downtime of 298ms (goal was 300ms) cloud-hypervisor: 7.703453s: 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 --- vmm/src/lib.rs | 99 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 29 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index c64abac20d..5cedeef65e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -40,8 +40,8 @@ use vm_memory::GuestMemoryAtomic; use vm_memory::bitmap::AtomicBitmap; use vm_migration::protocol::*; use vm_migration::{ - MemoryMigrationContext, Migratable, MigratableError, Pausable, Snapshot, Snapshottable, - Transportable, + MemoryMigrationContext, Migratable, MigratableError, OngoingMigrationContext, Pausable, + Snapshot, Snapshottable, Transportable, }; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::signal::unblock_signal; @@ -1260,23 +1260,30 @@ impl Vmm { /// - initial memory - VM is running /// - multiple memory delta transmissions - VM is running /// - final memory iteration - VM is paused + /// + /// Stores the [finalized] [`MemoryMigrationContext`] in the provided + /// [`OngoingMigrationContext`]. + /// + /// [finalized]: MemoryMigrationContext::finalize fn do_memory_migration( vm: &mut Vm, socket: &mut SocketStream, send_data_migration: &VmSendMigrationData, mem_send: &mut SendAdditionalConnections, + ctx: &mut OngoingMigrationContext, ) -> result::Result<(), MigratableError> { - let mut ctx = MemoryMigrationContext::new(); + let mut mem_ctx = MemoryMigrationContext::new(); vm.start_dirty_log()?; let remaining = Self::do_memory_iterations( vm, socket, - &mut ctx, + &mut mem_ctx, // We bind send_data_migration to the callback |ctx| Self::is_precopy_converged(ctx, send_data_migration), mem_send, )?; + let downtime_begin = Instant::now(); vm.pause()?; // Send last batch of dirty pages: final iteration @@ -1286,26 +1293,31 @@ impl Vmm { let mut final_table = vm.dirty_log()?; final_table.extend(remaining); - ctx.update_metrics_before_transfer(iteration_begin, &final_table); + mem_ctx.update_metrics_before_transfer(iteration_begin, &final_table); let transfer_begin = Instant::now(); mem_send.send_memory(final_table, socket)?; let transfer_duration = transfer_begin.elapsed(); - ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); - ctx.iteration += 1; + mem_ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); + mem_ctx.iteration += 1; } - ctx.finalize(); - - info!("Precopy complete: {ctx}"); + mem_ctx.finalize(); + info!("Precopy complete: {mem_ctx}"); + ctx.set_vm_paused(downtime_begin, mem_ctx) + .expect("migration context should transition to VmPaused after memory migration"); Ok(()) } + /// Performs a migration including all its phases. fn send_migration( vm: &mut Vm, #[cfg(all(feature = "kvm", target_arch = "x86_64"))] hypervisor: &dyn hypervisor::Hypervisor, send_data_migration: &VmSendMigrationData, ) -> result::Result<(), MigratableError> { + // State machine that is updated with more context as we progress. + let mut ctx = OngoingMigrationContext::new(); + // Set up the socket connection let mut socket = migration_transport::send_migration_socket(&send_data_migration.destination_url)?; @@ -1373,7 +1385,14 @@ impl Vmm { if send_data_migration.local { // Now pause VM + let downtime_begin = Instant::now(); vm.pause()?; + ctx.set_vm_paused( + downtime_begin, + // No memory was transferred + MemoryMigrationContext::empty_finalized(), + ) + .expect("migration context should transition to VmPaused for local migration"); } else { let mut mem_send = migration_transport::SendAdditionalConnections::new( &send_data_migration.destination_url, @@ -1381,14 +1400,20 @@ impl Vmm { &vm.guest_memory(), )?; - Self::do_memory_migration(vm, &mut socket, send_data_migration, &mut mem_send) - .inspect_err(|_| { - // Calling cleanup multiple times is fine, thus here we just make sure - // that it is called. - if let Err(e) = mem_send.cleanup() { - warn!("Error cleaning up migration connections: {e}"); - } - })?; + Self::do_memory_migration( + vm, + &mut socket, + send_data_migration, + &mut mem_send, + &mut ctx, + ) + .inspect_err(|_| { + // Calling cleanup multiple times is fine, thus here we just make sure + // that it is called. + if let Err(e) = mem_send.cleanup() { + warn!("Error cleaning up migration connections: {e}"); + } + })?; mem_send.cleanup()?; } @@ -1399,23 +1424,39 @@ impl Vmm { .map_err(|e| MigratableError::UnlockError(anyhow!("{e}")))?; // Capture snapshot and send it - let vm_snapshot = vm.snapshot()?; - migration_transport::send_state(&mut socket, &vm_snapshot)?; - // Complete the migration - // At this step, the receiving VMM will acquire disk locks again. - migration_transport::send_request_expect_ok( - &mut socket, - Request::complete(), - MigratableError::MigrateSend(anyhow!("Error completing migration")), - )?; + let (vm_snapshot, snapshot_duration) = measure_ok(|| vm.snapshot())?; + let (_, send_snapshot_duration) = + measure_ok(|| migration_transport::send_state(&mut socket, &vm_snapshot))?; + + // Complete the migration. + // When this returns, we know the VM was resumed (if it was running + // before the migration) and that the receiving VMM acquired disk + // locks again. + let (_, complete_duration) = measure_ok(|| { + migration_transport::send_request_expect_ok( + &mut socket, + Request::complete(), + MigratableError::MigrateSend(anyhow!("Error completing migration")), + ) + })?; + + let ctx = ctx + .finalize(snapshot_duration, send_snapshot_duration, complete_duration) + .expect("migration context should finalize after memory migration completed"); + + info!( + "Migration completed after {:.1}s with a downtime of {}ms (goal was {}ms)", + ctx.migration_dur.as_secs_f32(), + ctx.downtime_ctx.effective_downtime.as_millis(), + send_data_migration.downtime().as_millis() + ); + debug!("Downtime breakdown: {}", ctx.downtime_ctx); // Stop logging dirty pages if !send_data_migration.local { vm.stop_dirty_log()?; } - info!("Migration complete"); - // Let every Migratable object know about the migration being complete vm.complete_migration() } From 2515b06f1938a12a98f4926539a2d481f8fe09bf Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 8 Apr 2026 16:04:30 +0200 Subject: [PATCH 1115/1893] 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: DEBUG:vmm/src/lib.rs:948 -- Migration (incoming): recv_snapshot:3ms restore:10ms cloud-hypervisor: 7.284824s: DEBUG:vmm/src/lib.rs:967 -- Migration (incoming): resume:1ms cloud-hypervisor: 7.284842s: 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 --- vmm/src/lib.rs | 126 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 41 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 5cedeef65e..df94b54e22 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -653,7 +653,10 @@ enum ReceiveMigrationState { Configured(ReceiveMigrationConfiguredData), /// Memory is populated and we received the state. The VM is ready to go. - StateReceived, + StateReceived { + /// The timestamp where the VMM started to receive the final state. + state_receive_begin: Instant, + }, /// The migration is successful. Completed, @@ -938,18 +941,43 @@ impl Vmm { Ok(Configured(config_data)) } Command::State => { + let state_receive_begin = Instant::now(); config_data.connections.cleanup()?; - self.vm_receive_state(req, socket, config_data.memory_manager)?; - Ok(StateReceived) + let (recv_state_dur, restore_vm_dur) = + self.vm_receive_state(req, socket, config_data.memory_manager)?; + debug!( + "Migration (incoming): recv_snapshot:{}ms restore:{}ms", + recv_state_dur.as_millis(), + restore_vm_dur.as_millis(), + ); + Ok(StateReceived { + state_receive_begin, + }) } _ => invalid_command(), }, - StateReceived => match req.command() { + StateReceived { + state_receive_begin, + } => match req.command() { Command::Complete => { // The unwrap is safe, because the state machine makes sure we called // vm_receive_state before, which creates the VM. let vm = self.vm.as_mut().unwrap(); - vm.resume()?; + let (_, resume_duration) = measure_ok(|| vm.resume())?; + debug!( + "Migration (incoming): resume:{}ms", + resume_duration.as_millis() + ); + // This logs the downtime without the final memory delta, so + // it does not reflect the actual downtime. While we could + // pass along the timestamp from when the VM was paused, + // that would rely on both VM hosts having synchronized + // clocks, which we cannot guarantee. For that reason, this + // is logged as debug! rather than info!. + debug!( + "Migration (incoming): Receiving final state and resuming the VM took {}ms", + state_receive_begin.elapsed().as_millis() + ); Ok(Completed) } _ => invalid_command(), @@ -1046,23 +1074,33 @@ impl Vmm { Ok(memory_manager) } + /// Receives the final VM state (devices, vCPUs) and restores the VM. + /// + /// Measures the time for each step. fn vm_receive_state( &mut self, req: &Request, socket: &mut T, mm: Arc>, - ) -> std::result::Result<(), MigratableError> + ) -> std::result::Result< + ( + Duration, /* state receive + deserialize */ + Duration, /* restoring */ + ), + MigratableError, + > where T: Read, { - // Read in state data - let mut data: Vec = Vec::new(); - data.resize_with(req.length() as usize, Default::default); - socket - .read_exact(&mut data) - .map_err(MigratableError::MigrateSocket)?; - let snapshot: Snapshot = serde_json::from_slice(&data).map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error deserialising snapshot: {e}")) + let (snapshot, receive_duration): (Snapshot, Duration) = measure_ok(|| { + let mut data: Vec = Vec::new(); + data.resize_with(req.length() as usize, Default::default); + socket + .read_exact(&mut data) + .map_err(MigratableError::MigrateSocket)?; + serde_json::from_slice(&data).map_err(|e| { + MigratableError::MigrateReceive(anyhow!("Error deserialising snapshot: {e}")) + }) })?; let exit_evt = self.exit_evt.try_clone().map_err(|e| { @@ -1079,38 +1117,44 @@ impl Vmm { MigratableError::MigrateReceive(anyhow!("Error cloning activate EventFd: {e}")) })?; - #[cfg(not(target_arch = "riscv64"))] - let timestamp = Instant::now(); - let hypervisor_vm = mm.lock().unwrap().vm.clone(); - let mut vm = Vm::new_from_memory_manager( - self.vm_config.clone().unwrap(), - mm, - hypervisor_vm, - exit_evt, - reset_evt, - #[cfg(feature = "guest_debug")] - debug_evt, - &self.seccomp_action, - self.hypervisor.clone(), - activate_evt, + let (vm, restore_duration) = measure_ok(|| { #[cfg(not(target_arch = "riscv64"))] - timestamp, - self.console_info.clone(), - self.console_resize_pipe.clone(), - Arc::clone(&self.original_termios_opt), - Some(&snapshot), - ) - .map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Error creating VM from snapshot: {e:?}")) - })?; + let timestamp = Instant::now(); + let hypervisor_vm = mm.lock().unwrap().vm.clone(); + + let mut vm = Vm::new_from_memory_manager( + self.vm_config.clone().unwrap(), + mm, + hypervisor_vm, + exit_evt, + reset_evt, + #[cfg(feature = "guest_debug")] + debug_evt, + &self.seccomp_action, + self.hypervisor.clone(), + activate_evt, + #[cfg(not(target_arch = "riscv64"))] + timestamp, + self.console_info.clone(), + self.console_resize_pipe.clone(), + Arc::clone(&self.original_termios_opt), + Some(&snapshot), + ) + .map_err(|e| { + MigratableError::MigrateReceive(anyhow!("Error creating VM from snapshot: {e:?}")) + })?; - // Create VM - vm.restore().map_err(|e| { - MigratableError::MigrateReceive(anyhow!("Failed restoring the Vm: {e}")) + // Create VM + vm.restore().map_err(|e| { + MigratableError::MigrateReceive(anyhow!("Failed restoring the Vm: {e}")) + })?; + + Ok(vm) })?; + self.vm = Some(vm); - Ok(()) + Ok((receive_duration, restore_duration)) } /// Performs the initial memory transmission (iteration zero) plus a From fd2d33e8ab8ce57f26d62f38b8bd1403b848a097 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Thu, 26 Mar 2026 10:20:18 +0000 Subject: [PATCH 1116/1893] 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 --- performance-metrics/src/main.rs | 69 ++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 2f6b8bdb50..622d8793c7 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -28,6 +28,14 @@ enum Error { TestFailed, } +#[derive(Deserialize, Serialize)] +enum TestStatus { + #[serde(rename = "PASSED")] + Passed, + #[serde(rename = "FAILED")] + Failed, +} + #[derive(Deserialize, Serialize)] pub struct PerformanceTestResult { name: String, @@ -35,6 +43,31 @@ pub struct PerformanceTestResult { std_dev: f64, max: f64, min: f64, + status: TestStatus, +} + +impl PerformanceTestResult { + fn passed(name: &str, mean: f64, std_dev: f64, max: f64, min: f64) -> Self { + Self { + name: name.to_string(), + mean, + std_dev, + max, + min, + status: TestStatus::Passed, + } + } + + fn failed(name: &str) -> Self { + Self { + name: name.to_string(), + mean: 0.0, + std_dev: 0.0, + max: 0.0, + min: 0.0, + status: TestStatus::Failed, + } + } } #[derive(Deserialize, Serialize)] @@ -280,13 +313,7 @@ impl PerformanceTest { let max = (self.unit_adjuster)(metrics.clone().into_iter().reduce(f64::max).unwrap()); let min = (self.unit_adjuster)(metrics.clone().into_iter().reduce(f64::min).unwrap()); - PerformanceTestResult { - name: self.name.to_string(), - mean, - std_dev, - max, - min, - } + PerformanceTestResult::passed(self.name, mean, std_dev, max, min) } // Calculate the timeout for each test @@ -1316,6 +1343,14 @@ fn main() { .action(ArgAction::SetTrue) .required(false), ) + .arg( + Arg::new("continue-on-failure") + .long("continue-on-failure") + .help("Continue running remaining tests after a test failure") + .num_args(0) + .action(ArgAction::SetTrue) + .required(false), + ) .arg( Arg::new("report-file") .long("report-file") @@ -1406,6 +1441,9 @@ fn main() { init_tests(&overrides); } + let continue_on_failure = cmd_arguments.get_flag("continue-on-failure"); + let mut has_failure = false; + for test in tests_to_run { settle_host(); match run_test_with_timeout(test, &overrides) { @@ -1413,8 +1451,17 @@ fn main() { metrics_report.results.push(r); } Err(e) => { - eprintln!("Aborting test due to error: '{e:?}'"); - std::process::exit(1); + if continue_on_failure { + eprintln!("Test '{}' failed: '{e:?}'. Continuing.", test.name); + has_failure = true; + metrics_report + .results + .push(PerformanceTestResult::failed(test.name)); + cleanup_stale_processes(); + } else { + eprintln!("Aborting test due to error: '{e:?}'"); + std::process::exit(1); + } } } } @@ -1448,4 +1495,8 @@ fn main() { std::process::exit(1); }) .unwrap(); + + if has_failure { + std::process::exit(1); + } } From e38c5c434038776a7c2cc01d9dbe72d3c057d493 Mon Sep 17 00:00:00 2001 From: CMGS Date: Mon, 13 Apr 2026 17:23:08 +0800 Subject: [PATCH 1117/1893] 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 --- devices/src/ivshmem.rs | 4 ++ devices/src/pvmemcontrol.rs | 4 ++ devices/src/pvpanic.rs | 4 ++ pci/src/bus.rs | 16 ++++--- pci/src/configuration.rs | 49 ++++++++++++++++++++++ pci/src/device.rs | 4 ++ pci/src/vfio.rs | 4 ++ pci/src/vfio_user.rs | 4 ++ virtio-devices/src/transport/pci_device.rs | 4 ++ 9 files changed, 88 insertions(+), 5 deletions(-) diff --git a/devices/src/ivshmem.rs b/devices/src/ivshmem.rs index 98291c74e8..932e0d9eba 100644 --- a/devices/src/ivshmem.rs +++ b/devices/src/ivshmem.rs @@ -382,6 +382,10 @@ impl PciDevice for IvshmemDevice { Ok(()) } + fn restore_bar_addr(&mut self, params: &BarReprogrammingParams) { + self.configuration.restore_bar_addr(params); + } + fn as_any_mut(&mut self) -> &mut dyn Any { self } diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index d4b37456be..171fdf1544 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -712,6 +712,10 @@ impl PciDevice for PvmemcontrolPciDevice { self.configuration.read_config_register(reg_idx) } + fn restore_bar_addr(&mut self, params: &BarReprogrammingParams) { + self.configuration.restore_bar_addr(params); + } + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } diff --git a/devices/src/pvpanic.rs b/devices/src/pvpanic.rs index 9540a91252..3b9c9d5a80 100644 --- a/devices/src/pvpanic.rs +++ b/devices/src/pvpanic.rs @@ -231,6 +231,10 @@ impl PciDevice for PvPanicDevice { Ok(()) } + fn restore_bar_addr(&mut self, params: &BarReprogrammingParams) { + self.configuration.restore_bar_addr(params); + } + fn read_bar(&mut self, _base: u64, _offset: u64, data: &mut [u8]) { data[0] = self.events; } diff --git a/pci/src/bus.rs b/pci/src/bus.rs index eaae23a4d8..1fa7bd866a 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -10,7 +10,7 @@ use std::ops::DerefMut; use std::sync::{Arc, Barrier, Mutex}; use byteorder::{ByteOrder, LittleEndian}; -use log::error; +use log::warn; use thiserror::Error; use vm_device::{Bus, BusDevice, BusDeviceSync}; @@ -280,10 +280,15 @@ impl PciConfigIo { device.deref_mut(), params.region_type, ) { - error!( - "Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})", + warn!( + "Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x}), keeping old BAR", e, params.old_base, params.new_base, params.len ); + // Rollback: the config register was already updated to + // new_base by detect_bar_reprogramming(). Restore it by + // writing back the old address so device state stays + // consistent with the MMIO bus mapping. + device.restore_bar_addr(params); } } @@ -405,10 +410,11 @@ impl PciConfigMmio { device.deref_mut(), params.region_type, ) { - error!( - "Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})", + warn!( + "Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x}), keeping old BAR", e, params.old_base, params.new_base, params.len ); + device.restore_bar_addr(params); } } } diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index f506017b26..2a905e19bc 100644 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -1093,6 +1093,55 @@ impl PciConfiguration { pub(crate) fn clear_pending_bar_reprogram(&mut self) { self.pending_bar_reprogram = Vec::new(); } + + /// Restore BAR address after a failed move. This undoes the premature + /// address update in detect_bar_reprogramming() so that config space + /// stays consistent with the actual MMIO mapping. + pub fn restore_bar_addr(&mut self, params: &BarReprogrammingParams) { + match params.region_type { + PciBarRegionType::Memory64BitRegion => { + // 64-bit BAR spans two slots: bars[i] (low, type Memory64BitRegion) + // and bars[i+1] (high, type None). Mirror detect_bar_reprogramming + // by matching the combined address and restoring both halves. + for i in 0..NUM_BAR_REGS - 1 { + if self.bars[i].r#type != Some(PciBarRegionType::Memory64BitRegion) { + continue; + } + let low_mask = self.writable_bits[BAR0_REG + i]; + let high_mask = self.writable_bits[BAR0_REG + i + 1]; + let current = (u64::from(self.bars[i + 1].addr & high_mask) << 32) + | u64::from(self.bars[i].addr & low_mask); + if current == params.new_base { + let old_low = params.old_base as u32; + let old_high = (params.old_base >> 32) as u32; + self.bars[i].addr = old_low; + self.bars[i + 1].addr = old_high; + self.registers[BAR0_REG + i] = + (self.registers[BAR0_REG + i] & !low_mask) | (old_low & low_mask); + self.registers[BAR0_REG + i + 1] = (self.registers[BAR0_REG + i + 1] + & !high_mask) + | (old_high & high_mask); + return; + } + } + } + _ => { + // 32-bit Memory or IO BAR + for i in 0..NUM_BAR_REGS { + let mask = self.writable_bits[BAR0_REG + i]; + if self.bars[i].r#type == Some(params.region_type) + && u64::from(self.bars[i].addr & mask) == params.new_base + { + let old = params.old_base as u32; + self.bars[i].addr = old; + self.registers[BAR0_REG + i] = + (self.registers[BAR0_REG + i] & !mask) | (old & mask); + return; + } + } + } + } + } } impl Pausable for PciConfiguration {} diff --git a/pci/src/device.rs b/pci/src/device.rs index 29c89b8c42..482e15e404 100644 --- a/pci/src/device.rs +++ b/pci/src/device.rs @@ -93,6 +93,10 @@ pub trait PciDevice: Send { fn move_bar(&mut self, _old_base: u64, _new_base: u64) -> result::Result<(), io::Error> { Ok(()) } + /// Restore BAR address in config space after a failed move_bar. + /// This rolls back the address update made by detect_bar_reprogramming() + /// so that the config register stays consistent with the MMIO bus mapping. + fn restore_bar_addr(&mut self, _params: &BarReprogrammingParams) {} /// Provides a mutable reference to the Any trait. This is useful to let /// the caller have access to the underlying type behind the trait. fn as_any_mut(&mut self) -> &mut dyn Any; diff --git a/pci/src/vfio.rs b/pci/src/vfio.rs index b49ed51989..e0c9110a86 100644 --- a/pci/src/vfio.rs +++ b/pci/src/vfio.rs @@ -2034,6 +2034,10 @@ iova 0x{:x}, size 0x{:x}: {}, ", Ok(()) } + fn restore_bar_addr(&mut self, params: &BarReprogrammingParams) { + self.common.configuration.restore_bar_addr(params); + } + fn as_any_mut(&mut self) -> &mut dyn Any { self } diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index 456047d42d..27c7dc0405 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -414,6 +414,10 @@ impl PciDevice for VfioUserPciDevice { .free_bars(allocator, mmio32_allocator, mmio64_allocator) } + fn restore_bar_addr(&mut self, params: &BarReprogrammingParams) { + self.common.configuration.restore_bar_addr(params); + } + fn as_any_mut(&mut self) -> &mut dyn Any { self } diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 36975e3f7f..54a29caa5d 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -1133,6 +1133,10 @@ impl PciDevice for VirtioPciDevice { Ok(()) } + fn restore_bar_addr(&mut self, params: &BarReprogrammingParams) { + self.configuration.restore_bar_addr(params); + } + fn read_bar(&mut self, _base: u64, offset: u64, data: &mut [u8]) { match offset { o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => self.common_config.read( From 0a4be0c1c7368b21d66d26b990492a3736a28f4c Mon Sep 17 00:00:00 2001 From: CMGS Date: Mon, 13 Apr 2026 17:23:15 +0800 Subject: [PATCH 1118/1893] 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 --- vmm/src/device_manager.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 0ae8526448..77ef0cc423 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1140,6 +1140,8 @@ pub struct DeviceManager { ivshmem_device: Option>>, } +/// Create per-PCI-segment MMIO allocators over the range `[start, end]`. +/// Both `start` and `end` are inclusive addresses. fn create_mmio_allocators( start: u64, end: u64, @@ -1157,7 +1159,15 @@ fn create_mmio_allocators( for segment_id in 0..num_pci_segments as u64 { let weight = weights[segment_id as usize] as u64; let mmio_start = start + i * pci_segment_mmio_size; - let mmio_size = pci_segment_mmio_size * weight; + let is_last = segment_id == num_pci_segments as u64 - 1; + // Give the last segment all remaining space so no addresses + // near the top of the physical address space are lost to + // alignment truncation. + let mmio_size = if is_last { + end - mmio_start + 1 + } else { + pci_segment_mmio_size * weight + }; let allocator = Arc::new(Mutex::new( AddressAllocator::new(GuestAddress(mmio_start), mmio_size).unwrap(), )); @@ -1218,7 +1228,8 @@ impl DeviceManager { } let start_of_mmio32_area = layout::MEM_32BIT_DEVICES_START.0; - let end_of_mmio32_area = layout::MEM_32BIT_DEVICES_START.0 + layout::MEM_32BIT_DEVICES_SIZE; + let end_of_mmio32_area = + layout::MEM_32BIT_DEVICES_START.0 + layout::MEM_32BIT_DEVICES_SIZE - 1; let pci_mmio32_allocators = create_mmio_allocators( start_of_mmio32_area, end_of_mmio32_area, @@ -5739,7 +5750,7 @@ mod unit_tests { #[test] fn test_create_mmio_allocators() { - let res = create_mmio_allocators(0x100000, 0x400000, 1, &[1], 4 << 10); + let res = create_mmio_allocators(0x100000, 0x3fffff, 1, &[1], 4 << 10); assert_eq!(res.len(), 1); assert_eq!( res[0].lock().unwrap().base(), @@ -5750,7 +5761,7 @@ mod unit_tests { vm_memory::GuestAddress(0x3fffff) ); - let res = create_mmio_allocators(0x100000, 0x400000, 2, &[1, 1], 4 << 10); + let res = create_mmio_allocators(0x100000, 0x3fffff, 2, &[1, 1], 4 << 10); assert_eq!(res.len(), 2); assert_eq!( res[0].lock().unwrap().base(), @@ -5769,7 +5780,7 @@ mod unit_tests { vm_memory::GuestAddress(0x3fffff) ); - let res = create_mmio_allocators(0x100000, 0x400000, 2, &[2, 1], 4 << 10); + let res = create_mmio_allocators(0x100000, 0x3fffff, 2, &[2, 1], 4 << 10); assert_eq!(res.len(), 2); assert_eq!( res[0].lock().unwrap().base(), From ff329126150930a88212d4c31607265d06ae619b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:55:43 +0000 Subject: [PATCH 1119/1893] 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] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 73b385811b..776cd8eb42 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -167,4 +167,4 @@ jobs: steps: - uses: actions/checkout@v6 # Executes "typos ." - - uses: crate-ci/typos@v1.45.0 + - uses: crate-ci/typos@v1.45.1 From c77094bd5408450d2bef79f9ccdb761c8e06b5ad Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 10 Apr 2026 13:36:25 +0200 Subject: [PATCH 1120/1893] 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 --- test_infra/src/lib.rs | 108 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 47d022cd6e..c66d40ec28 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -15,7 +15,7 @@ use std::os::unix::io::{AsRawFd, FromRawFd}; use std::path::{Path, PathBuf}; use std::process::{Child, Command, ExitStatus, Output, Stdio}; use std::str::FromStr; -use std::time::Duration; +use std::time::{Duration, Instant}; use std::{env, fmt, fs, io, thread}; use rand::Rng; @@ -57,6 +57,44 @@ pub enum Error { WaitTimeout(#[source] WaitTimeoutError), } +/// Polls a boolean condition until it becomes true or the timeout expires. +pub fn wait_until(timeout: Duration, mut condition: F) -> bool +where + F: FnMut() -> bool, +{ + const INTERVAL: Duration = Duration::from_millis(50); + let start = Instant::now(); + + loop { + if condition() { + return true; + } + + if start.elapsed() >= timeout { + return false; + } + + thread::sleep(INTERVAL); + } +} + +/// Retries an operation until it returns `Ok` or the timeout expires. +pub fn wait_until_succeeds(timeout: Duration, mut operation: F) -> Result +where + F: FnMut() -> Result, +{ + const INTERVAL: Duration = Duration::from_millis(50); + let start = Instant::now(); + + loop { + match operation() { + Ok(result) => return Ok(result), + Err(err) if start.elapsed() >= timeout => return Err(err), + Err(_) => thread::sleep(INTERVAL), + } + } +} + pub struct GuestNetworkConfig { pub guest_ip0: String, pub host_ip0: String, @@ -618,6 +656,25 @@ pub enum SshCommandError { WaitEof(#[source] ssh2::Error), } +#[derive(Error, Debug)] +pub enum WaitForSshError { + #[error("timed out after {timeout:?} waiting for ssh command {command:?} on {ip}: {source}")] + Timeout { + command: String, + ip: String, + timeout: Duration, + #[source] + source: SshCommandError, + }, +} + +fn default_guest_auth() -> PasswordAuth { + PasswordAuth { + username: String::from("cloud"), + password: String::from("cloud123"), + } +} + fn scp_to_guest_with_auth( path: &Path, remote_path: &Path, @@ -791,6 +848,24 @@ pub fn ssh_command_ip( ) } +/// Waits until SSH to the guest becomes available. +pub fn wait_for_ssh( + command: &str, + auth: &PasswordAuth, + ip: &str, + timeout: Duration, +) -> Result { + wait_until_succeeds(timeout, || { + ssh_command_ip_with_auth(command, auth, ip, 1, 1) + }) + .map_err(|source| WaitForSshError::Timeout { + command: command.to_string(), + ip: ip.to_string(), + timeout, + source, + }) +} + pub fn exec_host_command_with_retries(command: &str, retries: u32, interval: Duration) -> bool { for _ in 0..retries { let s = exec_host_command_output(command).status; @@ -1093,6 +1168,37 @@ impl Guest { ) } + /// Waits until SSH to the guest becomes available using the + /// [default guest authentication] and the default guest IP. + /// + /// [default guest authentication]: default_guest_auth + pub fn wait_for_ssh(&self, timeout: Duration) -> Result<(), WaitForSshError> { + wait_for_ssh( + "true", + &default_guest_auth(), + &self.network.guest_ip0, + timeout, + ) + .map(|_| ()) + } + + /// Waits until the provided command succeeds via SSH on the guest using the + /// [default guest authentication] and the default guest IP. + /// + /// [default guest authentication]: default_guest_auth + pub fn wait_for_ssh_command( + &self, + command: &str, + timeout: Duration, + ) -> Result { + wait_for_ssh( + command, + &default_guest_auth(), + &self.network.guest_ip0, + timeout, + ) + } + pub fn api_create_body(&self) -> String { let mut body = serde_json::json!({ "cpus": { From a3b4687caa58fbf5b7f469afffd3e584ef4b4ba7 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 13 Apr 2026 17:10:16 +0200 Subject: [PATCH 1121/1893] 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 --- cloud-hypervisor/tests/integration.rs | 4 +- test_infra/src/lib.rs | 98 +++++++++++++++------------ 2 files changed, 58 insertions(+), 44 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 22d32852e6..f16ea003bc 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7286,7 +7286,7 @@ mod windows { } fn ssh_cmd(&self, cmd: &str) -> String { - ssh_command_ip_with_auth( + ssh_command_ip_with_auth_retry( cmd, &self.auth, &self.guest.network.guest_ip0, @@ -7477,7 +7477,7 @@ mod windows { // The timeout increase by n*1+n*2+n*3+..., therefore the initial // interval must be small. let tmo_int = 2; - let out = ssh_command_ip_with_auth( + let out = ssh_command_ip_with_auth_retry( cmd, &self.auth, &self.guest.network.guest_ip0, diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index c66d40ec28..21bf8f3c11 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -764,50 +764,63 @@ pub fn scp_to_guest( ) } +/// Executes a command on a remote host via SSH using password authentication. +/// Returns the stdout output on success, or an [`SshCommandError`] on any +/// connection, authentication, or execution failure. pub fn ssh_command_ip_with_auth( command: &str, auth: &PasswordAuth, ip: &str, - retries: u8, - timeout: u8, ) -> Result { let mut s = String::new(); + let tcp = TcpStream::connect(format!("{ip}:22")).map_err(SshCommandError::Connection)?; + let mut sess = Session::new().unwrap(); + sess.set_tcp_stream(tcp); + sess.handshake().map_err(SshCommandError::Handshake)?; + sess.userauth_password(&auth.username, &auth.password) + .map_err(SshCommandError::Authentication)?; + assert!(sess.authenticated()); + let mut channel = sess + .channel_session() + .map_err(SshCommandError::ChannelSession)?; + channel.exec(command).map_err(SshCommandError::Command)?; + // Intentionally ignore these results here as their failure + // does not precipitate a repeat + let _ = channel.read_to_string(&mut s); + let _ = channel.close(); + let _ = channel.wait_close(); + let status = channel.exit_status().map_err(SshCommandError::ExitStatus)?; + if status != 0 { + Err(SshCommandError::NonZeroExitStatus(status)) + } else { + Ok(s) + } +} +/// Executes a command on a remote host via SSH using password authentication, +/// retrying on failure with linear backoff. +/// +/// Delegates each attempt to [`ssh_command_ip_with_auth`]. After the +/// *n*-th consecutive failure the function sleeps for `timeout_s * n` seconds +/// before the next attempt. Once `retries` attempts are exhausted the command +/// output and error are printed to stderr and the last error is returned. +/// +/// Note that `timeout_s` is not a per-attempt deadline — individual connection +/// and I/O operations may block for as long as the OS or SSH layer allows. +// TODO since we have we probably want to migrate every single invocation to a +// more graceful combination of wait_until() and ssh_command_ip_with_auth(). +pub fn ssh_command_ip_with_auth_retry( + command: &str, + auth: &PasswordAuth, + ip: &str, + retries: u8, + // Base unit for the inter-retry sleep duration, in seconds. + timeout_s: u8, +) -> Result { let mut counter = 0; loop { - let mut closure = || -> Result<(), SshCommandError> { - let tcp = - TcpStream::connect(format!("{ip}:22")).map_err(SshCommandError::Connection)?; - let mut sess = Session::new().unwrap(); - sess.set_tcp_stream(tcp); - sess.handshake().map_err(SshCommandError::Handshake)?; - - sess.userauth_password(&auth.username, &auth.password) - .map_err(SshCommandError::Authentication)?; - assert!(sess.authenticated()); - - let mut channel = sess - .channel_session() - .map_err(SshCommandError::ChannelSession)?; - channel.exec(command).map_err(SshCommandError::Command)?; - - // Intentionally ignore these results here as their failure - // does not precipitate a repeat - let _ = channel.read_to_string(&mut s); - let _ = channel.close(); - let _ = channel.wait_close(); - - let status = channel.exit_status().map_err(SshCommandError::ExitStatus)?; - - if status != 0 { - Err(SshCommandError::NonZeroExitStatus(status)) - } else { - Ok(()) - } - }; - - match closure() { - Ok(_) => break, + match ssh_command_ip_with_auth(command, auth, ip) { + Ok(s) => return Ok(s), Err(e) => { counter += 1; if counter >= retries { @@ -816,27 +829,28 @@ pub fn ssh_command_ip_with_auth( command=\"{command}\"\n\ auth=\"{auth:#?}\"\n\ ip=\"{ip}\"\n\ - output=\"{s}\"\n\ error=\"{e:?}\"\n\ - \n==== End ssh command outout ====\n\n" + \n==== End ssh command output ====\n\n" ); - return Err(e); } } } - thread::sleep(std::time::Duration::new((timeout * counter).into(), 0)); + thread::sleep(std::time::Duration::new((timeout_s * counter).into(), 0)); } - Ok(s) } +/// Executes a command on a remote host via SSH using password authentication, +/// retrying on failure with linear backoff. +/// +/// Wrapper around [`ssh_command_ip_with_auth_retry`]. pub fn ssh_command_ip( command: &str, ip: &str, retries: u8, timeout: u8, ) -> Result { - ssh_command_ip_with_auth( + ssh_command_ip_with_auth_retry( command, &PasswordAuth { username: String::from("cloud"), @@ -856,7 +870,7 @@ pub fn wait_for_ssh( timeout: Duration, ) -> Result { wait_until_succeeds(timeout, || { - ssh_command_ip_with_auth(command, auth, ip, 1, 1) + ssh_command_ip_with_auth_retry(command, auth, ip, 1, 1) }) .map_err(|source| WaitForSshError::Timeout { command: command.to_string(), From b26488b1bff6aff39c0fdaa9f63d803aee23b6b3 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 13 Apr 2026 18:03:18 +0200 Subject: [PATCH 1122/1893] 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 --- test_infra/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 21bf8f3c11..f9f45d36d9 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -771,11 +771,15 @@ pub fn ssh_command_ip_with_auth( command: &str, auth: &PasswordAuth, ip: &str, + timeout: Option, ) -> Result { let mut s = String::new(); let tcp = TcpStream::connect(format!("{ip}:22")).map_err(SshCommandError::Connection)?; let mut sess = Session::new().unwrap(); sess.set_tcp_stream(tcp); + if let Some(timeout) = timeout { + sess.set_timeout(timeout.as_millis() as u32); + } sess.handshake().map_err(SshCommandError::Handshake)?; sess.userauth_password(&auth.username, &auth.password) .map_err(SshCommandError::Authentication)?; @@ -819,7 +823,7 @@ pub fn ssh_command_ip_with_auth_retry( ) -> Result { let mut counter = 0; loop { - match ssh_command_ip_with_auth(command, auth, ip) { + match ssh_command_ip_with_auth(command, auth, ip, None) { Ok(s) => return Ok(s), Err(e) => { counter += 1; @@ -870,7 +874,7 @@ pub fn wait_for_ssh( timeout: Duration, ) -> Result { wait_until_succeeds(timeout, || { - ssh_command_ip_with_auth_retry(command, auth, ip, 1, 1) + ssh_command_ip_with_auth(command, auth, ip, Some(timeout)) }) .map_err(|source| WaitForSshError::Timeout { command: command.to_string(), From 2c395d4ae8c1fd866708a6265302d849db1e8a67 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 10 Apr 2026 13:36:34 +0200 Subject: [PATCH 1123/1893] 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 --- cloud-hypervisor/tests/common/utils.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs index 5064e1970e..f7cc1ea181 100644 --- a/cloud-hypervisor/tests/common/utils.rs +++ b/cloud-hypervisor/tests/common/utils.rs @@ -498,6 +498,9 @@ fn parse_event_file(event_file: &str) -> Vec { // Return true if all events from the input 'expected_events' are matched sequentially // with events from the 'event_file' pub(crate) fn check_sequential_events(expected_events: &[&MetaEvent], event_file: &str) -> bool { + if !Path::new(event_file).exists() { + return false; + } let json_events = parse_event_file(event_file); let len = expected_events.len(); let mut idx = 0; @@ -529,8 +532,13 @@ pub(crate) fn check_sequential_events_exact( expected_events: &[&MetaEvent], event_file: &str, ) -> bool { + if !Path::new(event_file).exists() { + return false; + } let json_events = parse_event_file(event_file); - assert!(expected_events.len() <= json_events.len()); + if expected_events.len() > json_events.len() { + return false; + } let json_events = &json_events[..expected_events.len()]; for (idx, e) in json_events.iter().enumerate() { @@ -551,8 +559,13 @@ pub(crate) fn check_sequential_events_exact( // Return true if events from the input 'latest_events' are matched exactly // with the most recent events from the 'event_file' pub(crate) fn check_latest_events_exact(latest_events: &[&MetaEvent], event_file: &str) -> bool { + if !Path::new(event_file).exists() { + return false; + } let json_events = parse_event_file(event_file); - assert!(latest_events.len() <= json_events.len()); + if latest_events.len() > json_events.len() { + return false; + } let json_events = &json_events[(json_events.len() - latest_events.len())..]; for (idx, e) in json_events.iter().enumerate() { From 47024e73ceac3ff6f6fe931bf59ee7d497f6d61d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 10 Apr 2026 13:37:45 +0200 Subject: [PATCH 1124/1893] 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 --- cloud-hypervisor/tests/integration.rs | 295 +++++++++++++++----------- test_infra/src/lib.rs | 28 ++- 2 files changed, 194 insertions(+), 129 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index f16ea003bc..9b0a0a952e 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -282,17 +282,25 @@ mod common_parallel { guest.enable_memory_hotplug(); resize_zone_command(&api_socket, "mem0", "3G"); - thread::sleep(std::time::Duration::new(5, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 4_800_000); + assert!(wait_until(Duration::from_secs(5), || guest + .get_total_memory() + .unwrap_or_default() + > 4_800_000)); resize_zone_command(&api_socket, "mem2", "3G"); - thread::sleep(std::time::Duration::new(5, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 6_720_000); + assert!(wait_until(Duration::from_secs(5), || guest + .get_total_memory() + .unwrap_or_default() + > 6_720_000)); resize_zone_command(&api_socket, "mem0", "2G"); - thread::sleep(std::time::Duration::new(5, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); + assert!(wait_until(Duration::from_secs(5), || guest + .get_total_memory() + .unwrap_or_default() + > 5_760_000)); resize_zone_command(&api_socket, "mem2", "2G"); - thread::sleep(std::time::Duration::new(5, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 4_800_000); + assert!(wait_until(Duration::from_secs(5), || guest + .get_total_memory() + .unwrap_or_default() + > 4_800_000)); guest.reboot_linux(0); @@ -302,11 +310,15 @@ mod common_parallel { // Check if we can still resize down to the initial 'boot'size resize_zone_command(&api_socket, "mem0", "1G"); - thread::sleep(std::time::Duration::new(5, 0)); - assert!(guest.get_total_memory().unwrap_or_default() < 4_800_000); + assert!(wait_until(Duration::from_secs(5), || guest + .get_total_memory() + .unwrap_or_default() + < 4_800_000)); resize_zone_command(&api_socket, "mem2", "1G"); - thread::sleep(std::time::Duration::new(5, 0)); - assert!(guest.get_total_memory().unwrap_or_default() < 3_840_000); + assert!(wait_until(Duration::from_secs(5), || guest + .get_total_memory() + .unwrap_or_default() + < 3_840_000)); }); kill_child(&mut child); @@ -2332,11 +2344,28 @@ mod common_parallel { .spawn() .unwrap(); - thread::sleep(std::time::Duration::new(30, 0)); + guest.wait_for_ssh(Duration::from_secs(30)).unwrap(); let r = std::panic::catch_unwind(|| { guest.ssh_command_l1("sudo systemctl start vfio").unwrap(); - thread::sleep(std::time::Duration::new(120, 0)); + let auth = PasswordAuth { + username: String::from("cloud"), + password: String::from("cloud123"), + }; + wait_for_ssh( + "true", + &auth, + &guest.network.l2_guest_ip1, + Duration::from_secs(120), + ) + .unwrap(); + wait_for_ssh( + "true", + &auth, + &guest.network.l2_guest_ip2, + Duration::from_secs(120), + ) + .unwrap(); // We booted our cloud hypervisor L2 guest with a "VFIOTAG" tag // added to its kernel command line. @@ -2395,7 +2424,18 @@ mod common_parallel { 1 )); - thread::sleep(std::time::Duration::new(10, 0)); + wait_for_ssh( + "true", + &auth, + &guest.network.l2_guest_ip3, + Duration::from_secs(10), + ) + .unwrap(); + assert!(wait_until(Duration::from_secs(10), || { + guest + .ssh_command_l2_1("ls /sys/bus/pci/devices") + .is_ok_and(|output| check_lines_count(output.trim(), 9)) + })); // Let's also verify from the third virtio-net device passed to // the L2 VM. This third device has been hotplugged through the L2 @@ -2427,7 +2467,11 @@ mod common_parallel { remove-device vfio123", ) .unwrap(); - thread::sleep(std::time::Duration::new(10, 0)); + assert!(wait_until(Duration::from_secs(10), || { + guest + .ssh_command_l2_1("ls /sys/bus/pci/devices") + .is_ok_and(|output| check_lines_count(output.trim(), 8)) + })); // Check the amount of PCI devices appearing in L2 VM is back down // to 8 devices. @@ -2596,11 +2640,9 @@ mod common_parallel { guest .ssh_command("echo 1 | sudo tee /sys/bus/cpu/devices/cpu3/online") .unwrap(); - thread::sleep(std::time::Duration::new(10, 0)); - assert_eq!( - guest.get_cpu_count().unwrap_or_default(), - u32::from(desired_vcpus) - ); + assert!(wait_until(Duration::from_secs(10), || { + guest.get_cpu_count().unwrap_or_default() == u32::from(desired_vcpus) + })); guest.reboot_linux(0); @@ -2613,11 +2655,9 @@ mod common_parallel { let desired_vcpus = 2; resize_command(&api_socket, Some(desired_vcpus), None, None, None); - thread::sleep(std::time::Duration::new(10, 0)); - assert_eq!( - guest.get_cpu_count().unwrap_or_default(), - u32::from(desired_vcpus) - ); + assert!(wait_until(Duration::from_secs(10), || { + guest.get_cpu_count().unwrap_or_default() == u32::from(desired_vcpus) + })); // Resize the VM back up to 4 let desired_vcpus = 4; @@ -2629,11 +2669,9 @@ mod common_parallel { guest .ssh_command("echo 1 | sudo tee /sys/bus/cpu/devices/cpu3/online") .unwrap(); - thread::sleep(std::time::Duration::new(10, 0)); - assert_eq!( - guest.get_cpu_count().unwrap_or_default(), - u32::from(desired_vcpus) - ); + assert!(wait_until(Duration::from_secs(10), || { + guest.get_cpu_count().unwrap_or_default() == u32::from(desired_vcpus) + })); }); kill_child(&mut child); @@ -2681,16 +2719,18 @@ mod common_parallel { let desired_ram = 1024 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(10, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + assert!(wait_until(Duration::from_secs(10), || { + guest.get_total_memory().unwrap_or_default() > 960_000 + })); // Use balloon to remove RAM from the VM let desired_balloon = 512 << 20; resize_command(&api_socket, None, None, Some(desired_balloon), None); - thread::sleep(std::time::Duration::new(10, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); - assert!(guest.get_total_memory().unwrap_or_default() < 960_000); + assert!(wait_until(Duration::from_secs(10), || { + let total_memory = guest.get_total_memory().unwrap_or_default(); + total_memory > 480_000 && total_memory < 960_000 + })); guest.reboot_linux(0); @@ -2700,9 +2740,9 @@ mod common_parallel { let desired_balloon = 0; resize_command(&api_socket, None, None, Some(desired_balloon), None); - thread::sleep(std::time::Duration::new(10, 0)); - - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + assert!(wait_until(Duration::from_secs(10), || { + guest.get_total_memory().unwrap_or_default() > 960_000 + })); guest.enable_memory_hotplug(); @@ -2710,8 +2750,9 @@ mod common_parallel { let desired_ram = 2048 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(10, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 1_920_000); + assert!(wait_until(Duration::from_secs(10), || { + guest.get_total_memory().unwrap_or_default() > 1_920_000 + })); // Remove RAM to the VM (only applies after reboot) let desired_ram = 1024 << 20; @@ -2764,23 +2805,26 @@ mod common_parallel { let desired_ram = 1024 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(10, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + assert!(wait_until(Duration::from_secs(10), || { + guest.get_total_memory().unwrap_or_default() > 960_000 + })); // Add RAM to the VM let desired_ram = 2048 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(10, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 1_920_000); + assert!(wait_until(Duration::from_secs(10), || { + guest.get_total_memory().unwrap_or_default() > 1_920_000 + })); // Remove RAM from the VM let desired_ram = 1024 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(10, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); - assert!(guest.get_total_memory().unwrap_or_default() < 1_920_000); + assert!(wait_until(Duration::from_secs(10), || { + let total_memory = guest.get_total_memory().unwrap_or_default(); + total_memory > 960_000 && total_memory < 1_920_000 + })); guest.reboot_linux(0); @@ -2791,9 +2835,10 @@ mod common_parallel { // Check we can still resize to 512MiB let desired_ram = 512 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(10, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); - assert!(guest.get_total_memory().unwrap_or_default() < 960_000); + assert!(wait_until(Duration::from_secs(10), || { + let total_memory = guest.get_total_memory().unwrap_or_default(); + total_memory > 480_000 && total_memory < 960_000 + })); }); kill_child(&mut child); @@ -2849,11 +2894,9 @@ mod common_parallel { guest .ssh_command("echo 1 | sudo tee /sys/bus/cpu/devices/cpu3/online") .unwrap(); - thread::sleep(std::time::Duration::new(10, 0)); - assert_eq!( - guest.get_cpu_count().unwrap_or_default(), - u32::from(desired_vcpus) - ); + assert!(wait_until(Duration::from_secs(10), || { + guest.get_cpu_count().unwrap_or_default() == u32::from(desired_vcpus) + })); assert!(guest.get_total_memory().unwrap_or_default() > 960_000); }); @@ -4779,18 +4822,16 @@ mod common_parallel { .unwrap(); }); - // Wait for 50 seconds to make sure the stress command is consuming - // the expected amount of memory. - thread::sleep(std::time::Duration::new(50, 0)); + // Wait for guest memory consumption to reach the expected level. + assert!(wait_until(Duration::from_secs(60), || process_rss_kib(pid) >= 2097152)); let rss = process_rss_kib(pid); println!("RSS {rss} >= 2097152"); assert!(rss >= 2097152); - // Wait for an extra minute to make sure the stress command has - // completed and that the guest reported the free pages to the VMM - // through the virtio-balloon device. We expect the RSS to be under - // 2GiB. - thread::sleep(std::time::Duration::new(60, 0)); + // Wait for stress to complete and free-page reporting to shrink RSS again. + assert!(wait_until(Duration::from_secs(120), || process_rss_kib( + pid + ) < 2097152)); let rss = process_rss_kib(pid); println!("RSS {rss} < 2097152"); assert!(rss < 2097152); @@ -4914,18 +4955,12 @@ mod common_parallel { assert!(remote_command(&api_socket, "remove-device", Some("test0"))); - thread::sleep(std::time::Duration::new(20, 0)); - - // Check device has gone away - assert_eq!( + // Wait for the pmem device to disappear from lsblk. + assert!(wait_until(Duration::from_secs(20), || { guest .ssh_command("lsblk | grep -c pmem0.*128M || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); + .is_ok_and(|output| output.trim().parse::().unwrap_or(1) == 0) + })); guest.reboot_linux(1); @@ -5174,8 +5209,12 @@ mod common_parallel { .unwrap(); }); - // Wait for the server to be listening - thread::sleep(std::time::Duration::new(5, 0)); + guest1 + .wait_for_ssh_command( + "ss -ltnH | awk '{print $4}' | grep -q ':12345$'", + Duration::from_secs(20), + ) + .unwrap(); // Check the connection fails this time guest2.ssh_command("nc -vz 172.100.0.1 12345").unwrap_err(); @@ -5196,8 +5235,10 @@ mod common_parallel { Some(format!("file://{snapshot_dir}").as_str()), )); - // Wait to make sure the snapshot is completed - thread::sleep(std::time::Duration::new(10, 0)); + // Wait for the source VM snapshot artifacts to be ready. + assert!(wait_until(Duration::from_secs(10), || { + std::path::Path::new(&snapshot_dir).exists() + })); }); // Shutdown the source VM @@ -5224,12 +5265,17 @@ mod common_parallel { .spawn() .unwrap(); - // Wait for the VM to be restored - thread::sleep(std::time::Duration::new(10, 0)); + // Wait for the restored VM to accept SSH again after resume. let r = std::panic::catch_unwind(|| { // Resume the VM + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); assert!(remote_command(&api_socket_restored, "resume", None)); + guest2.wait_for_ssh(Duration::from_secs(30)).unwrap(); // Spawn a new netcat listener in the first VM let guest_ip = guest1.network.guest_ip0.clone(); @@ -5243,8 +5289,12 @@ mod common_parallel { .unwrap(); }); - // Wait for the server to be listening - thread::sleep(std::time::Duration::new(5, 0)); + guest1 + .wait_for_ssh_command( + "ss -ltnH | awk '{print $4}' | grep -q ':12345$'", + Duration::from_secs(20), + ) + .unwrap(); // And check the connection is still functional after restore guest2.ssh_command("nc -vz 172.100.0.1 12345").unwrap(); @@ -5370,18 +5420,14 @@ mod common_parallel { .contains("{\"id\":\"vfio_user0\",\"bdf\":\"0000:00:05.0\"}") ); - thread::sleep(std::time::Duration::new(10, 0)); - // Check both if /dev/nvme exists and if the block size is 128M. - assert_eq!( + assert!(wait_until(Duration::from_secs(10), || { guest .ssh_command("lsblk | grep nvme0n1 | grep -c 128M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); + .ok() + .and_then(|output| output.trim().parse::().ok()) + == Some(1) + })); // Check changes persist after reboot assert_eq!( @@ -5528,7 +5574,9 @@ mod common_parallel { // Start swtpm daemon let mut swtpm_child = swtpm_command.spawn().unwrap(); - thread::sleep(std::time::Duration::new(10, 0)); + assert!(wait_until(Duration::from_secs(10), || { + std::path::Path::new(&swtpm_socket_path).exists() + })); let mut child = guest_cmd.spawn().unwrap(); let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); @@ -5634,17 +5682,13 @@ mod common_parallel { assert!(remote_command(&api_socket, "nmi", None)); - // Wait a while for guest - thread::sleep(std::time::Duration::new(3, 0)); - let expected_sequential_events = [&MetaEvent { event: "panic".to_string(), device_id: None, }]; - assert!(check_latest_events_exact( - &expected_sequential_events, - &event_path - )); + assert!(wait_until(Duration::from_secs(3), || { + check_latest_events_exact(&expected_sequential_events, &event_path) + })); }); kill_child(&mut child); @@ -8296,11 +8340,13 @@ mod vfio { // Add RAM to the VM let desired_ram = 6 << 30; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(30, 0)); + assert!(wait_until(Duration::from_secs(5), || { + guest.get_total_memory().unwrap_or_default() > 5_760_000 + })); assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); // Check the VFIO device works when RAM is increased to 6GiB - guest.check_nvidia_gpu(); + assert!(guest.check_nvidia_gpu()); }); let _ = child.kill(); @@ -8361,10 +8407,8 @@ mod vfio { .contains("{\"id\":\"vfio0\",\"bdf\":\"0000:00:06.0\"}") ); - thread::sleep(std::time::Duration::new(10, 0)); - // Check the VFIO device works after hotplug - guest.check_nvidia_gpu(); + assert!(wait_until(Duration::from_secs(10), || guest.check_nvidia_gpu())); }); let _ = child.kill(); @@ -8408,12 +8452,12 @@ mod vfio { guest.wait_vm_boot().unwrap(); // Check the VFIO device works after boot - guest.check_nvidia_gpu(); + assert!(guest.check_nvidia_gpu()); guest.reboot_linux(0); // Check the VFIO device works after reboot - guest.check_nvidia_gpu(); + assert!(guest.check_nvidia_gpu()); }); let _ = child.kill(); @@ -8841,7 +8885,9 @@ mod live_migration { "remove-device", Some(net_id), )); - thread::sleep(std::time::Duration::new(10, 0)); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); // Plug the virtio-net device again assert!(remote_command( @@ -8849,7 +8895,7 @@ mod live_migration { "add-net", Some(net_params.as_str()), )); - thread::sleep(std::time::Duration::new(10, 0)); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); } // Start the live-migration @@ -8993,11 +9039,16 @@ mod live_migration { assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); // Increase the guest RAM resize_command(&src_api_socket, None, Some(6 << 30), None, None); - thread::sleep(std::time::Duration::new(5, 0)); + assert!(wait_until(Duration::from_secs(30), || { + guest.get_total_memory().unwrap_or_default() > 5_760_000 + })); assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); // Use balloon to remove RAM from the VM resize_command(&src_api_socket, None, None, Some(1 << 30), None); - thread::sleep(std::time::Duration::new(5, 0)); + assert!(wait_until(Duration::from_secs(5), || { + let total_memory = guest.get_total_memory().unwrap_or_default(); + total_memory > 4_800_000 && total_memory < 5_760_000 + })); let total_memory = guest.get_total_memory().unwrap_or_default(); assert!(total_memory > 4_800_000); assert!(total_memory < 5_760_000); @@ -9015,7 +9066,9 @@ mod live_migration { "remove-device", Some(net_id), )); - thread::sleep(std::time::Duration::new(10, 0)); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); // Plug the virtio-net device again assert!(remote_command( @@ -9023,7 +9076,7 @@ mod live_migration { "add-net", Some(net_params.as_str()), )); - thread::sleep(std::time::Duration::new(10, 0)); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); } // Start the live-migration @@ -9233,7 +9286,9 @@ mod live_migration { "remove-device", Some(net_id), )); - thread::sleep(std::time::Duration::new(10, 0)); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); // Plug the virtio-net device again assert!(remote_command( @@ -9241,7 +9296,7 @@ mod live_migration { "add-net", Some(net_params.as_str()), )); - thread::sleep(std::time::Duration::new(10, 0)); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); } // Start the live-migration @@ -9429,7 +9484,9 @@ mod live_migration { "remove-device", Some(net_id), )); - thread::sleep(std::time::Duration::new(10, 0)); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); // Plug the virtio-net device again assert!(remote_command( @@ -9437,7 +9494,7 @@ mod live_migration { "add-net", Some(net_params.as_str()), )); - thread::sleep(std::time::Duration::new(10, 0)); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); } // Enable watchdog and ensure its functional @@ -9938,14 +9995,16 @@ mod live_migration { "remove-device", Some(net_id), )); - thread::sleep(Duration::new(10, 0)); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); // Re-add the virtio-net device assert!(remote_command( &src_api_socket, "add-net", Some(net_params.as_str()), )); - thread::sleep(Duration::new(10, 0)); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); } // Start TCP live migration assert!( diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index f9f45d36d9..ba10cd5b51 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1439,19 +1439,25 @@ impl Guest { } #[cfg(target_arch = "x86_64")] - pub fn check_nvidia_gpu(&self) { + pub fn check_nvidia_gpu(&self) -> bool { let output = self.ssh_command("nvidia-smi").unwrap(); - if !output.contains("NVIDIA L40S") { - let dmesg = self - .ssh_command("sudo dmesg") - .unwrap_or_else(|e| format!("Failed to get dmesg: {e:?}")); - eprintln!( - "\n\n==== Guest dmesg (nvidia-smi check failed) ====\n\n\ - {dmesg}\n\ - \n==== End guest dmesg ====\n\n" - ); - panic!("nvidia-smi output did not contain 'NVIDIA L40S': {output}"); + + if output.contains("NVIDIA L40S") { + return true; } + + let dmesg = self + .ssh_command("sudo dmesg") + .unwrap_or_else(|e| format!("Failed to get dmesg: {e:?}")); + + eprintln!( + "\n\n==== Guest dmesg (nvidia-smi check failed) ====\n\n\ + {dmesg}\n\ + \n==== End guest dmesg ====\n\n" + ); + eprintln!("nvidia-smi output did not contain 'NVIDIA L40S': {output}"); + + false } pub fn reboot_linux(&self, current_reboot_count: u32) { From 44ac7c0ba084c50bbf32b1ef4abbf806671f8e1c Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 10 Apr 2026 13:38:19 +0200 Subject: [PATCH 1125/1893] 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 --- cloud-hypervisor/tests/integration.rs | 223 +++++++++++++------------- 1 file changed, 113 insertions(+), 110 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 9b0a0a952e..3371118216 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -6162,30 +6162,26 @@ mod ivshmem { .spawn() .unwrap(); - // Wait for the VM to be restored - thread::sleep(std::time::Duration::new(20, 0)); - let latest_events = [&MetaEvent { event: "restored".to_string(), device_id: None, }]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + // Wait for the restored event to show up in the monitor file. + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); // Remove the snapshot dir let _ = remove_dir_all(snapshot_dir.as_str()); let r = std::panic::catch_unwind(|| { // Resume the VM + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); assert!(remote_command(&api_socket_restored, "resume", None)); - // There is no way that we can ensure the 'write()' to the - // event file is completed when the 'resume' request is - // returned successfully, because the 'write()' was done - // asynchronously from a different thread of Cloud - // Hypervisor (e.g. the event-monitor thread). - thread::sleep(std::time::Duration::new(1, 0)); let latest_events = [ &MetaEvent { event: "resuming".to_string(), @@ -6196,10 +6192,9 @@ mod ivshmem { device_id: None, }, ]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); @@ -6259,9 +6254,10 @@ mod common_sequential { device_id: None, }, ]; - // See: #5938 - thread::sleep(std::time::Duration::new(1, 0)); - assert!(check_latest_events_exact(&latest_events, event_path)); + + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, event_path) + })); // Take a snapshot from the VM assert!(remote_command( @@ -6270,9 +6266,6 @@ mod common_sequential { Some(format!("file://{snapshot_dir}").as_str()), )); - // Wait to make sure the snapshot is completed - thread::sleep(std::time::Duration::new(10, 0)); - let latest_events = [ &MetaEvent { event: "snapshotting".to_string(), @@ -6283,9 +6276,10 @@ mod common_sequential { device_id: None, }, ]; - // See: #5938 - thread::sleep(std::time::Duration::new(1, 0)); - assert!(check_latest_events_exact(&latest_events, event_path)); + + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, event_path) + })); } // One thing to note about this test. The virtio-net device is heavily used @@ -6321,7 +6315,7 @@ mod common_sequential { "id={},tap=,mac={},ip={},mask=255.255.255.128", net_id, guest.network.guest_mac0, guest.network.host_ip0 ); - let mut mem_params = "size=2G"; + let mut mem_params = "size=1G"; if use_hotplug { mem_params = "size=2G,hotplug_method=virtio-mem,hotplug_size=32G"; @@ -6368,7 +6362,12 @@ mod common_sequential { // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 1_920_000); + let total_memory = guest.get_total_memory().unwrap_or_default(); + if use_hotplug { + assert!(total_memory > 1_900_000, "total memory: {total_memory}"); + } else { + assert!(total_memory > 900_000, "total memory: {total_memory}"); + } if use_hotplug { // Increase guest RAM with virtio-mem resize_command( @@ -6390,8 +6389,8 @@ mod common_sequential { ); thread::sleep(std::time::Duration::new(5, 0)); let total_memory = guest.get_total_memory().unwrap_or_default(); - assert!(total_memory > 4_800_000); - assert!(total_memory < 5_760_000); + assert!(total_memory > 4_800_000, "total_memory is {total_memory}"); + assert!(total_memory < 5_760_000, "total_memory is {total_memory}"); } // Check the guest virtio-devices, e.g. block, rng, vsock, console, and net guest.check_devices_common(Some(&socket), Some(&console_text), None); @@ -6415,9 +6414,9 @@ mod common_sequential { event: "device-removed".to_string(), device_id: Some(net_id.to_string()), }]; - // See: #5938 - thread::sleep(std::time::Duration::new(1, 0)); - assert!(check_latest_events_exact(&latest_events, &event_path)); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path) + })); // Plug the virtio-net device again assert!(remote_command( @@ -6467,8 +6466,6 @@ mod common_sequential { .spawn() .unwrap(); - // Wait for the VM to be restored - thread::sleep(std::time::Duration::new(20, 0)); let expected_events = [ &MetaEvent { event: "starting".to_string(), @@ -6487,10 +6484,9 @@ mod common_sequential { device_id: None, }, ]; - assert!(check_sequential_events( - &expected_events, - &event_path_restored - )); + assert!(wait_until(Duration::from_secs(30), || { + check_sequential_events(&expected_events, &event_path_restored) + })); if use_resume_option { let latest_events = [ &MetaEvent { @@ -6506,21 +6502,26 @@ mod common_sequential { device_id: None, }, ]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); } else { let latest_events = [&MetaEvent { event: "restored".to_string(), device_id: None, }]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); } + // Wait until the restored VM API is ready before issuing follow-up requests. + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); + // Remove the snapshot dir let _ = remove_dir_all(snapshot_dir.as_str()); @@ -6530,13 +6531,12 @@ mod common_sequential { thread::sleep(std::time::Duration::new(1, 0)); } else { // Resume the VM manually + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); assert!(remote_command(&api_socket_restored, "resume", None)); - // There is no way that we can ensure the 'write()' to the - // event file is completed when the 'resume' request is - // returned successfully, because the 'write()' was done - // asynchronously from a different thread of Cloud - // Hypervisor (e.g. the event-monitor thread). - thread::sleep(std::time::Duration::new(1, 0)); let latest_events = [ &MetaEvent { @@ -6548,18 +6548,17 @@ mod common_sequential { device_id: None, }, ]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); } // Perform same checks to validate VM has been properly restored assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); let total_memory = guest.get_total_memory().unwrap_or_default(); if use_hotplug { - assert!(total_memory > 4_800_000); - assert!(total_memory < 5_760_000); + assert!(total_memory > 4_800_000, "total_memory is {total_memory}"); + assert!(total_memory < 5_760_000, "total_memory is {total_memory}"); // Deflate balloon to restore entire RAM to the VM resize_command(&api_socket_restored, None, None, Some(0), None); thread::sleep(std::time::Duration::new(5, 0)); @@ -6568,10 +6567,10 @@ mod common_sequential { resize_command(&api_socket_restored, None, Some(5 << 30), None, None); thread::sleep(std::time::Duration::new(5, 0)); let total_memory = guest.get_total_memory().unwrap_or_default(); - assert!(total_memory > 4_800_000); - assert!(total_memory < 5_760_000); + assert!(total_memory > 4_800_000, "total_memory is {total_memory}"); + assert!(total_memory < 5_760_000, "total_memory is {total_memory}"); } else { - assert!(total_memory > 1_920_000); + assert!(total_memory > 900_000, "total memory: {total_memory}"); } guest.check_devices_common(Some(&socket), Some(&console_text), None); @@ -6699,20 +6698,23 @@ mod common_sequential { .spawn() .unwrap(); - thread::sleep(std::time::Duration::new(20, 0)); - let latest_events = [&MetaEvent { event: "restored".to_string(), device_id: None, }]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); let r = std::panic::catch_unwind(|| { + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); assert!(remote_command(&api_socket_restored, "resume", None)); - thread::sleep(std::time::Duration::new(1, 0)); + let latest_events = [ &MetaEvent { event: "resuming".to_string(), @@ -6723,10 +6725,9 @@ mod common_sequential { device_id: None, }, ]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); assert!(guest.get_total_memory().unwrap_or_default() > min_total_memory_kib); @@ -6897,7 +6898,9 @@ mod common_sequential { )); // Wait for the VM to be restored - thread::sleep(std::time::Duration::new(20, 0)); + assert!(wait_until(Duration::from_secs(20), || { + remote_command(&api_socket_restored, "info", None) + })); // close the fds as CH duplicates them before using for tap in taps.iter() { @@ -6922,31 +6925,30 @@ mod common_sequential { device_id: None, }, ]; - assert!(check_sequential_events( - &expected_events, - &event_path_restored - )); + // Wait for the restore event sequence to be recorded. + assert!(wait_until(Duration::from_secs(30), || { + check_sequential_events(&expected_events, &event_path_restored) + })); let latest_events = [&MetaEvent { event: "restored".to_string(), device_id: None, }]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); // Remove the snapshot dir let _ = remove_dir_all(snapshot_dir.as_str()); let r = std::panic::catch_unwind(|| { // Resume the VM + assert!(wait_until(Duration::from_secs(20), || remote_command( + &api_socket_restored, + "info", + None + ))); assert!(remote_command(&api_socket_restored, "resume", None)); - // There is no way that we can ensure the 'write()' to the - // event file is completed when the 'resume' request is - // returned successfully, because the 'write()' was done - // asynchronously from a different thread of Cloud - // Hypervisor (e.g. the event-monitor thread). - thread::sleep(std::time::Duration::new(1, 0)); + let latest_events = [ &MetaEvent { event: "resuming".to_string(), @@ -6957,10 +6959,9 @@ mod common_sequential { device_id: None, }, ]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); // Perform same checks to validate VM has been properly restored assert_eq!(guest.get_cpu_count().unwrap_or_default(), n_cpu); @@ -7063,30 +7064,26 @@ mod common_sequential { .spawn() .unwrap(); - // Wait for the VM to be restored - thread::sleep(std::time::Duration::new(20, 0)); - let latest_events = [&MetaEvent { event: "restored".to_string(), device_id: None, }]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + // Wait for the restored event to show up in the monitor file. + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); // Remove the snapshot dir let _ = remove_dir_all(snapshot_dir.as_str()); let r = std::panic::catch_unwind(|| { // Resume the VM + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); assert!(remote_command(&api_socket_restored, "resume", None)); - // There is no way that we can ensure the 'write()' to the - // event file is completed when the 'resume' request is - // returned successfully, because the 'write()' was done - // asynchronously from a different thread of Cloud - // Hypervisor (e.g. the event-monitor thread). - thread::sleep(std::time::Duration::new(1, 0)); let latest_events = [ &MetaEvent { event: "resuming".to_string(), @@ -7097,10 +7094,9 @@ mod common_sequential { device_id: None, }, ]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); @@ -7228,7 +7224,9 @@ mod common_sequential { .unwrap(); // Wait for the VM to be restored - thread::sleep(std::time::Duration::new(20, 0)); + assert!(wait_until(Duration::from_secs(30), || { + remote_command(&api_socket_restored, "info", None) + })); let latest_events = [&MetaEvent { event: "restored".to_string(), @@ -7244,6 +7242,11 @@ mod common_sequential { let r = std::panic::catch_unwind(|| { // Resume the VM + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); assert!(remote_command(&api_socket_restored, "resume", None)); thread::sleep(std::time::Duration::new(5, 0)); From cd92f65f7b8bcf91dc1e0e05d9571e7d5a73023d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 10 Apr 2026 13:39:53 +0200 Subject: [PATCH 1126/1893] 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 --- cloud-hypervisor/tests/integration.rs | 157 ++++++++++++++++---------- 1 file changed, 97 insertions(+), 60 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 3371118216..82b1076017 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7518,37 +7518,19 @@ mod windows { )) } - fn wait_for_boot(&self) -> bool { - let cmd = "dir /b c:\\ | find \"Windows\""; - let tmo_max = 180; - // The timeout increase by n*1+n*2+n*3+..., therefore the initial - // interval must be small. - let tmo_int = 2; - let out = ssh_command_ip_with_auth_retry( - cmd, + fn wait_for_boot(&self) -> Result<(), WaitForSshError> { + let out = wait_for_ssh( + "dir /b c:\\ | find \"Windows\"", &self.auth, &self.guest.network.guest_ip0, - { - let mut ret = 1; - let mut tmo_acc = 0; - loop { - tmo_acc += tmo_int * ret; - if tmo_acc >= tmo_max { - break; - } - ret += 1; - } - ret - }, - tmo_int, - ) - .unwrap(); + Duration::from_secs(180), + )?; - if "Windows" == out.trim() { - return true; + if out.trim() == "Windows" { + Ok(()) + } else { + panic!("Unexpected Windows boot probe output: {:?}", out.trim()); } - - false } } @@ -7617,7 +7599,7 @@ mod windows { let r = std::panic::catch_unwind(|| { // Wait to make sure Windows boots up - assert!(windows_guest.wait_for_boot()); + windows_guest.wait_for_boot().unwrap(); windows_guest.shutdown(); }); @@ -7682,7 +7664,7 @@ mod windows { let r = std::panic::catch_unwind(|| { // Wait to make sure Windows boots up - assert!(windows_guest.wait_for_boot()); + windows_guest.wait_for_boot().unwrap(); windows_guest.shutdown(); }); @@ -7733,7 +7715,7 @@ mod windows { let mut child_dnsmasq = windows_guest.run_dnsmasq(); // Wait to make sure Windows boots up - assert!(windows_guest.wait_for_boot()); + windows_guest.wait_for_boot().unwrap(); let snapshot_dir = temp_snapshot_dir_path(&tmp_dir); @@ -7747,8 +7729,11 @@ mod windows { Some(format!("file://{snapshot_dir}").as_str()), )); - // Wait to make sure the snapshot is completed - thread::sleep(std::time::Duration::new(30, 0)); + let snapshot_state_path = std::path::Path::new(&snapshot_dir).join("state.json"); + let snapshot_config_path = std::path::Path::new(&snapshot_dir).join("config.json"); + assert!(wait_until(Duration::from_secs(30), || { + snapshot_state_path.exists() && snapshot_config_path.exists() + })); let _ = child.kill(); child.wait().unwrap(); @@ -7767,10 +7752,17 @@ mod windows { .unwrap(); // Wait for the VM to be restored - thread::sleep(std::time::Duration::new(20, 0)); + assert!(wait_until(Duration::from_secs(30), || { + remote_command(&api_socket_restored, "info", None) + })); let r = std::panic::catch_unwind(|| { // Resume the VM + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); assert!(remote_command(&api_socket_restored, "resume", None)); windows_guest.shutdown(); @@ -7816,7 +7808,7 @@ mod windows { let r = std::panic::catch_unwind(|| { // Wait to make sure Windows boots up - assert!(windows_guest.wait_for_boot()); + windows_guest.wait_for_boot().unwrap(); let vcpu_num = 2; // Check the initial number of CPUs the guest sees @@ -7827,8 +7819,10 @@ mod windows { let vcpu_num = 6; // Hotplug some CPUs resize_command(&api_socket, Some(vcpu_num), None, None, None); - // Wait to make sure CPUs are added - thread::sleep(std::time::Duration::new(10, 0)); + // Wait for Windows to report the hotplugged CPUs. + assert!(wait_until(Duration::from_secs(10), || windows_guest + .cpu_count() + == vcpu_num)); // Check the guest sees the correct number assert_eq!(windows_guest.cpu_count(), vcpu_num); // Check the CH process has the correct number of vcpu threads @@ -7837,12 +7831,16 @@ mod windows { let vcpu_num = 4; // Remove some CPUs. Note that Windows doesn't support hot-remove. resize_command(&api_socket, Some(vcpu_num), None, None, None); - // Wait to make sure CPUs are removed thread::sleep(std::time::Duration::new(10, 0)); + // Reboot to let Windows catch up windows_guest.reboot(); - // Wait to make sure Windows completely rebooted - thread::sleep(std::time::Duration::new(60, 0)); + // Wait for Windows to come back after the reboot. + windows_guest.wait_for_boot().unwrap(); + // Wait for Windows to reflect the unplugged CPU count. + assert!(wait_until(Duration::from_secs(60), || windows_guest + .cpu_count() + == vcpu_num)); // Check the guest sees the correct number assert_eq!(windows_guest.cpu_count(), vcpu_num); // Check the CH process has the correct number of vcpu threads @@ -7891,7 +7889,7 @@ mod windows { let r = std::panic::catch_unwind(|| { // Wait to make sure Windows boots up - assert!(windows_guest.wait_for_boot()); + windows_guest.wait_for_boot().unwrap(); let ram_size = 2 * 1024 * 1024 * 1024; // Check the initial number of RAM the guest sees @@ -7906,20 +7904,22 @@ mod windows { let ram_size = 4 * 1024 * 1024 * 1024; // Hotplug some RAM resize_command(&api_socket, None, Some(ram_size), None, None); - // Wait to make sure RAM has been added - thread::sleep(std::time::Duration::new(10, 0)); - // Check the guest sees the correct number - assert_eq!(windows_guest.ram_size(), ram_size - reserved_ram_size); + // Wait for Windows to report the hotplugged memory. + assert!(wait_until(Duration::from_secs(10), || windows_guest + .ram_size() + == ram_size - reserved_ram_size)); let ram_size = 3 * 1024 * 1024 * 1024; // Unplug some RAM. Note that hot-remove most likely won't work. resize_command(&api_socket, None, Some(ram_size), None, None); - // Wait to make sure RAM has been added - thread::sleep(std::time::Duration::new(10, 0)); // Reboot to let Windows catch up windows_guest.reboot(); - // Wait to make sure guest completely rebooted - thread::sleep(std::time::Duration::new(60, 0)); + // Wait for Windows to come back after the reboot. + windows_guest.wait_for_boot().unwrap(); + // Wait for Windows to reflect the unplugged RAM amount. + assert!(wait_until(Duration::from_secs(60), || windows_guest + .ram_size() + == ram_size - reserved_ram_size)); // Check the guest sees the correct number assert_eq!(windows_guest.ram_size(), ram_size - reserved_ram_size); @@ -7965,7 +7965,7 @@ mod windows { let r = std::panic::catch_unwind(|| { // Wait to make sure Windows boots up - assert!(windows_guest.wait_for_boot()); + windows_guest.wait_for_boot().unwrap(); // Initially present network device let netdev_num = 1; @@ -7980,7 +7980,11 @@ mod windows { ); assert!(cmd_success); assert!(String::from_utf8_lossy(&cmd_output).contains("\"id\":\"_net2\"")); - thread::sleep(std::time::Duration::new(5, 0)); + // Wait for Windows to enumerate the added network device. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .netdev_count() + == 2 + && netdev_ctrl_threads_count(child.id()) == 2)); // Verify the device is on the system let netdev_num = 2; assert_eq!(windows_guest.netdev_count(), netdev_num); @@ -7989,7 +7993,11 @@ mod windows { // Remove network device let cmd_success = remote_command(&api_socket, "remove-device", Some("_net2")); assert!(cmd_success); - thread::sleep(std::time::Duration::new(5, 0)); + // Wait for Windows to drop the removed network device. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .netdev_count() + == 1 + && netdev_ctrl_threads_count(child.id()) == 1)); // Verify the device has been removed let netdev_num = 1; assert_eq!(windows_guest.netdev_count(), netdev_num); @@ -8041,7 +8049,7 @@ mod windows { let r = std::panic::catch_unwind(|| { // Wait to make sure Windows boots up - assert!(windows_guest.wait_for_boot()); + windows_guest.wait_for_boot().unwrap(); // Initially present disk device let disk_num = 1; @@ -8056,10 +8064,14 @@ mod windows { ); assert!(cmd_success); assert!(String::from_utf8_lossy(&cmd_output).contains("\"id\":\"_disk2\"")); - thread::sleep(std::time::Duration::new(5, 0)); // Online disk device windows_guest.disks_set_rw(); windows_guest.disks_online(); + // Wait for Windows to enumerate the added disk. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .disk_count() + == 2 + && disk_ctrl_threads_count(child.id()) == 2)); // Verify the device is on the system let disk_num = 2; assert_eq!(windows_guest.disk_count(), disk_num); @@ -8072,7 +8084,11 @@ mod windows { // Unmount disk device let cmd_success = remote_command(&api_socket, "remove-device", Some("_disk2")); assert!(cmd_success); - thread::sleep(std::time::Duration::new(5, 0)); + // Wait for Windows to drop the removed disk. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .disk_count() + == 1 + && disk_ctrl_threads_count(child.id()) == 1)); // Verify the device has been removed let disk_num = 1; assert_eq!(windows_guest.disk_count(), disk_num); @@ -8085,7 +8101,11 @@ mod windows { Some(format!("path={disk},readonly=off").as_str()), ); assert!(cmd_success); - thread::sleep(std::time::Duration::new(5, 0)); + // Wait for Windows to mount the re-added disk again. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .disk_file_read(fname) + .trim() + == data)); let out = windows_guest.disk_file_read(fname); assert_eq!(data, out.trim()); @@ -8151,7 +8171,7 @@ mod windows { let r = std::panic::catch_unwind(|| { // Wait to make sure Windows boots up - assert!(windows_guest.wait_for_boot()); + windows_guest.wait_for_boot().unwrap(); // Initially present disk device let disk_num = 1; @@ -8161,6 +8181,10 @@ mod windows { for it in &disk_test_data { let disk_id = it[0].as_str(); let disk = it[1].as_str(); + + let expected_disk_num = windows_guest.disk_count() + 1; + let expected_ctrl_threads = disk_ctrl_threads_count(child.id()) + 1; + // Hotplug disk device let (cmd_success, cmd_output) = remote_command_w_output( &api_socket, @@ -8172,7 +8196,13 @@ mod windows { String::from_utf8_lossy(&cmd_output) .contains(format!("\"id\":\"{disk_id}\"").as_str()) ); - thread::sleep(std::time::Duration::new(5, 0)); + + // Wait for disk to appear + assert!(wait_until(Duration::from_secs(5), || { + windows_guest.disk_count() == expected_disk_num + && disk_ctrl_threads_count(child.id()) == expected_ctrl_threads + })); + // Online disk devices windows_guest.disks_set_rw(); windows_guest.disks_online(); @@ -8194,9 +8224,13 @@ mod windows { let disk_id = it[0].as_str(); let cmd_success = remote_command(&api_socket, "remove-device", Some(disk_id)); assert!(cmd_success); - thread::sleep(std::time::Duration::new(5, 0)); } + // Wait for Windows to drop all removed disks. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .disk_count() + == 1 + && disk_ctrl_threads_count(child.id()) == 1)); // Verify the devices have been removed let disk_num = 1; assert_eq!(windows_guest.disk_count(), disk_num); @@ -8211,9 +8245,12 @@ mod windows { Some(format!("path={disk},readonly=off").as_str()), ); assert!(cmd_success); - thread::sleep(std::time::Duration::new(5, 0)); } + // Wait for Windows to enumerate the re-added disks. + assert!(wait_until(Duration::from_secs(5), || { + windows_guest.disk_count() == 4 && disk_ctrl_threads_count(child.id()) == 4 + })); // Check the files exists with the expected contents for it in &disk_test_data { let fname = it[2].as_str(); @@ -8273,7 +8310,7 @@ mod windows { let r = std::panic::catch_unwind(|| { // Wait to make sure Windows boots up - assert!(windows_guest.wait_for_boot()); + windows_guest.wait_for_boot().unwrap(); let netdev_num = 3; assert_eq!(windows_guest.netdev_count(), netdev_num); From d54a7d27a1995c4156f323ef4aa6f39784fc0a5a Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 10 Apr 2026 13:40:06 +0200 Subject: [PATCH 1127/1893] 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 --- cloud-hypervisor/tests/integration.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 82b1076017..82c24a4f8f 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5069,7 +5069,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=4"]) - .args(["--memory", "size=4G"]) + .args(["--memory", "size=1G"]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .default_disks() .args(["--net", guest.default_net_string().as_str()]) @@ -5117,7 +5117,7 @@ mod common_parallel { let mut cmd = GuestCommand::new(&guest); cmd.args(["--cpus", "boot=4"]) - .args(["--memory", "size=4G"]) + .args(["--memory", "size=1G"]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .default_disks() .args(["--net", guest.default_net_string().as_str()]) @@ -8422,7 +8422,7 @@ mod vfio { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) - .args(["--memory", "size=4G"]) + .args(["--memory", "size=1G"]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args(["--platform", &platform_cfg(iommufd)]) .args(["--api-socket", &api_socket]) @@ -8474,7 +8474,7 @@ mod vfio { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) - .args(["--memory", "size=4G"]) + .args(["--memory", "size=1G"]) .args(["--platform", &platform_cfg(iommufd)]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args([ @@ -8528,7 +8528,7 @@ mod vfio { let mut child = GuestCommand::new(&guest) .args(["--cpus", "boot=4"]) - .args(["--memory", "size=4G"]) + .args(["--memory", "size=1G"]) .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) .args(["--device", format!("path={NVIDIA_VFIO_DEVICE}").as_str()]) .args(["--platform", &platform]) @@ -10713,7 +10713,7 @@ mod rate_limiter { let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("boot={}", num_queues / 2)]) - .args(["--memory", "size=4G"]) + .args(["--memory", "size=1G"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() @@ -10789,7 +10789,7 @@ mod rate_limiter { let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("boot={num_queues}")]) - .args(["--memory", "size=4G"]) + .args(["--memory", "size=1G"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args([ @@ -10899,7 +10899,7 @@ mod rate_limiter { let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("boot={}", num_queues * num_disks)]) - .args(["--memory", "size=4G"]) + .args(["--memory", "size=1G"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args(["--rate-limit-group", &rate_limit_group_arg]) From df58e814ebbd9e79c64bd732be078224c221e795 Mon Sep 17 00:00:00 2001 From: Zhiheng Tao Date: Tue, 14 Apr 2026 14:12:59 +0800 Subject: [PATCH 1128/1893] 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 --- vmm/src/userfaultfd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/userfaultfd.rs b/vmm/src/userfaultfd.rs index 3f8447a327..f79ef52322 100644 --- a/vmm/src/userfaultfd.rs +++ b/vmm/src/userfaultfd.rs @@ -6,7 +6,7 @@ pub const UFFDIO_API: u64 = 0xc018_aa3f; // _IOWR(0xAA, 0x3F, struct uffdio_api) pub const UFFDIO_REGISTER: u64 = 0xc020_aa00; // _IOWR(0xAA, 0x00, struct uffdio_register) pub const UFFDIO_COPY: u64 = 0xc028_aa03; // _IOWR(0xAA, 0x03, struct uffdio_copy) -pub const UFFDIO_WAKE: u64 = 0x4010_aa02; // _IOW(0xAA, 0x02, struct uffdio_range) +pub const UFFDIO_WAKE: u64 = 0x8010_aa02; // _IOR(0xAA, 0x02, struct uffdio_range) // Seccomp compares these as Dword (u32); ensure they fit. const _: () = assert!(UFFDIO_API <= u32::MAX as u64); @@ -17,8 +17,8 @@ const _: () = assert!(UFFDIO_WAKE <= u32::MAX as u64); pub const UFFD_API: u64 = 0xAA; pub const UFFDIO_REGISTER_MODE_MISSING: u64 = 1; pub const UFFD_EVENT_PAGEFAULT: u8 = 0x12; +pub const UFFD_FEATURE_MISSING_HUGETLBFS: u64 = 1 << 4; pub const UFFD_FEATURE_MISSING_SHMEM: u64 = 1 << 5; -pub const UFFD_FEATURE_MISSING_HUGETLBFS: u64 = 1 << 6; const _UFFDIO_COPY: u64 = 0x03; const _UFFDIO_WAKE: u64 = 0x02; From 3e3008f3657cc7a2db3da75977a91f7e0d7e92c9 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Tue, 14 Apr 2026 08:56:01 +0200 Subject: [PATCH 1129/1893] 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 --- virtio-devices/src/device.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index f0673f5614..96a40a4dad 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -13,6 +13,7 @@ use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Barrier}; use std::thread; +use anyhow::anyhow; use libc::EFD_NONBLOCK; use log::{error, info, warn}; use virtio_queue::Queue; @@ -215,6 +216,7 @@ pub struct VirtioCommon { pub paused_sync: Option>, pub epoll_threads: Option>>, pub queue_sizes: Vec, + pub queue_evts: Vec, pub device_type: u32, pub min_queues: u16, pub access_platform: Option>, @@ -252,6 +254,21 @@ impl VirtioCommon { return Err(ActivateError::BadActivate); } + // Do not retain virtio-net queue eventfds here. Signaling them on + // resume would break its `driver_awake` workaround. + self.queue_evts = match VirtioDeviceType::from(self.device_type) { + VirtioDeviceType::Net => Vec::new(), + _ => queues + .iter() + .map(|(_, _, queue_evt)| { + queue_evt.try_clone().map_err(|e| { + error!("failed cloning queue EventFd: {e}"); + ActivateError::BadActivate + }) + }) + .collect::, _>>()?, + }; + let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(|e| { error!("failed creating kill EventFd: {e}"); ActivateError::BadActivate @@ -272,6 +289,8 @@ impl VirtioCommon { } pub fn reset(&mut self) -> Option> { + self.queue_evts.clear(); + // We first must resume the virtio thread if it was paused. if self.pause_evt.take().is_some() { self.resume().ok()?; @@ -355,6 +374,16 @@ impl Pausable for VirtioCommon { } } + // Signal each activated queue eventfd so workers process restored queues + // that may already contain pending requests. + for queue_evt in &self.queue_evts { + queue_evt.write(1).map_err(|e| { + MigratableError::Resume(anyhow!( + "Could not notify restored virtio worker on resume: {e}" + )) + })?; + } + Ok(()) } } From 101c2590514ffaf53726aaf2a9a636673b6142b5 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 14 Apr 2026 12:10:55 +0100 Subject: [PATCH 1130/1893] 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 --- virtio-devices/src/device.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 96a40a4dad..0bab703137 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -384,6 +384,15 @@ impl Pausable for VirtioCommon { })?; } + // Also trigger interrupts into the guest to wake up the driver to avoid a "livelock" + if let Some(interrupt_cb) = &self.interrupt_cb { + for i in 0..self.queue_evts.len() { + interrupt_cb + .trigger(crate::VirtioInterruptType::Queue(i as u16)) + .ok(); + } + } + Ok(()) } } From 72fc0976f113284ee229bc657926d3b712f74e6a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 14 Apr 2026 12:14:05 +0100 Subject: [PATCH 1131/1893] 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 --- virtio-devices/src/device.rs | 21 ++++++++------------- virtio-devices/src/net.rs | 14 ++------------ 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 0bab703137..d1b9257995 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -254,20 +254,15 @@ impl VirtioCommon { return Err(ActivateError::BadActivate); } - // Do not retain virtio-net queue eventfds here. Signaling them on - // resume would break its `driver_awake` workaround. - self.queue_evts = match VirtioDeviceType::from(self.device_type) { - VirtioDeviceType::Net => Vec::new(), - _ => queues - .iter() - .map(|(_, _, queue_evt)| { - queue_evt.try_clone().map_err(|e| { - error!("failed cloning queue EventFd: {e}"); - ActivateError::BadActivate - }) + self.queue_evts = queues + .iter() + .map(|(_, _, queue_evt)| { + queue_evt.try_clone().map_err(|e| { + error!("failed cloning queue EventFd: {e}"); + ActivateError::BadActivate }) - .collect::, _>>()?, - }; + }) + .collect::, _>>()?; let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(|e| { error!("failed creating kill EventFd: {e}"); diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 3bb360c646..ed8c05eeb8 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -174,11 +174,6 @@ struct NetEpollHandler { queue_index_base: u16, queue_pair: (Queue, Queue), queue_evt_pair: (EventFd, EventFd), - // Always generate interrupts until the driver has signalled to the device. - // This mitigates a problem with interrupts from tap events being "lost" upon - // a restore as the vCPU thread isn't ready to handle the interrupt. This causes - // issues when combined with VIRTIO_RING_F_EVENT_IDX interrupt suppression. - driver_awake: bool, device_status: Arc, } @@ -260,7 +255,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese return Ok(()); } - if res.map_err(DeviceError::NetQueuePair)? || !self.driver_awake { + if res.map_err(DeviceError::NetQueuePair)? { self.signal_used_queue(self.queue_index_base + 1)?; debug!("Signalling TX queue"); } else { @@ -296,7 +291,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese return Ok(()); } - if res.map_err(DeviceError::NetQueuePair)? || !self.driver_awake { + if res.map_err(DeviceError::NetQueuePair)? { self.signal_used_queue(self.queue_index_base)?; debug!("Signalling RX queue"); } else { @@ -361,7 +356,6 @@ impl EpollHelperHandler for NetEpollHandler { let ev_type = event.data as u16; match ev_type { RX_QUEUE_EVENT => { - self.driver_awake = true; self.handle_rx_event().map_err(|e| { EpollHelperError::HandleEvent(anyhow!("Error processing RX queue: {e:?}")) })?; @@ -371,7 +365,6 @@ impl EpollHelperHandler for NetEpollHandler { if let Err(e) = queue_evt.read() { error!("Failed to get tx queue event: {e:?}"); } - self.driver_awake = true; self.handle_tx_event().map_err(|e| { EpollHelperError::HandleEvent(anyhow!("Error processing TX queue: {e:?}")) })?; @@ -428,8 +421,6 @@ impl EpollHelperHandler for NetEpollHandler { "Error from 'rate_limiter.event_handler()': {e:?}" )) })?; - - self.driver_awake = true; self.process_tx().map_err(|e| { EpollHelperError::HandleEvent(anyhow!("Error processing TX queue: {e:?}")) })?; @@ -855,7 +846,6 @@ impl VirtioDevice for Net { interrupt_cb: interrupt_cb.clone(), kill_evt, pause_evt, - driver_awake: false, device_status: self.device_status.clone(), }; From 0a32a9ca9194878585e530c05f77f00c63d9c6a8 Mon Sep 17 00:00:00 2001 From: Shayon Mukherjee Date: Tue, 14 Apr 2026 06:58:59 -0400 Subject: [PATCH 1132/1893] 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 --- vmm/src/userfaultfd.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vmm/src/userfaultfd.rs b/vmm/src/userfaultfd.rs index f79ef52322..bbefb16bc6 100644 --- a/vmm/src/userfaultfd.rs +++ b/vmm/src/userfaultfd.rs @@ -8,6 +8,18 @@ pub const UFFDIO_REGISTER: u64 = 0xc020_aa00; // _IOWR(0xAA, 0x00, struct uffdio pub const UFFDIO_COPY: u64 = 0xc028_aa03; // _IOWR(0xAA, 0x03, struct uffdio_copy) pub const UFFDIO_WAKE: u64 = 0x8010_aa02; // _IOR(0xAA, 0x02, struct uffdio_range) +// Validate ioctl encoding against the _IO{R,W,WR}(type, nr, size) formula so +// transposed direction bits or sizes are caught at compile time. +const fn ioctl_ioc(dir: u64, typ: u64, nr: u64, size: u64) -> u64 { + (dir << 30) | (size << 16) | (typ << 8) | nr +} +const IOC_READ: u64 = 2; +const IOC_READWRITE: u64 = 3; +const _: () = assert!(UFFDIO_API == ioctl_ioc(IOC_READWRITE, 0xAA, 0x3F, 24)); +const _: () = assert!(UFFDIO_REGISTER == ioctl_ioc(IOC_READWRITE, 0xAA, 0x00, 32)); +const _: () = assert!(UFFDIO_COPY == ioctl_ioc(IOC_READWRITE, 0xAA, 0x03, 40)); +const _: () = assert!(UFFDIO_WAKE == ioctl_ioc(IOC_READ, 0xAA, 0x02, 16)); + // Seccomp compares these as Dword (u32); ensure they fit. const _: () = assert!(UFFDIO_API <= u32::MAX as u64); const _: () = assert!(UFFDIO_REGISTER <= u32::MAX as u64); From 1268539b26b42c45af4f591902f429a91dca00fb Mon Sep 17 00:00:00 2001 From: Nguyen Dinh Phi Date: Wed, 8 Apr 2026 23:11:27 +0800 Subject: [PATCH 1133/1893] 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 --- cloud-hypervisor/src/main.rs | 80 ++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 415e7ed922..06ef1fe30f 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -504,7 +504,37 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String .args(args) } -fn start_vmm(cmd_arguments: &ArgMatches) -> Result, Error> { +fn parse_api_socket(cmd_arguments: &ArgMatches) -> Result<(Option, Option), Error> { + if let Some(socket_config) = cmd_arguments.get_one::("api-socket") { + let mut parser = OptionParser::new(); + parser.add("path").add("fd"); + parser.parse(socket_config).unwrap_or_default(); + + if let Some(fd) = parser.get("fd") { + Ok(( + None, + Some(fd.parse::().map_err(Error::ParsingApiSocket)?), + )) + } else if let Some(path) = parser.get("path") { + Ok((Some(path), None)) + } else { + Ok(( + cmd_arguments + .get_one::("api-socket") + .map(|s| s.to_string()), + None, + )) + } + } else { + Ok((None, None)) + } +} + +fn start_vmm( + cmd_arguments: &ArgMatches, + api_socket_path: &Option, + api_socket_fd: Option, +) -> Result<(), Error> { let log_level = match cmd_arguments.get_count("v") { 0 => LevelFilter::Warn, 1 => LevelFilter::Info, @@ -527,31 +557,6 @@ fn start_vmm(cmd_arguments: &ArgMatches) -> Result, Error> { .map(|()| log::set_max_level(log_level)) .map_err(Error::LoggerSetup)?; - let (api_socket_path, api_socket_fd) = - if let Some(socket_config) = cmd_arguments.get_one::("api-socket") { - let mut parser = OptionParser::new(); - parser.add("path").add("fd"); - parser.parse(socket_config).unwrap_or_default(); - - if let Some(fd) = parser.get("fd") { - ( - None, - Some(fd.parse::().map_err(Error::ParsingApiSocket)?), - ) - } else if let Some(path) = parser.get("path") { - (Some(path), None) - } else { - ( - cmd_arguments - .get_one::("api-socket") - .map(|s| s.to_string()), - None, - ) - } - } else { - (None, None) - }; - let (api_request_sender, api_request_receiver) = channel(); let api_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateApiEventFd)?; @@ -712,7 +717,7 @@ fn start_vmm(cmd_arguments: &ArgMatches) -> Result, Error> { let vmm_thread_handle = vmm::start_vmm_thread( vmm::VmmVersionInfo::new(env!("BUILD_VERSION"), env!("CARGO_PKG_VERSION")), - &api_socket_path, + api_socket_path, api_socket_fd, #[cfg(feature = "dbus_api")] dbus_options, @@ -798,7 +803,7 @@ fn start_vmm(cmd_arguments: &ArgMatches) -> Result, Error> { dbus_api_graceful_shutdown(chs); } - r.map(|_| api_socket_path) + r } // This is a best-effort solution to the latency induced by the RCU @@ -904,9 +909,22 @@ fn main() { warn!("Error expanding FD table: {e}"); } - let exit_code = match start_vmm(&cmd_arguments) { - Ok(path) => { - path.map(|s| std::fs::remove_file(s).ok()); + let (api_socket_path, api_socket_fd) = match parse_api_socket(&cmd_arguments) { + Ok(p) => p, + Err(top_error) => { + cloud_hypervisor::cli_print_error_chain(&top_error, "Cloud Hypervisor", |_, _, _| None); + std::process::exit(1); + } + }; + + let vmm_result = start_vmm(&cmd_arguments, &api_socket_path, api_socket_fd); + + if let Some(ref p) = api_socket_path { + let _ = std::fs::remove_file(p); + } + + let exit_code = match vmm_result { + Ok(()) => { info!("Cloud Hypervisor exited successfully"); 0 } From a8ff2c50afcff47afae04faa3de820b8c76d32b8 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 17:12:30 +0100 Subject: [PATCH 1134/1893] 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 --- option_parser/src/lib.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index 699a26252c..72b9783de1 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -93,7 +93,7 @@ impl OptionParser { } } - pub fn parse(&mut self, input: &str) -> OptionParserResult<()> { + fn parse_inner(&mut self, input: &str, ignore_unknown: bool) -> OptionParserResult<()> { if input.trim().is_empty() { return Ok(()); } @@ -101,7 +101,11 @@ impl OptionParser { for option in split_commas(input)?.iter() { let parts: Vec<&str> = option.splitn(2, '=').collect(); match self.options.get_mut(parts[0]) { - None => return Err(OptionParserError::UnknownOption(parts[0].to_owned())), + None => { + if !ignore_unknown { + return Err(OptionParserError::UnknownOption(parts[0].to_owned())); + } + } Some(value) => { if value.requires_value { if parts.len() != 2 { @@ -118,6 +122,14 @@ impl OptionParser { Ok(()) } + pub fn parse(&mut self, input: &str) -> OptionParserResult<()> { + self.parse_inner(input, false) + } + + pub fn parse_subset(&mut self, input: &str) -> OptionParserResult<()> { + self.parse_inner(input, true) + } + pub fn add(&mut self, option: &str) -> &mut Self { // Check that option=value has balanced // quotes and brackets iff value does. From 791889cefd62f96c80475b36bf5d1613fa739c35 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 4 Apr 2026 08:44:11 -0700 Subject: [PATCH 1135/1893] 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 --- option_parser/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index 72b9783de1..67c12bd055 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -148,6 +148,14 @@ impl OptionParser { self } + pub fn add_all(&mut self, options: &[&str]) -> &mut Self { + for option in options { + self.add(option); + } + + self + } + pub fn add_valueless(&mut self, option: &str) -> &mut Self { self.options.insert( option.to_owned(), From d212255073b411db6806f04999b79bb4821ffbb7 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 14 Apr 2026 07:49:46 +0100 Subject: [PATCH 1136/1893] option_parser: Add documentation strings Autogenerated with Claude Opus 4.6 and reviewed with human eyes. Signed-off-by: Rob Bradford --- option_parser/src/lib.rs | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index 67c12bd055..85d265f6a0 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -3,6 +3,29 @@ // SPDX-License-Identifier: Apache-2.0 // +//! A parser for comma-separated `key=value` option strings. +//! +//! This crate provides [`OptionParser`], which parses strings of the form +//! `"key1=value1,key2=value2,..."` into a set of named options that can then +//! be retrieved and converted to various types. +//! +//! Values may be quoted with `"` to embed commas and other special characters, +//! and brackets `[` `]` are tracked so that list-valued options like +//! `topology=[1,2,3]` are not split at inner commas. +//! +//! # Example +//! +//! ``` +//! use option_parser::OptionParser; +//! +//! let mut parser = OptionParser::new(); +//! parser.add("size").add("mergeable"); +//! parser.parse("size=128M,mergeable=on").unwrap(); +//! +//! assert_eq!(parser.get("size"), Some("128M".to_owned())); +//! assert_eq!(parser.get("mergeable"), Some("on".to_owned())); +//! ``` + use std::collections::HashMap; use std::fmt::{Display, Write}; use std::num::ParseIntError; @@ -27,6 +50,12 @@ mod private_trait { } use private_trait::Parseable; +/// A parser for comma-separated `key=value` option strings. +/// +/// Options must be registered with [`add`](Self::add) or +/// [`add_valueless`](Self::add_valueless) before parsing. After calling +/// [`parse`](Self::parse), values can be retrieved with [`get`](Self::get) +/// or converted to a specific type with [`convert`](Self::convert). #[derive(Default)] pub struct OptionParser { options: HashMap, @@ -37,14 +66,19 @@ struct OptionParserValue { requires_value: bool, } +/// Errors returned when parsing or converting options. #[derive(Debug, Error)] pub enum OptionParserError { + /// An option name was not previously registered with [`OptionParser::add`]. #[error("unknown option: {0}")] UnknownOption(String), + /// The input string has invalid syntax (unbalanced quotes/brackets, missing `=`). #[error("unknown option: {0}")] InvalidSyntax(String), + /// A value could not be converted to the requested type. #[error("unable to convert {1} for {0}")] Conversion(String /* field */, String /* value */), + /// A value was syntactically valid but semantically wrong. #[error("invalid value: {0}")] InvalidValue(String), } @@ -87,6 +121,7 @@ fn split_commas(s: &str) -> OptionParserResult> { } impl OptionParser { + /// Creates an empty `OptionParser` with no registered options. pub fn new() -> Self { Self { options: HashMap::new(), @@ -122,14 +157,30 @@ impl OptionParser { Ok(()) } + /// Parses a comma-separated `key=value` string, updating registered options. + /// + /// Returns an error if the input contains an unknown option name, has + /// unbalanced quotes or brackets, or a value-requiring option lacks `=`. pub fn parse(&mut self, input: &str) -> OptionParserResult<()> { self.parse_inner(input, false) } + /// Like [`parse`](Self::parse), but silently ignores unknown option names. + /// + /// This is useful when multiple parsers share the same input string and + /// each only cares about a subset of the options. pub fn parse_subset(&mut self, input: &str) -> OptionParserResult<()> { self.parse_inner(input, true) } + /// Registers a named option that requires a value (i.e. `key=value`). + /// + /// Option names must not contain `"`, `[`, `]`, `=`, or `,`. + /// Returns `&mut Self` for chaining. + /// + /// # Panics + /// + /// Panics if the option name contains a forbidden character. pub fn add(&mut self, option: &str) -> &mut Self { // Check that option=value has balanced // quotes and brackets iff value does. @@ -148,6 +199,9 @@ impl OptionParser { self } + /// Registers multiple value-requiring options at once. + /// + /// Equivalent to calling [`add`](Self::add) for each element in the slice. pub fn add_all(&mut self, options: &[&str]) -> &mut Self { for option in options { self.add(option); @@ -156,6 +210,10 @@ impl OptionParser { self } + /// Registers a flag-style option that does not take a value. + /// + /// When this option appears in the input string (without `=`), it is + /// marked as set. Use [`is_set`](Self::is_set) to query it. pub fn add_valueless(&mut self, option: &str) -> &mut Self { self.options.insert( option.to_owned(), @@ -168,6 +226,10 @@ impl OptionParser { self } + /// Returns the raw string value of an option, or `None` if the option was + /// not set or if its value is an empty string (e.g. `key=`). + /// + /// Surrounding double-quotes in the value are removed. pub fn get(&self, option: &str) -> Option { self.options .get(option) @@ -181,6 +243,9 @@ impl OptionParser { }) } + /// Returns `true` if the option was present in the parsed input. + /// + /// This works for both value-requiring and valueless options. pub fn is_set(&self, option: &str) -> bool { self.options .get(option) @@ -188,6 +253,14 @@ impl OptionParser { .is_some() } + /// Retrieves and converts an option value to type `T`. + /// + /// Returns `Ok(None)` if the option was not set or its value is empty. + /// Returns `Err` if the value cannot be converted to `T`. + /// + /// `T` can be any type that implements `FromStr` (e.g. `u32`, `String`), + /// or one of this crate's types such as [`Toggle`], [`IntegerList`], + /// [`Tuple`], or [`StringList`]. pub fn convert(&self, option: &str) -> OptionParserResult> { match self.options.get(option).and_then(|v| v.value.as_ref()) { None => Ok(None), @@ -204,6 +277,9 @@ impl OptionParser { } } +/// A boolean-like value that accepts `"on"`, `"true"`, `"off"`, `"false"`, or `""`. +/// +/// An empty string is treated as `false`. pub struct Toggle(pub bool); #[derive(Error, Debug)] @@ -227,6 +303,10 @@ impl Parseable for Toggle { } } +/// A byte size parsed from a human-readable string with optional `K`, `M`, or `G` suffix. +/// +/// The suffix is binary (1K = 1024, 1M = 1048576, 1G = 1073741824). +/// A bare integer is treated as bytes. pub struct ByteSized(pub u64); #[derive(Error, Debug)] @@ -259,6 +339,9 @@ impl FromStr for ByteSized { } } +/// A list of integers parsed from a bracket-enclosed, comma-separated string. +/// +/// Ranges are supported with `-`: `"[0,2-4,6]"` produces `[0, 2, 3, 4, 6]`. pub struct IntegerList(pub Vec); impl Display for IntegerList { @@ -324,7 +407,11 @@ impl Parseable for IntegerList { } } +/// Types that can appear as the second element of a [`Tuple`] pair. +/// +/// Implemented for `u64`, `Vec`, `Vec`, and `Vec`. pub trait TupleValue { + /// Parses the value portion of a `key@value` tuple element. fn parse_value(input: &str) -> Result where Self: Sized; @@ -366,6 +453,10 @@ impl TupleValue for Vec { } } +/// A list of `key@value` pairs parsed from a bracket-enclosed string. +/// +/// The format is `[key1@value1,key2@value2,...]` where `@` separates each +/// pair's elements. `S` is the key type and `T` is the value type. #[derive(PartialEq, Eq, Debug)] pub struct Tuple(pub Vec<(S, T)>); @@ -421,6 +512,9 @@ impl Parseable for Tuple { } } +/// A list of strings parsed from a bracket-enclosed, comma-separated string. +/// +/// The format is `[str1,str2,...]`. Brackets are optional. #[derive(Default)] pub struct StringList(pub Vec); From e5496a093d50242144138d94b5022da6a31cfc84 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 14 Apr 2026 07:52:07 +0100 Subject: [PATCH 1137/1893] 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 --- option_parser/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index 85d265f6a0..e674507d4d 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -73,7 +73,7 @@ pub enum OptionParserError { #[error("unknown option: {0}")] UnknownOption(String), /// The input string has invalid syntax (unbalanced quotes/brackets, missing `=`). - #[error("unknown option: {0}")] + #[error("invalid syntax: {0}")] InvalidSyntax(String), /// A value could not be converted to the requested type. #[error("unable to convert {1} for {0}")] From d93770c11dc63d239f0e4f9af3256104aad7adbe Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 14 Apr 2026 08:03:39 +0100 Subject: [PATCH 1138/1893] 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 --- option_parser/src/lib.rs | 230 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index e674507d4d..e57cd895e2 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -647,4 +647,234 @@ mod unit_tests { fn check_dequote() { assert_eq!(dequote("a\u{3b2}\"a\"\"\""), "a\u{3b2}a\""); } + + #[test] + fn test_empty_input() { + let mut parser = OptionParser::new(); + parser.add("foo"); + parser.parse("").unwrap(); + parser.parse(" ").unwrap(); + assert!(!parser.is_set("foo")); + } + + #[test] + fn test_parse_subset_ignores_unknown() { + let mut parser = OptionParser::new(); + parser.add("known"); + parser.parse_subset("known=val,unknown=other").unwrap(); + assert_eq!(parser.get("known"), Some("val".to_owned())); + assert!(!parser.is_set("unknown")); + } + + #[test] + fn test_add_all() { + let mut parser = OptionParser::new(); + parser.add_all(&["a", "b", "c"]); + parser.parse("a=1,b=2,c=3").unwrap(); + assert_eq!(parser.get("a"), Some("1".to_owned())); + assert_eq!(parser.get("b"), Some("2".to_owned())); + assert_eq!(parser.get("c"), Some("3".to_owned())); + } + + #[test] + fn test_add_valueless() { + let mut parser = OptionParser::new(); + parser.add_valueless("readonly"); + parser.add("path"); + parser.parse("path=/dev/sda,readonly").unwrap(); + assert!(parser.is_set("readonly")); + assert_eq!(parser.get("readonly"), None); + assert_eq!(parser.get("path"), Some("/dev/sda".to_owned())); + } + + #[test] + fn test_convert_integer() { + let mut parser = OptionParser::new(); + parser.add("count"); + parser.parse("count=42").unwrap(); + assert_eq!(parser.convert::("count").unwrap(), Some(42)); + assert_eq!(parser.convert::("count").unwrap(), Some(42)); + } + + #[test] + fn test_convert_unset_returns_none() { + let mut parser = OptionParser::new(); + parser.add("count"); + assert_eq!(parser.convert::("count").unwrap(), None); + } + + #[test] + fn test_convert_invalid_returns_error() { + let mut parser = OptionParser::new(); + parser.add("count"); + parser.parse("count=notanumber").unwrap(); + parser.convert::("count").unwrap_err(); + } + + #[test] + fn test_toggle() { + for (input, expected) in [ + ("on", true), + ("off", false), + ("true", true), + ("false", false), + ("ON", true), + ("OFF", false), + ("True", true), + ("False", false), + ] { + let mut parser = OptionParser::new(); + parser.add("flag"); + parser.parse(&format!("flag={input}")).unwrap(); + let toggle = parser.convert::("flag").unwrap().unwrap(); + assert_eq!(toggle.0, expected, "Toggle({input}) should be {expected}"); + } + } + + #[test] + fn test_toggle_invalid() { + let mut parser = OptionParser::new(); + parser.add("flag"); + parser.parse("flag=maybe").unwrap(); + assert!(parser.convert::("flag").is_err()); + } + + #[test] + fn test_byte_sized() { + let cases = [ + ("1024", 1024u64), + ("1K", 1024), + ("2M", 2 * 1024 * 1024), + ("4G", 4 * 1024 * 1024 * 1024), + ("0K", 0), + ]; + for (input, expected) in cases { + let mut parser = OptionParser::new(); + parser.add("size"); + parser.parse(&format!("size={input}")).unwrap(); + let bs = parser.convert::("size").unwrap().unwrap(); + assert_eq!(bs.0, expected, "ByteSized({input}) should be {expected}"); + } + } + + #[test] + fn test_byte_sized_invalid() { + assert!("xyzK".parse::().is_err()); + assert!("".parse::().is_err()); + } + + #[test] + fn test_integer_list_single_values() { + let list = IntegerList::from_str("[1,3,5]").unwrap(); + assert_eq!(list.0, vec![1, 3, 5]); + } + + #[test] + fn test_integer_list_ranges() { + let list = IntegerList::from_str("[0,2-4,7]").unwrap(); + assert_eq!(list.0, vec![0, 2, 3, 4, 7]); + } + + #[test] + fn test_integer_list_invalid_range() { + assert!(IntegerList::from_str("[5-3]").is_err()); + assert!(IntegerList::from_str("[5-5]").is_err()); + } + + #[test] + fn test_integer_list_too_many_dashes() { + assert!(IntegerList::from_str("[1-2-3]").is_err()); + } + + #[test] + fn test_integer_list_display() { + let list = IntegerList(vec![1, 2, 3]); + assert_eq!(format!("{list}"), "[1,2,3]"); + + let empty = IntegerList(vec![]); + assert_eq!(format!("{empty}"), "[]"); + + let single = IntegerList(vec![42]); + assert_eq!(format!("{single}"), "[42]"); + } + + #[test] + fn test_string_list() { + let list = StringList::from_str("[foo,bar,baz]").unwrap(); + assert_eq!(list.0, vec!["foo", "bar", "baz"]); + } + + #[test] + fn test_string_list_no_brackets() { + let list = StringList::from_str("foo,bar").unwrap(); + assert_eq!(list.0, vec!["foo", "bar"]); + } + + #[test] + fn test_tuple_single_pair() { + let t = Tuple::::from_str("[foo@42]").unwrap(); + assert_eq!(t, Tuple(vec![("foo".to_owned(), 42)])); + } + + #[test] + fn test_tuple_multiple_pairs() { + let t = Tuple::>::from_str("[a@[1,2],b@[3,4]]").unwrap(); + assert_eq!( + t, + Tuple(vec![ + ("a".to_owned(), vec![1, 2]), + ("b".to_owned(), vec![3, 4]), + ]) + ); + } + + #[test] + fn test_tuple_missing_at_separator() { + Tuple::::from_str("[foo42]").unwrap_err(); + } + + #[test] + fn test_tuple_missing_brackets() { + Tuple::::from_str("foo@42").unwrap_err(); + } + + #[test] + fn test_split_commas_unbalanced_bracket() { + split_commas("[a,b").unwrap_err(); + split_commas("a]").unwrap_err(); + } + + #[test] + fn test_split_commas_unbalanced_quote() { + split_commas("\"abc").unwrap_err(); + } + + #[test] + fn test_quoted_value_with_commas() { + let mut parser = OptionParser::new(); + parser.add("cmd"); + parser.parse("cmd=\"a,b,c\"").unwrap(); + assert_eq!(parser.get("cmd"), Some("a,b,c".to_owned())); + } + + #[test] + #[should_panic(expected = "forbidden character")] + fn test_add_option_with_equals() { + let mut parser = OptionParser::new(); + parser.add("bad=name"); + } + + #[test] + #[should_panic(expected = "forbidden character")] + fn test_add_option_with_comma() { + let mut parser = OptionParser::new(); + parser.add("bad,name"); + } + + #[test] + #[should_panic(expected = "forbidden character")] + fn test_add_option_with_bracket() { + let mut parser = OptionParser::new(); + parser.add("bad[name"); + } } From 2148f2e0bc69b7563358029b7ee8db94aa7720f0 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 14 Apr 2026 08:25:17 +0100 Subject: [PATCH 1139/1893] 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 --- option_parser/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index e57cd895e2..6be4bcbb1a 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -588,11 +588,13 @@ mod unit_tests { assert_eq!(split_commas("\"\"").unwrap(), vec!["\"\""]); parser.parse("size=128M,hanging_param").unwrap_err(); - parser - .parse("size=128M,too_many_equals=foo=bar") - .unwrap_err(); parser.parse("size=128M,file=/dev/shm").unwrap_err(); + // Equals signs within a value are fine (splitn(2, '=') keeps them) + parser.add("extra"); + parser.parse("extra=foo=bar").unwrap(); + assert_eq!(parser.get("extra"), Some("foo=bar".to_owned())); + parser.parse("size=128M").unwrap(); assert_eq!(parser.get("size"), Some("128M".to_owned())); assert!(!parser.is_set("mergeable")); From 4f7ff8fe48bd7c6eb2d257920873378c71fdfa8e Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 16:46:46 +0100 Subject: [PATCH 1140/1893] 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 --- vmm/src/vm_config.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 541f6f21b0..1b20d76da6 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -273,6 +273,16 @@ pub struct VirtQueueAffinity { pub host_cpus: Vec, } +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] +pub struct PciDeviceCommonConfig { + #[serde(default)] + pub id: Option, + #[serde(default, skip_serializing_if = "<&bool as std::ops::Not>::not")] + pub iommu: bool, + #[serde(default)] + pub pci_segment: u16, +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct DiskConfig { pub path: Option, From c66c2b847053bcc22f1c367d59914536b69903c8 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 17:15:54 +0100 Subject: [PATCH 1141/1893] 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 --- vmm/src/config.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index b2ae29a84f..2cf80191d7 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -209,6 +209,8 @@ pub enum Error { /// Failed Parsing FwCfgItem config #[error("Error parsing --fw-cfg-config items")] ParseFwCfgItem(#[source] OptionParserError), + #[error("Error parsing common PCI device config")] + ParsePciDeviceCommonConfig(#[source] OptionParserError), } #[derive(Debug, PartialEq, Eq, Error)] @@ -1197,6 +1199,35 @@ impl RateLimiterGroupConfig { } } +impl PciDeviceCommonConfig { + pub fn parse(input: &str) -> Result { + let mut parser = OptionParser::new(); + + parser.add("id").add("iommu").add("pci_segment"); + + parser + .parse_subset(input) + .map_err(Error::ParsePciDeviceCommonConfig)?; + + let id = parser.get("id"); + let iommu = parser + .convert::("iommu") + .map_err(Error::ParsePciDeviceCommonConfig)? + .unwrap_or(Toggle(false)) + .0; + let pci_segment = parser + .convert("pci_segment") + .map_err(Error::ParsePciDeviceCommonConfig)? + .unwrap_or_default(); + + Ok(Self { + id, + iommu, + pci_segment, + }) + } +} + impl DiskConfig { pub const SYNTAX: &'static str = "Disk parameters \ \"path=,readonly=on|off,direct=on|off,iommu=on|off,\ From a96da9d4bc8e82f25a673439d5320ec0638c94ce Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 11:16:24 -0700 Subject: [PATCH 1142/1893] 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 --- vmm/src/config.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 2cf80191d7..6c399dbdd9 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1226,6 +1226,23 @@ impl PciDeviceCommonConfig { pci_segment, }) } + + pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { + if let Some(platform_config) = vm_config.platform.as_ref() { + if self.pci_segment >= platform_config.num_pci_segments { + return Err(ValidationError::InvalidPciSegment(self.pci_segment)); + } + + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + && !self.iommu + { + return Err(ValidationError::OnIommuSegment(self.pci_segment)); + } + } + + Ok(()) + } } impl DiskConfig { From d2ce7667bc9db5f81bfbd4bb0217d846fca9bfed Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 11:18:09 -0700 Subject: [PATCH 1143/1893] 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 --- vmm/src/config.rs | 77 ++++++++++++++++++--------------------- vmm/src/device_manager.rs | 16 ++++---- vmm/src/vm_config.rs | 8 +--- 3 files changed, 46 insertions(+), 55 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 6c399dbdd9..9ff05bb5f6 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1299,11 +1299,6 @@ impl DiskConfig { .map_err(Error::ParseDisk)? .unwrap_or(Toggle(false)) .0; - let iommu = parser - .convert::("iommu") - .map_err(Error::ParseDisk)? - .unwrap_or(Toggle(false)) - .0; let queue_size = parser .convert("queue_size") .map_err(Error::ParseDisk)? @@ -1318,7 +1313,6 @@ impl DiskConfig { .unwrap_or(Toggle(false)) .0; let vhost_socket = parser.get("socket"); - let id = parser.get("id"); let disable_io_uring = parser .convert::("_disable_io_uring") .map_err(Error::ParseDisk)? @@ -1329,10 +1323,6 @@ impl DiskConfig { .map_err(Error::ParseDisk)? .unwrap_or(Toggle(false)) .0; - let pci_segment = parser - .convert("pci_segment") - .map_err(Error::ParseDisk)? - .unwrap_or_default(); let rate_limit_group = parser.get("rate_limit_group"); let bw_size = parser .convert("bw_size") @@ -1423,21 +1413,21 @@ impl DiskConfig { .unwrap_or_else(|| Toggle(default_diskconfig_sparse())) .0; + let pci_common = PciDeviceCommonConfig::parse(disk)?; + Ok(DiskConfig { + pci_common, path, readonly, direct, - iommu, num_queues, queue_size, vhost_user, vhost_socket, rate_limit_group, rate_limiter_config, - id, disable_io_uring, disable_aio, - pci_segment, serial, queue_affinity, backing_files, @@ -1448,6 +1438,8 @@ impl DiskConfig { } pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { + self.pci_common.validate(vm_config)?; + if self.num_queues > vm_config.cpus.boot_vcpus as usize { return Err(ValidationError::TooManyQueues( self.num_queues, @@ -1459,23 +1451,10 @@ impl DiskConfig { return Err(ValidationError::InvalidQueueSize(self.queue_size)); } - if self.vhost_user && self.iommu { + if self.vhost_user && self.pci_common.iommu { return Err(ValidationError::IommuNotSupported); } - if let Some(platform_config) = vm_config.platform.as_ref() { - if self.pci_segment >= platform_config.num_pci_segments { - return Err(ValidationError::InvalidPciSegment(self.pci_segment)); - } - - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() - && iommu_segments.contains(&self.pci_segment) - && !self.iommu - { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } - } - if self.rate_limiter_config.is_some() && self.rate_limit_group.is_some() { return Err(ValidationError::InvalidRateLimiterGroup); } @@ -3075,9 +3054,9 @@ impl VmConfig { } disk.validate(self)?; - self.iommu |= disk.iommu; + self.iommu |= disk.pci_common.iommu; - Self::validate_identifier(&mut id_list, &disk.id)?; + Self::validate_identifier(&mut id_list, &disk.pci_common.id)?; } } @@ -3564,7 +3543,7 @@ impl VmConfig { // Remove if disk device if let Some(disks) = self.disks.as_mut() { let len = disks.len(); - disks.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + disks.retain(|dev| dev.pci_common.id.as_ref().map(|id| id.as_ref()) != Some(id)); removed |= disks.len() != len; } @@ -4025,20 +4004,18 @@ mod unit_tests { fn disk_fixture() -> DiskConfig { DiskConfig { + pci_common: PciDeviceCommonConfig::default(), path: Some(PathBuf::from("/path/to_file")), readonly: false, direct: false, - iommu: false, num_queues: 1, queue_size: 128, vhost_user: false, vhost_socket: None, - id: None, disable_io_uring: false, disable_aio: false, rate_limit_group: None, rate_limiter_config: None, - pci_segment: 0, serial: None, queue_affinity: None, backing_files: false, @@ -4057,7 +4034,10 @@ mod unit_tests { assert_eq!( DiskConfig::parse("path=/path/to_file,id=mydisk0")?, DiskConfig { - id: Some("mydisk0".to_owned()), + pci_common: PciDeviceCommonConfig { + id: Some("mydisk0".to_owned()), + ..Default::default() + }, ..disk_fixture() } ); @@ -4074,14 +4054,20 @@ mod unit_tests { assert_eq!( DiskConfig::parse("path=/path/to_file,iommu=on")?, DiskConfig { - iommu: true, + pci_common: PciDeviceCommonConfig { + iommu: true, + ..Default::default() + }, ..disk_fixture() } ); assert_eq!( DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256")?, DiskConfig { - iommu: true, + pci_common: PciDeviceCommonConfig { + iommu: true, + ..Default::default() + }, queue_size: 256, ..disk_fixture() } @@ -4089,7 +4075,10 @@ mod unit_tests { assert_eq!( DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256,num_queues=4")?, DiskConfig { - iommu: true, + pci_common: PciDeviceCommonConfig { + iommu: true, + ..Default::default() + }, queue_size: 256, num_queues: 4, ..disk_fixture() @@ -5326,8 +5315,11 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); still_valid_config.disks = Some(vec![DiskConfig { - iommu: true, - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + iommu: true, + pci_segment: 1, + ..Default::default() + }, ..disk_fixture() }]); still_valid_config.validate().unwrap(); @@ -5388,8 +5380,11 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); invalid_config.disks = Some(vec![DiskConfig { - iommu: false, - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + iommu: false, + pci_segment: 1, + ..Default::default() + }, ..disk_fixture() }]); assert_eq!( diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 77ef0cc423..9cfbbc4e21 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2636,11 +2636,11 @@ impl DeviceManager { disk_cfg: &mut DiskConfig, is_hotplug: bool, ) -> DeviceManagerResult { - let id = if let Some(id) = &disk_cfg.id { + let id = if let Some(id) = &disk_cfg.pci_common.id { id.clone() } else { let id = self.next_device_name(DISK_DEVICE_NAME_PREFIX)?; - disk_cfg.id = Some(id.clone()); + disk_cfg.pci_common.id = Some(id.clone()); id }; @@ -2822,7 +2822,7 @@ impl DeviceManager { let bw = rate_limiter_cfg.bandwidth.unwrap_or_default(); let ops = rate_limiter_cfg.ops.unwrap_or_default(); let mut rate_limit_group = RateLimiterGroup::new( - disk_cfg.id.as_ref().unwrap(), + disk_cfg.pci_common.id.as_ref().unwrap(), bw.size, bw.one_time_burst.unwrap_or(0), bw.refill_time, @@ -2865,7 +2865,7 @@ impl DeviceManager { .ok_or(DeviceManagerError::NoDiskPath)? .clone(), disk_cfg.readonly, - self.force_iommu | disk_cfg.iommu, + self.force_iommu | disk_cfg.pci_common.iommu, disk_cfg.num_queues, disk_cfg.queue_size, disk_cfg.serial.clone(), @@ -2913,9 +2913,9 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device, - iommu: disk_cfg.iommu, + iommu: disk_cfg.pci_common.iommu, id, - pci_segment: disk_cfg.pci_segment, + pci_segment: disk_cfg.pci_common.pci_segment, dma_handler: None, }) } @@ -5028,9 +5028,9 @@ impl DeviceManager { } pub fn add_disk(&mut self, disk_cfg: &mut DiskConfig) -> DeviceManagerResult { - self.validate_identifier(&disk_cfg.id)?; + self.validate_identifier(&disk_cfg.pci_common.id)?; - if disk_cfg.iommu && !self.is_iommu_segment(disk_cfg.pci_segment) { + if disk_cfg.pci_common.iommu && !self.is_iommu_segment(disk_cfg.pci_common.pci_segment) { return Err(DeviceManagerError::InvalidIommuHotplug); } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 1b20d76da6..d5465d9245 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -285,13 +285,13 @@ pub struct PciDeviceCommonConfig { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct DiskConfig { + #[serde(flatten)] + pub pci_common: PciDeviceCommonConfig, pub path: Option, #[serde(default)] pub readonly: bool, #[serde(default)] pub direct: bool, - #[serde(default)] - pub iommu: bool, #[serde(default = "default_diskconfig_num_queues")] pub num_queues: usize, #[serde(default = "default_diskconfig_queue_size")] @@ -303,8 +303,6 @@ pub struct DiskConfig { pub rate_limit_group: Option, #[serde(default)] pub rate_limiter_config: Option, - #[serde(default)] - pub id: Option, // For testing use only. Not exposed in API. #[serde(default)] pub disable_io_uring: bool, @@ -312,8 +310,6 @@ pub struct DiskConfig { #[serde(default)] pub disable_aio: bool, #[serde(default)] - pub pci_segment: u16, - #[serde(default)] pub serial: Option, #[serde(default)] pub queue_affinity: Option>, From 92c2cf0103e25505b2cf034c97676be7e76c809f Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 11:49:54 -0700 Subject: [PATCH 1144/1893] 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 --- vmm/src/api/http/http_endpoint.rs | 2 +- vmm/src/config.rs | 88 ++++++++++++++++--------------- vmm/src/device_manager.rs | 20 +++---- vmm/src/lib.rs | 4 +- vmm/src/vm_config.rs | 8 +-- 5 files changed, 61 insertions(+), 61 deletions(-) diff --git a/vmm/src/api/http/http_endpoint.rs b/vmm/src/api/http/http_endpoint.rs index 15ebfd2f9e..92b53ac68e 100644 --- a/vmm/src/api/http/http_endpoint.rs +++ b/vmm/src/api/http/http_endpoint.rs @@ -122,7 +122,7 @@ mod fds_helper { impl ConfigWithFDs for NetConfig { fn id(&self) -> Option<&str> { - self.id.as_deref() + self.pci_common.id.as_deref() } fn fds_from_http_body(&self) -> Option<&[RawFd]> { diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 9ff05bb5f6..5e53e8bdac 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1554,11 +1554,6 @@ impl NetConfig { .unwrap_or(Toggle(true)) .0; let mtu = parser.convert("mtu").map_err(Error::ParseNetwork)?; - let iommu = parser - .convert::("iommu") - .map_err(Error::ParseNetwork)? - .unwrap_or(Toggle(false)) - .0; let queue_size = parser .convert("queue_size") .map_err(Error::ParseNetwork)? @@ -1577,15 +1572,10 @@ impl NetConfig { .convert("vhost_mode") .map_err(Error::ParseNetwork)? .unwrap_or_default(); - let id = parser.get("id"); let fds = parser .convert::("fd") .map_err(Error::ParseNetwork)? .map(|v| v.0.iter().map(|e| *e as i32).collect()); - let pci_segment = parser - .convert("pci_segment") - .map_err(Error::ParseNetwork)? - .unwrap_or_default(); let bw_size = parser .convert("bw_size") .map_err(Error::ParseNetwork)? @@ -1637,23 +1627,23 @@ impl NetConfig { None }; + let pci_common = PciDeviceCommonConfig::parse(net)?; + let config = NetConfig { + pci_common, tap, ip, mask, mac, host_mac, mtu, - iommu, num_queues, queue_size, vhost_user, vhost_socket, vhost_mode, - id, fds, rate_limiter_config, - pci_segment, offload_tso, offload_ufo, offload_csum, @@ -1662,6 +1652,8 @@ impl NetConfig { } pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { + self.pci_common.validate(vm_config)?; + if self.num_queues < 2 { return Err(ValidationError::VnetQueueLowerThan2(self.num_queues)); } @@ -1689,23 +1681,10 @@ impl NetConfig { )); } - if self.vhost_user && self.iommu { + if self.vhost_user && self.pci_common.iommu { return Err(ValidationError::IommuNotSupported); } - if let Some(platform_config) = vm_config.platform.as_ref() { - if self.pci_segment >= platform_config.num_pci_segments { - return Err(ValidationError::InvalidPciSegment(self.pci_segment)); - } - - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() - && iommu_segments.contains(&self.pci_segment) - && !self.iommu - { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } - } - if let Some(mtu) = self.mtu && mtu < virtio_devices::net::MIN_MTU { @@ -2778,6 +2757,7 @@ impl RestoreConfig { for net_fds in vm_config.net.iter().flatten() { if let Some(expected_fds) = &net_fds.fds { let expected_id = net_fds + .pci_common .id .as_ref() .expect("Invalid 'NetConfig' with empty 'id' for VM restore."); @@ -3066,9 +3046,9 @@ impl VmConfig { return Err(ValidationError::VhostUserRequiresSharedMemory); } net.validate(self)?; - self.iommu |= net.iommu; + self.iommu |= net.pci_common.iommu; - Self::validate_identifier(&mut id_list, &net.id)?; + Self::validate_identifier(&mut id_list, &net.pci_common.id)?; } } @@ -3564,7 +3544,7 @@ impl VmConfig { // Remove if net device if let Some(net) = self.net.as_mut() { let len = net.len(); - net.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + net.retain(|dev| dev.pci_common.id.as_ref().map(|id| id.as_ref()) != Some(id)); removed |= net.len() != len; } @@ -4148,22 +4128,20 @@ mod unit_tests { fn net_fixture() -> NetConfig { NetConfig { + pci_common: PciDeviceCommonConfig::default(), tap: None, ip: None, mask: None, mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), mtu: None, - iommu: false, num_queues: 2, queue_size: 256, vhost_user: false, vhost_socket: None, vhost_mode: VhostMode::Client, - id: None, fds: None, rate_limiter_config: None, - pci_segment: 0, offload_tso: true, offload_ufo: true, offload_csum: true, @@ -4181,7 +4159,10 @@ mod unit_tests { assert_eq!( NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,id=mynet0")?, NetConfig { - id: Some("mynet0".to_owned()), + pci_common: PciDeviceCommonConfig { + id: Some("mynet0".to_owned()), + ..Default::default() + }, ..net_fixture() } ); @@ -4214,9 +4195,12 @@ mod unit_tests { "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,num_queues=4,queue_size=1024,iommu=on" )?, NetConfig { + pci_common: PciDeviceCommonConfig { + iommu: true, + ..Default::default() + }, num_queues: 4, queue_size: 1024, - iommu: true, ..net_fixture() } ); @@ -4840,19 +4824,28 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" preserved_fds: None, net: Some(vec![ NetConfig { - id: Some("net0".to_owned()), + pci_common: PciDeviceCommonConfig { + id: Some("net0".to_owned()), + ..Default::default() + }, num_queues: 2, fds: Some(vec![-1, -1, -1, -1]), ..net_fixture() }, NetConfig { - id: Some("net1".to_owned()), + pci_common: PciDeviceCommonConfig { + id: Some("net1".to_owned()), + ..Default::default() + }, num_queues: 1, fds: Some(vec![-1, -1]), ..net_fixture() }, NetConfig { - id: Some("net2".to_owned()), + pci_common: PciDeviceCommonConfig { + id: Some("net2".to_owned()), + ..Default::default() + }, fds: None, ..net_fixture() }, @@ -4947,7 +4940,10 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" resume: false, }; snapshot_vm_config.net = Some(vec![NetConfig { - id: Some("net2".to_owned()), + pci_common: PciDeviceCommonConfig { + id: Some("net2".to_owned()), + ..Default::default() + }, fds: None, ..net_fixture() }]); @@ -5330,8 +5326,11 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); still_valid_config.net = Some(vec![NetConfig { - iommu: true, - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + iommu: true, + pci_segment: 1, + ..Default::default() + }, ..net_fixture() }]); still_valid_config.validate().unwrap(); @@ -5398,8 +5397,11 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); invalid_config.net = Some(vec![NetConfig { - iommu: false, - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + iommu: false, + pci_segment: 1, + ..Default::default() + }, ..net_fixture() }]); assert_eq!( diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 9cfbbc4e21..9a5bdff56f 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2937,11 +2937,11 @@ impl DeviceManager { &mut self, net_cfg: &mut NetConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &net_cfg.id { + let id = if let Some(id) = &net_cfg.pci_common.id { id.clone() } else { let id = self.next_device_name(NET_DEVICE_NAME_PREFIX)?; - net_cfg.id = Some(id.clone()); + net_cfg.pci_common.id = Some(id.clone()); id }; info!("Creating virtio-net device: {net_cfg:?}"); @@ -2999,7 +2999,7 @@ impl DeviceManager { Some(net_cfg.mac), &mut net_cfg.host_mac, net_cfg.mtu, - self.force_iommu | net_cfg.iommu, + self.force_iommu | net_cfg.pci_common.iommu, net_cfg.num_queues, net_cfg.queue_size, self.seccomp_action.clone(), @@ -3020,7 +3020,7 @@ impl DeviceManager { fds, Some(net_cfg.mac), net_cfg.mtu, - self.force_iommu | net_cfg.iommu, + self.force_iommu | net_cfg.pci_common.iommu, net_cfg.queue_size, self.seccomp_action.clone(), net_cfg.rate_limiter_config, @@ -3050,7 +3050,7 @@ impl DeviceManager { Some(net_cfg.mac), &mut net_cfg.host_mac, net_cfg.mtu, - self.force_iommu | net_cfg.iommu, + self.force_iommu | net_cfg.pci_common.iommu, net_cfg.num_queues, net_cfg.queue_size, self.seccomp_action.clone(), @@ -3083,9 +3083,9 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device, - iommu: net_cfg.iommu, + iommu: net_cfg.pci_common.iommu, id, - pci_segment: net_cfg.pci_segment, + pci_segment: net_cfg.pci_common.pci_segment, dma_handler: None, }) } @@ -4737,7 +4737,7 @@ impl DeviceManager { let nets = config.net.as_deref_mut().unwrap(); let net_dev_cfg = nets .iter_mut() - .find(|net| net.id.as_deref() == Some(id)) + .find(|net| net.pci_common.id.as_deref() == Some(id)) // unwrap: the device could not have been removed without an ID .unwrap(); let fds = net_dev_cfg.fds.take().unwrap_or(Vec::new()); @@ -5067,9 +5067,9 @@ impl DeviceManager { } pub fn add_net(&mut self, net_cfg: &mut NetConfig) -> DeviceManagerResult { - self.validate_identifier(&net_cfg.id)?; + self.validate_identifier(&net_cfg.pci_common.id)?; - if net_cfg.iommu && !self.is_iommu_segment(net_cfg.pci_segment) { + if net_cfg.pci_common.iommu && !self.is_iommu_segment(net_cfg.pci_common.pci_segment) { return Err(DeviceManagerError::InvalidIommuHotplug); } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index df94b54e22..66a2a104d0 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1879,7 +1879,9 @@ impl RequestHandler for Vmm { for net in restored_nets.iter() { for net_config in vm_net_configs.iter_mut() { // update only if the net dev is backed by FDs - if net_config.id.as_ref() == Some(&net.id) && net_config.fds.is_some() { + if net_config.pci_common.id.as_ref() == Some(&net.id) + && net_config.fds.is_some() + { net_config.fds.clone_from(&net.fds); } } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index d5465d9245..e318aba7d4 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -350,6 +350,8 @@ pub fn default_diskconfig_sparse() -> bool { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct NetConfig { + #[serde(flatten)] + pub pci_common: PciDeviceCommonConfig, #[serde(default = "default_netconfig_tap")] pub tap: Option, pub ip: Option, @@ -360,8 +362,6 @@ pub struct NetConfig { pub host_mac: Option, #[serde(default)] pub mtu: Option, - #[serde(default)] - pub iommu: bool, #[serde(default = "default_netconfig_num_queues")] pub num_queues: usize, #[serde(default = "default_netconfig_queue_size")] @@ -371,8 +371,6 @@ pub struct NetConfig { pub vhost_socket: Option, #[serde(default)] pub vhost_mode: VhostMode, - #[serde(default)] - pub id: Option, // Special deserialize handling: // Therefore, we don't serialize FDs, and whatever value is here after // deserialization is invalid. @@ -383,8 +381,6 @@ pub struct NetConfig { pub fds: Option>, #[serde(default)] pub rate_limiter_config: Option, - #[serde(default)] - pub pci_segment: u16, #[serde(default = "default_netconfig_true")] pub offload_tso: bool, #[serde(default = "default_netconfig_true")] From 40150dd72d288f7b2999e6ca08c88e22a18e4fbc Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 11:56:41 -0700 Subject: [PATCH 1145/1893] 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 --- vmm/src/config.rs | 42 +++++++++++++-------------------------- vmm/src/device_manager.rs | 8 ++++---- vmm/src/vm_config.rs | 6 ++---- 3 files changed, 20 insertions(+), 36 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 5e53e8bdac..a447b89f37 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1937,20 +1937,14 @@ impl FsConfig { .map_err(Error::ParseFileSystem)? .unwrap_or_else(default_fsconfig_num_queues); - let id = parser.get("id"); - - let pci_segment = parser - .convert("pci_segment") - .map_err(Error::ParseFileSystem)? - .unwrap_or_default(); + let pci_common = PciDeviceCommonConfig::parse(fs)?; Ok(FsConfig { + pci_common, tag, socket, num_queues, queue_size, - id, - pci_segment, }) } @@ -1962,21 +1956,11 @@ impl FsConfig { )); } - if let Some(platform_config) = vm_config.platform.as_ref() { - if self.pci_segment >= platform_config.num_pci_segments { - return Err(ValidationError::InvalidPciSegment(self.pci_segment)); - } - - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() - && iommu_segments.contains(&self.pci_segment) - { - return Err(ValidationError::IommuNotSupportedOnSegment( - self.pci_segment, - )); - } + if self.pci_common.iommu { + return Err(ValidationError::IommuNotSupported); } - Ok(()) + self.pci_common.validate(vm_config) } } @@ -3059,7 +3043,7 @@ impl VmConfig { for fs in fses { fs.validate(self)?; - Self::validate_identifier(&mut id_list, &fs.id)?; + Self::validate_identifier(&mut id_list, &fs.pci_common.id)?; } } @@ -3530,7 +3514,7 @@ impl VmConfig { // Remove if fs device if let Some(fs) = self.fs.as_mut() { let len = fs.len(); - fs.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + fs.retain(|dev| dev.pci_common.id.as_ref().map(|id| id.as_ref()) != Some(id)); removed |= fs.len() != len; } @@ -4256,12 +4240,11 @@ mod unit_tests { fn fs_fixture() -> FsConfig { FsConfig { + pci_common: PciDeviceCommonConfig::default(), socket: PathBuf::from("/tmp/sock"), tag: "mytag".to_owned(), num_queues: 1, queue_size: 1024, - id: None, - pci_segment: 0, } } @@ -5471,7 +5454,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" }]); assert_eq!( invalid_config.validate(), - Err(ValidationError::IommuNotSupportedOnSegment(1)) + Err(ValidationError::OnIommuSegment(1)) ); let mut invalid_config = valid_config.clone(); @@ -5495,12 +5478,15 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); invalid_config.fs = Some(vec![FsConfig { - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + pci_segment: 1, + ..Default::default() + }, ..fs_fixture() }]); assert_eq!( invalid_config.validate(), - Err(ValidationError::IommuNotSupportedOnSegment(1)) + Err(ValidationError::OnIommuSegment(1)) ); let mut invalid_config = valid_config.clone(); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 9a5bdff56f..c431fc7564 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3216,11 +3216,11 @@ impl DeviceManager { &mut self, fs_cfg: &mut FsConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &fs_cfg.id { + let id = if let Some(id) = &fs_cfg.pci_common.id { id.clone() } else { let id = self.next_device_name(FS_DEVICE_NAME_PREFIX)?; - fs_cfg.id = Some(id.clone()); + fs_cfg.pci_common.id = Some(id.clone()); id }; @@ -3257,7 +3257,7 @@ impl DeviceManager { as Arc>, iommu: false, id, - pci_segment: fs_cfg.pci_segment, + pci_segment: fs_cfg.pci_common.pci_segment, dma_handler: None, }) } else { @@ -5039,7 +5039,7 @@ impl DeviceManager { } pub fn add_fs(&mut self, fs_cfg: &mut FsConfig) -> DeviceManagerResult { - self.validate_identifier(&fs_cfg.id)?; + self.validate_identifier(&fs_cfg.pci_common.id)?; let device = self.make_virtio_fs_device(fs_cfg)?; self.hotplug_virtio_pci_device(device) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index e318aba7d4..847a79bdac 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -471,16 +471,14 @@ pub struct PvmemcontrolConfig {} #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct FsConfig { + #[serde(flatten)] + pub pci_common: PciDeviceCommonConfig, pub tag: String, pub socket: PathBuf, #[serde(default = "default_fsconfig_num_queues")] pub num_queues: usize, #[serde(default = "default_fsconfig_queue_size")] pub queue_size: u16, - #[serde(default)] - pub id: Option, - #[serde(default)] - pub pci_segment: u16, } pub fn default_fsconfig_num_queues() -> usize { From 0d3080e036c479e7791cc201f44239f0b0686f7b Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 11:59:42 -0700 Subject: [PATCH 1146/1893] 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 --- vmm/src/config.rs | 37 +++++++++++++------------------------ vmm/src/device_manager.rs | 8 ++++---- vmm/src/vm_config.rs | 6 ++---- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index a447b89f37..d3aad9d93b 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1861,11 +1861,7 @@ impl GenericVhostUserConfig { } _ => {} } - let id = parser.get("id"); - let pci_segment = parser - .convert("pci_segment") - .map_err(Error::ParseGenericVhostUser)? - .unwrap_or_default(); + let pci_common = PciDeviceCommonConfig::parse(vhost_user)?; let mut converted_queue_sizes: Vec = Vec::new(); for (offset, &queue_size) in queue_sizes.iter().enumerate() { match queue_size.try_into() { @@ -1879,30 +1875,19 @@ impl GenericVhostUserConfig { } Ok(GenericVhostUserConfig { + pci_common, socket: socket.into(), device_type, - id, - pci_segment, queue_sizes: converted_queue_sizes, }) } pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { - if let Some(platform_config) = vm_config.platform.as_ref() { - if self.pci_segment >= platform_config.num_pci_segments { - return Err(ValidationError::InvalidPciSegment(self.pci_segment)); - } - - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() - && iommu_segments.contains(&self.pci_segment) - { - return Err(ValidationError::IommuNotSupportedOnSegment( - self.pci_segment, - )); - } + if self.pci_common.iommu { + return Err(ValidationError::IommuNotSupported); } - Ok(()) + self.pci_common.validate(vm_config) } } @@ -3054,7 +3039,7 @@ impl VmConfig { for generic_vhost_user_device in generic_vhost_user_devices { generic_vhost_user_device.validate(self)?; - Self::validate_identifier(&mut id_list, &generic_vhost_user_device.id)?; + Self::validate_identifier(&mut id_list, &generic_vhost_user_device.pci_common.id)?; } } @@ -3521,7 +3506,8 @@ impl VmConfig { // Remove if generic vhost-user device if let Some(generic_vhost_user) = self.generic_vhost_user.as_mut() { let len = generic_vhost_user.len(); - generic_vhost_user.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + generic_vhost_user + .retain(|dev| dev.pci_common.id.as_ref().map(|id| id.as_ref()) != Some(id)); removed |= generic_vhost_user.len() != len; } @@ -4292,10 +4278,13 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" assert_eq!( config.unwrap(), GenericVhostUserConfig { + pci_common: PciDeviceCommonConfig { + id: Some(id.to_owned()), + pci_segment: u16::try_from(pci_segment).unwrap(), + ..Default::default() + }, socket: socket.into(), - id: Some(id.to_owned()), device_type: u32::try_from(virtio_id).unwrap(), - pci_segment: u16::try_from(pci_segment).unwrap(), queue_sizes: queue_sizes .0 .iter() diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index c431fc7564..cb3f7c0ef5 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3150,11 +3150,11 @@ impl DeviceManager { &mut self, generic_vhost_user_cfg: &mut GenericVhostUserConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &generic_vhost_user_cfg.id { + let id = if let Some(id) = &generic_vhost_user_cfg.pci_common.id { id.clone() } else { let id = self.next_device_name(GENERIC_VHOST_USER_DEVICE_NAME_PREFIX)?; - generic_vhost_user_cfg.id = Some(id.clone()); + generic_vhost_user_cfg.pci_common.id = Some(id.clone()); id }; @@ -3191,7 +3191,7 @@ impl DeviceManager { as Arc>, iommu: false, id, - pci_segment: generic_vhost_user_cfg.pci_segment, + pci_segment: generic_vhost_user_cfg.pci_common.pci_segment, dma_handler: None, }) } else { @@ -5049,7 +5049,7 @@ impl DeviceManager { &mut self, generic_vhost_user_cfg: &mut GenericVhostUserConfig, ) -> DeviceManagerResult { - self.validate_identifier(&generic_vhost_user_cfg.id)?; + self.validate_identifier(&generic_vhost_user_cfg.pci_common.id)?; let device = self.make_generic_vhost_user_device(generic_vhost_user_cfg)?; self.hotplug_virtio_pci_device(device) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 847a79bdac..b9b6609882 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -498,12 +498,10 @@ impl ApplyLandlock for FsConfig { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct GenericVhostUserConfig { + #[serde(flatten)] + pub pci_common: PciDeviceCommonConfig, pub socket: PathBuf, pub queue_sizes: Vec, - #[serde(default)] - pub id: Option, - #[serde(default)] - pub pci_segment: u16, pub device_type: u32, } From c9082570b0f589426409d8d144264041f168db7a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 12:05:29 -0700 Subject: [PATCH 1147/1893] 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 --- vmm/src/config.rs | 63 +++++++++++++++------------------------ vmm/src/device_manager.rs | 18 +++++------ vmm/src/vm_config.rs | 8 ++--- 3 files changed, 35 insertions(+), 54 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index d3aad9d93b..c9104007f1 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2061,52 +2061,28 @@ impl PmemConfig { .add("pci_segment"); parser.parse(pmem).map_err(Error::ParsePersistentMemory)?; + let pci_common = PciDeviceCommonConfig::parse(pmem)?; let file = PathBuf::from(parser.get("file").ok_or(Error::ParsePmemFileMissing)?); let size = parser .convert::("size") .map_err(Error::ParsePersistentMemory)? .map(|v| v.0); - let iommu = parser - .convert::("iommu") - .map_err(Error::ParsePersistentMemory)? - .unwrap_or(Toggle(false)) - .0; let discard_writes = parser .convert::("discard_writes") .map_err(Error::ParsePersistentMemory)? .unwrap_or(Toggle(false)) .0; - let id = parser.get("id"); - let pci_segment = parser - .convert("pci_segment") - .map_err(Error::ParsePersistentMemory)? - .unwrap_or_default(); Ok(PmemConfig { + pci_common, file, size, - iommu, discard_writes, - id, - pci_segment, }) } pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { - if let Some(platform_config) = vm_config.platform.as_ref() { - if self.pci_segment >= platform_config.num_pci_segments { - return Err(ValidationError::InvalidPciSegment(self.pci_segment)); - } - - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() - && iommu_segments.contains(&self.pci_segment) - && !self.iommu - { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } - } - - Ok(()) + self.pci_common.validate(vm_config) } } @@ -3046,9 +3022,9 @@ impl VmConfig { if let Some(pmems) = &self.pmem { for pmem in pmems { pmem.validate(self)?; - self.iommu |= pmem.iommu; + self.iommu |= pmem.pci_common.iommu; - Self::validate_identifier(&mut id_list, &pmem.id)?; + Self::validate_identifier(&mut id_list, &pmem.pci_common.id)?; } } @@ -3521,7 +3497,7 @@ impl VmConfig { // Remove if pmem device if let Some(pmem) = self.pmem.as_mut() { let len = pmem.len(); - pmem.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + pmem.retain(|dev| dev.pci_common.id.as_ref().map(|id| id.as_ref()) != Some(id)); removed |= pmem.len() != len; } @@ -4345,12 +4321,10 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" fn pmem_fixture() -> PmemConfig { PmemConfig { + pci_common: PciDeviceCommonConfig::default(), file: PathBuf::from("/tmp/pmem"), size: Some(128 << 20), - iommu: false, discard_writes: false, - id: None, - pci_segment: 0, } } @@ -4366,15 +4340,21 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" assert_eq!( PmemConfig::parse("file=/tmp/pmem,size=128M,id=mypmem0")?, PmemConfig { - id: Some("mypmem0".to_owned()), + pci_common: PciDeviceCommonConfig { + id: Some("mypmem0".to_owned()), + ..Default::default() + }, ..pmem_fixture() } ); assert_eq!( PmemConfig::parse("file=/tmp/pmem,size=128M,iommu=on,discard_writes=on")?, PmemConfig { + pci_common: PciDeviceCommonConfig { + iommu: true, + ..Default::default() + }, discard_writes: true, - iommu: true, ..pmem_fixture() } ); @@ -5313,8 +5293,11 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); still_valid_config.pmem = Some(vec![PmemConfig { - iommu: true, - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + iommu: true, + pci_segment: 1, + ..Default::default() + }, ..pmem_fixture() }]); still_valid_config.validate().unwrap(); @@ -5388,8 +5371,10 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); invalid_config.pmem = Some(vec![PmemConfig { - iommu: false, - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + pci_segment: 1, + ..Default::default() + }, ..pmem_fixture() }]); assert_eq!( diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index cb3f7c0ef5..061c80f62f 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3282,11 +3282,11 @@ impl DeviceManager { &mut self, pmem_cfg: &mut PmemConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &pmem_cfg.id { + let id = if let Some(id) = &pmem_cfg.pci_common.id { id.clone() } else { let id = self.next_device_name(PMEM_DEVICE_NAME_PREFIX)?; - pmem_cfg.id = Some(id.clone()); + pmem_cfg.pci_common.id = Some(id.clone()); id }; @@ -3358,7 +3358,7 @@ impl DeviceManager { let (region_base, region_size) = if let Some((base, size)) = region_range { // The memory needs to be 2MiB aligned in order to support // hugepages. - self.pci_segments[pmem_cfg.pci_segment as usize] + self.pci_segments[pmem_cfg.pci_common.pci_segment as usize] .mem64_allocator .lock() .unwrap() @@ -3373,7 +3373,7 @@ impl DeviceManager { } else { // The memory needs to be 2MiB aligned in order to support // hugepages. - let base = self.pci_segments[pmem_cfg.pci_segment as usize] + let base = self.pci_segments[pmem_cfg.pci_common.pci_segment as usize] .mem64_allocator .lock() .unwrap() @@ -3421,7 +3421,7 @@ impl DeviceManager { file, GuestAddress(region_base), mapping, - self.force_iommu | pmem_cfg.iommu, + self.force_iommu | pmem_cfg.pci_common.iommu, self.seccomp_action.clone(), self.exit_evt .try_clone() @@ -3444,9 +3444,9 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_pmem_device) as Arc>, - iommu: pmem_cfg.iommu, + iommu: pmem_cfg.pci_common.iommu, id, - pci_segment: pmem_cfg.pci_segment, + pci_segment: pmem_cfg.pci_common.pci_segment, dma_handler: None, }) } @@ -5056,9 +5056,9 @@ impl DeviceManager { } pub fn add_pmem(&mut self, pmem_cfg: &mut PmemConfig) -> DeviceManagerResult { - self.validate_identifier(&pmem_cfg.id)?; + self.validate_identifier(&pmem_cfg.pci_common.id)?; - if pmem_cfg.iommu && !self.is_iommu_segment(pmem_cfg.pci_segment) { + if pmem_cfg.pci_common.iommu && !self.is_iommu_segment(pmem_cfg.pci_common.pci_segment) { return Err(DeviceManagerError::InvalidIommuHotplug); } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index b9b6609882..079dc1ac61 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -514,17 +514,13 @@ impl ApplyLandlock for GenericVhostUserConfig { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct PmemConfig { + #[serde(flatten)] + pub pci_common: PciDeviceCommonConfig, pub file: PathBuf, #[serde(default)] pub size: Option, #[serde(default)] - pub iommu: bool, - #[serde(default)] pub discard_writes: bool, - #[serde(default)] - pub id: Option, - #[serde(default)] - pub pci_segment: u16, } impl ApplyLandlock for PmemConfig { From 47182201f9aadc1420df5bc266a6a44bdc361d0f Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 12:08:38 -0700 Subject: [PATCH 1148/1893] 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 --- vmm/src/config.rs | 63 ++++++++++++++++----------------------- vmm/src/device_manager.rs | 20 +++++++------ vmm/src/vm_config.rs | 8 ++--- 3 files changed, 38 insertions(+), 53 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index c9104007f1..c2c943020e 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2206,45 +2206,23 @@ impl DeviceConfig { .add("x_nv_gpudirect_clique"); parser.parse(device).map_err(Error::ParseDevice)?; + let pci_common = PciDeviceCommonConfig::parse(device)?; let path = parser .get("path") .map(PathBuf::from) .ok_or(Error::ParseDevicePathMissing)?; - let iommu = parser - .convert::("iommu") - .map_err(Error::ParseDevice)? - .unwrap_or(Toggle(false)) - .0; - let id = parser.get("id"); - let pci_segment = parser - .convert::("pci_segment") - .map_err(Error::ParseDevice)? - .unwrap_or_default(); let x_nv_gpudirect_clique = parser .convert::("x_nv_gpudirect_clique") .map_err(Error::ParseDevice)?; Ok(DeviceConfig { + pci_common, path, - iommu, - id, - pci_segment, x_nv_gpudirect_clique, }) } pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { - if let Some(platform_config) = vm_config.platform.as_ref() { - if self.pci_segment >= platform_config.num_pci_segments { - return Err(ValidationError::InvalidPciSegment(self.pci_segment)); - } - - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() - && iommu_segments.contains(&self.pci_segment) - && !self.iommu - { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } - } + self.pci_common.validate(vm_config)?; if self.x_nv_gpudirect_clique.is_some() { let vfio_p2p_dma = vm_config.platform.as_ref().is_none_or(|p| p.vfio_p2p_dma); @@ -3120,9 +3098,9 @@ impl VmConfig { } device.validate(self)?; - self.iommu |= device.iommu; + self.iommu |= device.pci_common.iommu; - Self::validate_identifier(&mut id_list, &device.id)?; + Self::validate_identifier(&mut id_list, &device.pci_common.id)?; } } @@ -3454,7 +3432,7 @@ impl VmConfig { // Remove if VFIO device if let Some(devices) = self.devices.as_mut() { let len = devices.len(); - devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + devices.retain(|dev| dev.pci_common.id.as_ref().map(|id| id.as_ref()) != Some(id)); removed |= devices.len() != len; } @@ -4443,10 +4421,8 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" fn device_fixture() -> DeviceConfig { DeviceConfig { + pci_common: PciDeviceCommonConfig::default(), path: PathBuf::from("/path/to/device"), - id: None, - iommu: false, - pci_segment: 0, x_nv_gpudirect_clique: None, } } @@ -4463,7 +4439,10 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" assert_eq!( DeviceConfig::parse("path=/path/to/device,iommu=on")?, DeviceConfig { - iommu: true, + pci_common: PciDeviceCommonConfig { + iommu: true, + ..Default::default() + }, ..device_fixture() } ); @@ -4471,8 +4450,11 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" assert_eq!( DeviceConfig::parse("path=/path/to/device,iommu=on,id=mydevice0")?, DeviceConfig { - id: Some("mydevice0".to_owned()), - iommu: true, + pci_common: PciDeviceCommonConfig { + id: Some("mydevice0".to_owned()), + iommu: true, + ..Default::default() + }, ..device_fixture() } ); @@ -5308,8 +5290,11 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); still_valid_config.devices = Some(vec![DeviceConfig { - iommu: true, - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + iommu: true, + pci_segment: 1, + ..Default::default() + }, ..device_fixture() }]); still_valid_config.validate().unwrap(); @@ -5389,8 +5374,10 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); invalid_config.devices = Some(vec![DeviceConfig { - iommu: false, - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + pci_segment: 1, + ..Default::default() + }, ..device_fixture() }]); assert_eq!( diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 061c80f62f..da03c6b553 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3858,16 +3858,16 @@ impl DeviceManager { &mut self, device_cfg: &mut DeviceConfig, ) -> DeviceManagerResult<(PciBdf, String)> { - let vfio_name = if let Some(id) = &device_cfg.id { + let vfio_name = if let Some(id) = &device_cfg.pci_common.id { id.clone() } else { let id = self.next_device_name(VFIO_DEVICE_NAME_PREFIX)?; - device_cfg.id = Some(id.clone()); + device_cfg.pci_common.id = Some(id.clone()); id }; let (pci_segment_id, pci_device_bdf, resources) = - self.pci_resources(&vfio_name, device_cfg.pci_segment)?; + self.pci_resources(&vfio_name, device_cfg.pci_common.pci_segment)?; let mut needs_dma_mapping = false; @@ -3884,7 +3884,7 @@ impl DeviceManager { // container/group. The VFIO cdev and iommufd do not have such a // limitation, and this will be revised once we have VFIO cdev and // iommufd support. - let vfio_ops = if device_cfg.iommu { + let vfio_ops = if device_cfg.pci_common.iommu { let vfio_ops = self.create_vfio_ops()?; let vfio_mapping = Arc::new(VfioDmaMapping::new( @@ -3989,7 +3989,7 @@ impl DeviceManager { vfio_ops, self.msi_interrupt_manager.clone(), legacy_interrupt_group, - device_cfg.iommu, + device_cfg.pci_common.iommu, vfio_p2p_dma, pci_device_bdf, memory_manager.lock().unwrap().memory_slot_allocator(), @@ -4104,7 +4104,7 @@ impl DeviceManager { if let Some(device_list_cfg) = &mut devices { for device_cfg in device_list_cfg.iter_mut() { let (device_id, _) = self.add_passthrough_device(device_cfg)?; - if device_cfg.iommu && self.iommu_device.is_some() { + if device_cfg.pci_common.iommu && self.iommu_device.is_some() { iommu_attached_device_ids.push(device_id); } } @@ -4630,16 +4630,18 @@ impl DeviceManager { &mut self, device_cfg: &mut DeviceConfig, ) -> DeviceManagerResult { - self.validate_identifier(&device_cfg.id)?; + self.validate_identifier(&device_cfg.pci_common.id)?; - if device_cfg.iommu && !self.is_iommu_segment(device_cfg.pci_segment) { + if device_cfg.pci_common.iommu && !self.is_iommu_segment(device_cfg.pci_common.pci_segment) + { return Err(DeviceManagerError::InvalidIommuHotplug); } let (bdf, device_name) = self.add_passthrough_device(device_cfg)?; // Update the PCIU bitmap - self.pci_segments[device_cfg.pci_segment as usize].pci_devices_up |= 1 << bdf.device(); + self.pci_segments[device_cfg.pci_common.pci_segment as usize].pci_devices_up |= + 1 << bdf.device(); Ok(PciDeviceInfo { id: device_name, diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 079dc1ac61..f6eb8af687 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -607,14 +607,10 @@ impl ApplyLandlock for DebugConsoleConfig { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct DeviceConfig { + #[serde(flatten)] + pub pci_common: PciDeviceCommonConfig, pub path: PathBuf, #[serde(default)] - pub iommu: bool, - #[serde(default)] - pub id: Option, - #[serde(default)] - pub pci_segment: u16, - #[serde(default)] pub x_nv_gpudirect_clique: Option, } From 2c50be4753dbdb5d8b793ca2b7708c3a9fe935c7 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 12:11:01 -0700 Subject: [PATCH 1149/1893] 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 --- vmm/src/config.rs | 38 +++++++++++--------------------------- vmm/src/device_manager.rs | 11 ++++++----- vmm/src/vm_config.rs | 6 ++---- 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index c2c943020e..b0d0de1e48 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2244,39 +2244,21 @@ impl UserDeviceConfig { parser.add("socket").add("id").add("pci_segment"); parser.parse(user_device).map_err(Error::ParseUserDevice)?; + let pci_common = PciDeviceCommonConfig::parse(user_device)?; let socket = parser .get("socket") .map(PathBuf::from) .ok_or(Error::ParseUserDeviceSocketMissing)?; - let id = parser.get("id"); - let pci_segment = parser - .convert::("pci_segment") - .map_err(Error::ParseUserDevice)? - .unwrap_or_default(); - Ok(UserDeviceConfig { - socket, - id, - pci_segment, - }) + Ok(UserDeviceConfig { pci_common, socket }) } pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { - if let Some(platform_config) = vm_config.platform.as_ref() { - if self.pci_segment >= platform_config.num_pci_segments { - return Err(ValidationError::InvalidPciSegment(self.pci_segment)); - } - - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() - && iommu_segments.contains(&self.pci_segment) - { - return Err(ValidationError::IommuNotSupportedOnSegment( - self.pci_segment, - )); - } + if self.pci_common.iommu { + return Err(ValidationError::IommuNotSupported); } - Ok(()) + self.pci_common.validate(vm_config) } } @@ -3052,7 +3034,7 @@ impl VmConfig { for user_device in user_devices { user_device.validate(self)?; - Self::validate_identifier(&mut id_list, &user_device.id)?; + Self::validate_identifier(&mut id_list, &user_device.pci_common.id)?; } } @@ -3439,7 +3421,7 @@ impl VmConfig { // Remove if VFIO user device if let Some(user_devices) = self.user_devices.as_mut() { let len = user_devices.len(); - user_devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + user_devices.retain(|dev| dev.pci_common.id.as_ref().map(|id| id.as_ref()) != Some(id)); removed |= user_devices.len() != len; } @@ -5409,9 +5391,11 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); invalid_config.user_devices = Some(vec![UserDeviceConfig { - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + pci_segment: 1, + ..Default::default() + }, socket: PathBuf::new(), - id: None, }]); assert_eq!( invalid_config.validate(), diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index da03c6b553..43eb7257cc 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4120,16 +4120,16 @@ impl DeviceManager { &mut self, device_cfg: &mut UserDeviceConfig, ) -> DeviceManagerResult<(PciBdf, String)> { - let vfio_user_name = if let Some(id) = &device_cfg.id { + let vfio_user_name = if let Some(id) = &device_cfg.pci_common.id { id.clone() } else { let id = self.next_device_name(VFIO_USER_DEVICE_NAME_PREFIX)?; - device_cfg.id = Some(id.clone()); + device_cfg.pci_common.id = Some(id.clone()); id }; let (pci_segment_id, pci_device_bdf, resources) = - self.pci_resources(&vfio_user_name, device_cfg.pci_segment)?; + self.pci_resources(&vfio_user_name, device_cfg.pci_common.pci_segment)?; let legacy_interrupt_group = if let Some(legacy_interrupt_manager) = &self.legacy_interrupt_manager { @@ -4653,12 +4653,13 @@ impl DeviceManager { &mut self, device_cfg: &mut UserDeviceConfig, ) -> DeviceManagerResult { - self.validate_identifier(&device_cfg.id)?; + self.validate_identifier(&device_cfg.pci_common.id)?; let (bdf, device_name) = self.add_vfio_user_device(device_cfg)?; // Update the PCIU bitmap - self.pci_segments[device_cfg.pci_segment as usize].pci_devices_up |= 1 << bdf.device(); + self.pci_segments[device_cfg.pci_common.pci_segment as usize].pci_devices_up |= + 1 << bdf.device(); Ok(PciDeviceInfo { id: device_name, diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index f6eb8af687..6afdb8f6c0 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -633,11 +633,9 @@ impl ApplyLandlock for DeviceConfig { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct UserDeviceConfig { + #[serde(flatten)] + pub pci_common: PciDeviceCommonConfig, pub socket: PathBuf, - #[serde(default)] - pub id: Option, - #[serde(default)] - pub pci_segment: u16, } impl ApplyLandlock for UserDeviceConfig { From ece77c3c52a8a9d4b297386c6f45604cb3c81b52 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 12:14:02 -0700 Subject: [PATCH 1150/1893] 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 --- vmm/src/config.rs | 50 ++++++++++++--------------------------- vmm/src/device_manager.rs | 12 +++++----- vmm/src/vm_config.rs | 8 ++----- 3 files changed, 23 insertions(+), 47 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index b0d0de1e48..e7ae70564a 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2277,6 +2277,7 @@ impl VdpaConfig { .add("pci_segment"); parser.parse(vdpa).map_err(Error::ParseVdpa)?; + let pci_common = PciDeviceCommonConfig::parse(vdpa)?; let path = parser .get("path") .map(PathBuf::from) @@ -2285,41 +2286,16 @@ impl VdpaConfig { .convert("num_queues") .map_err(Error::ParseVdpa)? .unwrap_or_else(default_vdpaconfig_num_queues); - let iommu = parser - .convert::("iommu") - .map_err(Error::ParseVdpa)? - .unwrap_or(Toggle(false)) - .0; - let id = parser.get("id"); - let pci_segment = parser - .convert("pci_segment") - .map_err(Error::ParseVdpa)? - .unwrap_or_default(); Ok(VdpaConfig { + pci_common, path, num_queues, - iommu, - id, - pci_segment, }) } pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { - if let Some(platform_config) = vm_config.platform.as_ref() { - if self.pci_segment >= platform_config.num_pci_segments { - return Err(ValidationError::InvalidPciSegment(self.pci_segment)); - } - - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() - && iommu_segments.contains(&self.pci_segment) - && !self.iommu - { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } - } - - Ok(()) + self.pci_common.validate(vm_config) } } @@ -3041,9 +3017,9 @@ impl VmConfig { if let Some(vdpa_devices) = &self.vdpa { for vdpa_device in vdpa_devices { vdpa_device.validate(self)?; - self.iommu |= vdpa_device.iommu; + self.iommu |= vdpa_device.pci_common.iommu; - Self::validate_identifier(&mut id_list, &vdpa_device.id)?; + Self::validate_identifier(&mut id_list, &vdpa_device.pci_common.id)?; } } @@ -3464,7 +3440,7 @@ impl VmConfig { // Remove if vDPA device if let Some(vdpa) = self.vdpa.as_mut() { let len = vdpa.len(); - vdpa.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + vdpa.retain(|dev| dev.pci_common.id.as_ref().map(|id| id.as_ref()) != Some(id)); removed |= vdpa.len() != len; } @@ -4446,11 +4422,9 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" fn vdpa_fixture() -> VdpaConfig { VdpaConfig { + pci_common: PciDeviceCommonConfig::default(), path: PathBuf::from("/dev/vhost-vdpa"), num_queues: 1, - iommu: false, - id: None, - pci_segment: 0, } } @@ -4462,8 +4436,11 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" assert_eq!( VdpaConfig::parse("path=/dev/vhost-vdpa,num_queues=2,id=my_vdpa")?, VdpaConfig { + pci_common: PciDeviceCommonConfig { + id: Some("my_vdpa".to_owned()), + ..Default::default() + }, num_queues: 2, - id: Some("my_vdpa".to_owned()), ..vdpa_fixture() } ); @@ -5408,7 +5385,10 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); invalid_config.vdpa = Some(vec![VdpaConfig { - pci_segment: 1, + pci_common: PciDeviceCommonConfig { + pci_segment: 1, + ..Default::default() + }, ..vdpa_fixture() }]); assert_eq!( diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 43eb7257cc..d16b9c2539 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3715,11 +3715,11 @@ impl DeviceManager { &mut self, vdpa_cfg: &mut VdpaConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &vdpa_cfg.id { + let id = if let Some(id) = &vdpa_cfg.pci_common.id { id.clone() } else { let id = self.next_device_name(VDPA_DEVICE_NAME_PREFIX)?; - vdpa_cfg.id = Some(id.clone()); + vdpa_cfg.pci_common.id = Some(id.clone()); id }; @@ -3755,9 +3755,9 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device: vdpa_device as Arc>, - iommu: vdpa_cfg.iommu, + iommu: vdpa_cfg.pci_common.iommu, id, - pci_segment: vdpa_cfg.pci_segment, + pci_segment: vdpa_cfg.pci_common.pci_segment, dma_handler: Some(vdpa_mapping), }) } @@ -5081,9 +5081,9 @@ impl DeviceManager { } pub fn add_vdpa(&mut self, vdpa_cfg: &mut VdpaConfig) -> DeviceManagerResult { - self.validate_identifier(&vdpa_cfg.id)?; + self.validate_identifier(&vdpa_cfg.pci_common.id)?; - if vdpa_cfg.iommu && !self.is_iommu_segment(vdpa_cfg.pci_segment) { + if vdpa_cfg.pci_common.iommu && !self.is_iommu_segment(vdpa_cfg.pci_common.pci_segment) { return Err(DeviceManagerError::InvalidIommuHotplug); } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 6afdb8f6c0..e915f955b7 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -647,15 +647,11 @@ impl ApplyLandlock for UserDeviceConfig { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct VdpaConfig { + #[serde(flatten)] + pub pci_common: PciDeviceCommonConfig, pub path: PathBuf, #[serde(default = "default_vdpaconfig_num_queues")] pub num_queues: usize, - #[serde(default)] - pub iommu: bool, - #[serde(default)] - pub id: Option, - #[serde(default)] - pub pci_segment: u16, } pub fn default_vdpaconfig_num_queues() -> usize { From 37b1ed1b84f09b5b8e7d6b7935054cfe91296f13 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 12:16:49 -0700 Subject: [PATCH 1151/1893] 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 --- vmm/src/config.rs | 62 +++++++++++++-------------------------- vmm/src/device_manager.rs | 14 ++++----- vmm/src/vm_config.rs | 8 ++--- 3 files changed, 29 insertions(+), 55 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index e7ae70564a..4b7e4a8ff3 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -2313,49 +2313,25 @@ impl VsockConfig { .add("pci_segment"); parser.parse(vsock).map_err(Error::ParseVsock)?; + let pci_common = PciDeviceCommonConfig::parse(vsock)?; let socket = parser .get("socket") .map(PathBuf::from) .ok_or(Error::ParseVsockSockMissing)?; - let iommu = parser - .convert::("iommu") - .map_err(Error::ParseVsock)? - .unwrap_or(Toggle(false)) - .0; let cid = parser .convert("cid") .map_err(Error::ParseVsock)? .ok_or(Error::ParseVsockCidMissing)?; - let id = parser.get("id"); - let pci_segment = parser - .convert("pci_segment") - .map_err(Error::ParseVsock)? - .unwrap_or_default(); Ok(VsockConfig { + pci_common, cid, socket, - iommu, - id, - pci_segment, }) } pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { - if let Some(platform_config) = vm_config.platform.as_ref() { - if self.pci_segment >= platform_config.num_pci_segments { - return Err(ValidationError::InvalidPciSegment(self.pci_segment)); - } - - if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() - && iommu_segments.contains(&self.pci_segment) - && !self.iommu - { - return Err(ValidationError::OnIommuSegment(self.pci_segment)); - } - } - - Ok(()) + self.pci_common.validate(vm_config) } } @@ -3064,9 +3040,9 @@ impl VmConfig { if let Some(vsock) = &self.vsock { vsock.validate(self)?; - self.iommu |= vsock.iommu; + self.iommu |= vsock.pci_common.iommu; - Self::validate_identifier(&mut id_list, &vsock.id)?; + Self::validate_identifier(&mut id_list, &vsock.pci_common.id)?; } let num_pci_segments = match &self.platform { @@ -3446,7 +3422,7 @@ impl VmConfig { // Remove if vsock device if let Some(vsock) = self.vsock.as_ref() - && vsock.id.as_ref().map(|id| id.as_ref()) == Some(id) + && vsock.pci_common.id.as_ref().map(|id| id.as_ref()) == Some(id) { self.vsock = None; removed = true; @@ -4467,21 +4443,20 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" assert_eq!( VsockConfig::parse("socket=/tmp/sock,cid=3")?, VsockConfig { + pci_common: PciDeviceCommonConfig::default(), cid: 3, socket: PathBuf::from("/tmp/sock"), - iommu: false, - id: None, - pci_segment: 0, } ); assert_eq!( VsockConfig::parse("socket=/tmp/sock,cid=3,iommu=on")?, VsockConfig { + pci_common: PciDeviceCommonConfig { + iommu: true, + ..Default::default() + }, cid: 3, socket: PathBuf::from("/tmp/sock"), - iommu: true, - id: None, - pci_segment: 0, } ); Ok(()) @@ -5264,11 +5239,13 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); still_valid_config.vsock = Some(VsockConfig { + pci_common: PciDeviceCommonConfig { + iommu: true, + pci_segment: 1, + ..Default::default() + }, cid: 3, socket: PathBuf::new(), - id: None, - iommu: true, - pci_segment: 1, }); still_valid_config.validate().unwrap(); @@ -5350,11 +5327,12 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" ..platform_fixture() }); invalid_config.vsock = Some(VsockConfig { + pci_common: PciDeviceCommonConfig { + pci_segment: 1, + ..Default::default() + }, cid: 3, socket: PathBuf::new(), - id: None, - iommu: false, - pci_segment: 1, }); assert_eq!( invalid_config.validate(), diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index d16b9c2539..12c1e41a5b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3469,11 +3469,11 @@ impl DeviceManager { &mut self, vsock_cfg: &mut VsockConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &vsock_cfg.id { + let id = if let Some(id) = &vsock_cfg.pci_common.id { id.clone() } else { let id = self.next_device_name(VSOCK_DEVICE_NAME_PREFIX)?; - vsock_cfg.id = Some(id.clone()); + vsock_cfg.pci_common.id = Some(id.clone()); id }; @@ -3493,7 +3493,7 @@ impl DeviceManager { vsock_cfg.cid, vsock_cfg.socket.clone(), backend, - self.force_iommu | vsock_cfg.iommu, + self.force_iommu | vsock_cfg.pci_common.iommu, self.seccomp_action.clone(), self.exit_evt .try_clone() @@ -3515,9 +3515,9 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device: Arc::clone(&vsock_device) as Arc>, - iommu: vsock_cfg.iommu, + iommu: vsock_cfg.pci_common.iommu, id, - pci_segment: vsock_cfg.pci_segment, + pci_segment: vsock_cfg.pci_common.pci_segment, dma_handler: None, }) } @@ -5092,9 +5092,9 @@ impl DeviceManager { } pub fn add_vsock(&mut self, vsock_cfg: &mut VsockConfig) -> DeviceManagerResult { - self.validate_identifier(&vsock_cfg.id)?; + self.validate_identifier(&vsock_cfg.pci_common.id)?; - if vsock_cfg.iommu && !self.is_iommu_segment(vsock_cfg.pci_segment) { + if vsock_cfg.pci_common.iommu && !self.is_iommu_segment(vsock_cfg.pci_common.pci_segment) { return Err(DeviceManagerError::InvalidIommuHotplug); } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index e915f955b7..156650cb2e 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -667,14 +667,10 @@ impl ApplyLandlock for VdpaConfig { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct VsockConfig { + #[serde(flatten)] + pub pci_common: PciDeviceCommonConfig, pub cid: u32, pub socket: PathBuf, - #[serde(default)] - pub iommu: bool, - #[serde(default)] - pub id: Option, - #[serde(default)] - pub pci_segment: u16, } impl ApplyLandlock for VsockConfig { From dde28dc38aac8ce50e26c9017e208b854a2f8d8e Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 12:40:55 -0700 Subject: [PATCH 1152/1893] 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 --- vmm/src/config.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 4b7e4a8ff3..d80e389f58 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -320,11 +320,6 @@ pub enum ValidationError { /// GPUDirect clique requires P2P DMA #[error("Device with x_nv_gpudirect_clique requires vfio_p2p_dma=on")] GpuDirectCliqueRequiresP2pDma, - // On a IOMMU segment but IOMMU not supported - #[error( - "Device is on an IOMMU PCI segment ({0}) but does not support being placed behind IOMMU" - )] - IommuNotSupportedOnSegment(u16), // Identifier is not unique #[error("Identifier {0} is not unique")] IdentifierNotUnique(String), From 0c837abff2b4b9bfb817fef57bb659bf6777d3ae Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 4 Apr 2026 08:56:49 -0700 Subject: [PATCH 1153/1893] 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 --- vmm/src/config.rs | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index d80e389f58..8e0c4232af 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1195,10 +1195,13 @@ impl RateLimiterGroupConfig { } impl PciDeviceCommonConfig { + const OPTIONS: &[&str] = &["id", "pci_segment"]; + const OPTIONS_IOMMU: &[&str] = &["id", "iommu", "pci_segment"]; + pub fn parse(input: &str) -> Result { let mut parser = OptionParser::new(); - parser.add("id").add("iommu").add("pci_segment"); + parser.add_all(Self::OPTIONS_IOMMU); parser .parse_subset(input) @@ -1258,7 +1261,6 @@ impl DiskConfig { .add("path") .add("readonly") .add("direct") - .add("iommu") .add("queue_size") .add("num_queues") .add("vhost_user") @@ -1269,17 +1271,16 @@ impl DiskConfig { .add("ops_size") .add("ops_one_time_burst") .add("ops_refill_time") - .add("id") .add("_disable_io_uring") .add("_disable_aio") - .add("pci_segment") .add("serial") .add("rate_limit_group") .add("queue_affinity") .add("backing_files") .add("sparse") .add("image_type") - .add("lock_granularity"); + .add("lock_granularity") + .add_all(PciDeviceCommonConfig::OPTIONS_IOMMU); parser.parse(disk).map_err(Error::ParseDisk)?; @@ -1507,13 +1508,11 @@ impl NetConfig { .add("offload_ufo") .add("offload_csum") .add("mtu") - .add("iommu") .add("queue_size") .add("num_queues") .add("vhost_user") .add("socket") .add("vhost_mode") - .add("id") .add("fd") .add("bw_size") .add("bw_one_time_burst") @@ -1521,7 +1520,7 @@ impl NetConfig { .add("ops_size") .add("ops_one_time_burst") .add("ops_refill_time") - .add("pci_segment"); + .add_all(PciDeviceCommonConfig::OPTIONS_IOMMU); parser.parse(net).map_err(Error::ParseNetwork)?; let tap = parser.get("tap"); @@ -1772,8 +1771,7 @@ impl GenericVhostUserConfig { .add("virtio_id") .add("queue_sizes") .add("socket") - .add("id") - .add("pci_segment"); + .add_all(PciDeviceCommonConfig::OPTIONS); parser .parse(vhost_user) .map_err(Error::ParseGenericVhostUser)?; @@ -1898,8 +1896,7 @@ impl FsConfig { .add("queue_size") .add("num_queues") .add("socket") - .add("id") - .add("pci_segment"); + .add_all(PciDeviceCommonConfig::OPTIONS); parser.parse(fs).map_err(Error::ParseFileSystem)?; let tag = parser.get("tag").ok_or(Error::ParseFsTagMissing)?; @@ -2050,10 +2047,8 @@ impl PmemConfig { parser .add("size") .add("file") - .add("iommu") .add("discard_writes") - .add("id") - .add("pci_segment"); + .add_all(PciDeviceCommonConfig::OPTIONS_IOMMU); parser.parse(pmem).map_err(Error::ParsePersistentMemory)?; let pci_common = PciDeviceCommonConfig::parse(pmem)?; @@ -2195,9 +2190,7 @@ impl DeviceConfig { let mut parser = OptionParser::new(); parser .add("path") - .add("id") - .add("iommu") - .add("pci_segment") + .add_all(PciDeviceCommonConfig::OPTIONS_IOMMU) .add("x_nv_gpudirect_clique"); parser.parse(device).map_err(Error::ParseDevice)?; @@ -2236,7 +2229,7 @@ impl UserDeviceConfig { pub fn parse(user_device: &str) -> Result { let mut parser = OptionParser::new(); - parser.add("socket").add("id").add("pci_segment"); + parser.add("socket").add_all(PciDeviceCommonConfig::OPTIONS); parser.parse(user_device).map_err(Error::ParseUserDevice)?; let pci_common = PciDeviceCommonConfig::parse(user_device)?; @@ -2267,9 +2260,7 @@ impl VdpaConfig { parser .add("path") .add("num_queues") - .add("iommu") - .add("id") - .add("pci_segment"); + .add_all(PciDeviceCommonConfig::OPTIONS_IOMMU); parser.parse(vdpa).map_err(Error::ParseVdpa)?; let pci_common = PciDeviceCommonConfig::parse(vdpa)?; @@ -2303,9 +2294,7 @@ impl VsockConfig { parser .add("socket") .add("cid") - .add("iommu") - .add("id") - .add("pci_segment"); + .add_all(PciDeviceCommonConfig::OPTIONS_IOMMU); parser.parse(vsock).map_err(Error::ParseVsock)?; let pci_common = PciDeviceCommonConfig::parse(vsock)?; From 7ac877cc26c405c65542289a44eeda929b2070a4 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 3 Apr 2026 13:32:07 -0700 Subject: [PATCH 1154/1893] 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 --- vmm/src/device_manager.rs | 109 ++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 12c1e41a5b..d32f5b7716 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -132,8 +132,8 @@ use crate::serial_manager::{Error as SerialManagerError, SerialManager}; use crate::vm_config::IvshmemConfig; use crate::vm_config::{ ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT, - DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig, - UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PciDeviceCommonConfig, + PmemConfig, UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig, }; use crate::{DEVICE_MANAGER_SNAPSHOT_ID, GuestRegionMmap, PciDeviceInfo, device_node}; @@ -926,12 +926,19 @@ pub enum PciDeviceHandle { #[derive(Clone)] struct MetaVirtioDevice { virtio_device: Arc>, - iommu: bool, - id: String, - pci_segment: u16, + pci_common: PciDeviceCommonConfig, dma_handler: Option>, } +impl MetaVirtioDevice { + fn id(&self) -> &str { + self.pci_common + .id + .as_deref() + .expect("ID should have been assigned before use") + } +} + #[derive(Default)] pub struct AcpiPlatformAddresses { pub pm_timer_address: Option, @@ -1669,24 +1676,25 @@ impl DeviceManager { let mut iommu_attached_devices = Vec::new(); { for handle in self.virtio_devices.clone() { - let mapping: Option> = if handle.iommu { + let mapping: Option> = if handle.pci_common.iommu { self.iommu_mapping.clone() } else { None }; + let id = handle.id().to_owned(); let dev_id = self.add_virtio_pci_device( handle.virtio_device, &mapping, - &handle.id, - handle.pci_segment, + &id, + handle.pci_common.pci_segment, handle.dma_handler, )?; // Track device BDF for Generic Initiator support - self.device_id_to_bdf.insert(handle.id.clone(), dev_id); + self.device_id_to_bdf.insert(id, dev_id); - if handle.iommu { + if handle.pci_common.iommu { iommu_attached_devices.push(dev_id); } } @@ -2417,9 +2425,11 @@ impl DeviceManager { self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_console_device) as Arc>, - iommu: console_config.iommu, - id: id.clone(), - pci_segment: 0, + pci_common: PciDeviceCommonConfig { + id: Some(id.clone()), + iommu: console_config.iommu, + ..Default::default() + }, dma_handler: None, }); @@ -2913,9 +2923,7 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device, - iommu: disk_cfg.pci_common.iommu, - id, - pci_segment: disk_cfg.pci_common.pci_segment, + pci_common: disk_cfg.pci_common.clone(), dma_handler: None, }) } @@ -3083,9 +3091,7 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device, - iommu: net_cfg.pci_common.iommu, - id, - pci_segment: net_cfg.pci_common.pci_segment, + pci_common: net_cfg.pci_common.clone(), dma_handler: None, }) } @@ -3128,9 +3134,11 @@ impl DeviceManager { self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_rng_device) as Arc>, - iommu: rng_config.iommu, - id: id.clone(), - pci_segment: 0, + pci_common: PciDeviceCommonConfig { + id: Some(id.clone()), + iommu: rng_config.iommu, + ..Default::default() + }, dma_handler: None, }); @@ -3189,9 +3197,7 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device: Arc::clone(&generic_vhost_user_device) as Arc>, - iommu: false, - id, - pci_segment: generic_vhost_user_cfg.pci_common.pci_segment, + pci_common: generic_vhost_user_cfg.pci_common.clone(), dma_handler: None, }) } else { @@ -3255,9 +3261,7 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_fs_device) as Arc>, - iommu: false, - id, - pci_segment: fs_cfg.pci_common.pci_segment, + pci_common: fs_cfg.pci_common.clone(), dma_handler: None, }) } else { @@ -3444,9 +3448,7 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_pmem_device) as Arc>, - iommu: pmem_cfg.pci_common.iommu, - id, - pci_segment: pmem_cfg.pci_common.pci_segment, + pci_common: pmem_cfg.pci_common.clone(), dma_handler: None, }) } @@ -3515,9 +3517,7 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device: Arc::clone(&vsock_device) as Arc>, - iommu: vsock_cfg.pci_common.iommu, - id, - pci_segment: vsock_cfg.pci_common.pci_segment, + pci_common: vsock_cfg.pci_common.clone(), dma_handler: None, }) } @@ -3571,9 +3571,10 @@ impl DeviceManager { self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_mem_device) as Arc>, - iommu: false, - id: memory_zone_id.clone(), - pci_segment: 0, + pci_common: PciDeviceCommonConfig { + id: Some(memory_zone_id.clone()), + ..Default::default() + }, dma_handler: None, }); @@ -3658,9 +3659,10 @@ impl DeviceManager { self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_balloon_device) as Arc>, - iommu: false, - id: id.clone(), - pci_segment: 0, + pci_common: PciDeviceCommonConfig { + id: Some(id.clone()), + ..Default::default() + }, dma_handler: None, }); @@ -3697,9 +3699,10 @@ impl DeviceManager { self.virtio_devices.push(MetaVirtioDevice { virtio_device: Arc::clone(&virtio_watchdog_device) as Arc>, - iommu: false, - id: id.clone(), - pci_segment: 0, + pci_common: PciDeviceCommonConfig { + id: Some(id.clone()), + ..Default::default() + }, dma_handler: None, }); @@ -3755,9 +3758,7 @@ impl DeviceManager { Ok(MetaVirtioDevice { virtio_device: vdpa_device as Arc>, - iommu: vdpa_cfg.pci_common.iommu, - id, - pci_segment: vdpa_cfg.pci_common.pci_segment, + pci_common: vdpa_cfg.pci_common.clone(), dma_handler: Some(vdpa_mapping), }) } @@ -4555,7 +4556,7 @@ impl DeviceManager { .map_err(DeviceManagerError::UpdateMemoryForVirtioDevice)?; if let Some(dma_handler) = &handle.dma_handler - && !handle.iommu + && !handle.pci_common.iommu { let gpa = new_region.start_addr().0; let size = new_region.len(); @@ -4994,24 +4995,26 @@ impl DeviceManager { // for instance. self.virtio_devices.push(handle.clone()); - let mapping: Option> = if handle.iommu { + let mapping: Option> = if handle.pci_common.iommu { self.iommu_mapping.clone() } else { None }; + let id = handle.id().to_owned(); let bdf = self.add_virtio_pci_device( handle.virtio_device, &mapping, - &handle.id, - handle.pci_segment, + &id, + handle.pci_common.pci_segment, handle.dma_handler, )?; // Update the PCIU bitmap - self.pci_segments[handle.pci_segment as usize].pci_devices_up |= 1 << bdf.device(); + self.pci_segments[handle.pci_common.pci_segment as usize].pci_devices_up |= + 1 << bdf.device(); - Ok(PciDeviceInfo { id: handle.id, bdf }) + Ok(PciDeviceInfo { id, bdf }) } fn is_iommu_segment(&self, pci_segment_id: u16) -> bool { @@ -5108,7 +5111,7 @@ impl DeviceManager { for handle in &self.virtio_devices { let virtio_device = handle.virtio_device.lock().unwrap(); if let Some(device_counters) = virtio_device.counters() { - counters.insert(handle.id.clone(), device_counters.clone()); + counters.insert(handle.id().to_owned(), device_counters.clone()); } } From cc7e56fa078cabe57802312228a333184f6985f2 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 14 Apr 2026 08:20:59 +0100 Subject: [PATCH 1155/1893] 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 --- vmm/src/device_manager.rs | 91 +++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index d32f5b7716..91d9509f38 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2646,12 +2646,13 @@ impl DeviceManager { disk_cfg: &mut DiskConfig, is_hotplug: bool, ) -> DeviceManagerResult { - let id = if let Some(id) = &disk_cfg.pci_common.id { - id.clone() - } else { - let id = self.next_device_name(DISK_DEVICE_NAME_PREFIX)?; - disk_cfg.pci_common.id = Some(id.clone()); - id + let id = match disk_cfg.pci_common.id.as_ref() { + Some(id) => id.clone(), + None => disk_cfg + .pci_common + .id + .insert(self.next_device_name(DISK_DEVICE_NAME_PREFIX)?) + .clone(), }; info!("Creating virtio-block device: {disk_cfg:?}"); @@ -2945,12 +2946,13 @@ impl DeviceManager { &mut self, net_cfg: &mut NetConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &net_cfg.pci_common.id { - id.clone() - } else { - let id = self.next_device_name(NET_DEVICE_NAME_PREFIX)?; - net_cfg.pci_common.id = Some(id.clone()); - id + let id = match net_cfg.pci_common.id.as_ref() { + Some(id) => id.clone(), + None => net_cfg + .pci_common + .id + .insert(self.next_device_name(NET_DEVICE_NAME_PREFIX)?) + .clone(), }; info!("Creating virtio-net device: {net_cfg:?}"); @@ -3158,12 +3160,13 @@ impl DeviceManager { &mut self, generic_vhost_user_cfg: &mut GenericVhostUserConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &generic_vhost_user_cfg.pci_common.id { - id.clone() - } else { - let id = self.next_device_name(GENERIC_VHOST_USER_DEVICE_NAME_PREFIX)?; - generic_vhost_user_cfg.pci_common.id = Some(id.clone()); - id + let id = match generic_vhost_user_cfg.pci_common.id.as_ref() { + Some(id) => id.clone(), + None => generic_vhost_user_cfg + .pci_common + .id + .insert(self.next_device_name(GENERIC_VHOST_USER_DEVICE_NAME_PREFIX)?) + .clone(), }; info!("Creating generic vhost-user device: {generic_vhost_user_cfg:?}"); @@ -3222,12 +3225,13 @@ impl DeviceManager { &mut self, fs_cfg: &mut FsConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &fs_cfg.pci_common.id { - id.clone() - } else { - let id = self.next_device_name(FS_DEVICE_NAME_PREFIX)?; - fs_cfg.pci_common.id = Some(id.clone()); - id + let id = match fs_cfg.pci_common.id.as_ref() { + Some(id) => id.clone(), + None => fs_cfg + .pci_common + .id + .insert(self.next_device_name(FS_DEVICE_NAME_PREFIX)?) + .clone(), }; info!("Creating virtio-fs device: {fs_cfg:?}"); @@ -3286,12 +3290,13 @@ impl DeviceManager { &mut self, pmem_cfg: &mut PmemConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &pmem_cfg.pci_common.id { - id.clone() - } else { - let id = self.next_device_name(PMEM_DEVICE_NAME_PREFIX)?; - pmem_cfg.pci_common.id = Some(id.clone()); - id + let id = match pmem_cfg.pci_common.id.as_ref() { + Some(id) => id.clone(), + None => pmem_cfg + .pci_common + .id + .insert(self.next_device_name(PMEM_DEVICE_NAME_PREFIX)?) + .clone(), }; info!("Creating virtio-pmem device: {pmem_cfg:?}"); @@ -3471,12 +3476,13 @@ impl DeviceManager { &mut self, vsock_cfg: &mut VsockConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &vsock_cfg.pci_common.id { - id.clone() - } else { - let id = self.next_device_name(VSOCK_DEVICE_NAME_PREFIX)?; - vsock_cfg.pci_common.id = Some(id.clone()); - id + let id = match vsock_cfg.pci_common.id.as_ref() { + Some(id) => id.clone(), + None => vsock_cfg + .pci_common + .id + .insert(self.next_device_name(VSOCK_DEVICE_NAME_PREFIX)?) + .clone(), }; info!("Creating virtio-vsock device: {vsock_cfg:?}"); @@ -3718,12 +3724,13 @@ impl DeviceManager { &mut self, vdpa_cfg: &mut VdpaConfig, ) -> DeviceManagerResult { - let id = if let Some(id) = &vdpa_cfg.pci_common.id { - id.clone() - } else { - let id = self.next_device_name(VDPA_DEVICE_NAME_PREFIX)?; - vdpa_cfg.pci_common.id = Some(id.clone()); - id + let id = match vdpa_cfg.pci_common.id.as_ref() { + Some(id) => id.clone(), + None => vdpa_cfg + .pci_common + .id + .insert(self.next_device_name(VDPA_DEVICE_NAME_PREFIX)?) + .clone(), }; info!("Creating vDPA device: {vdpa_cfg:?}"); From e6c8b5e8168d22e406c1b1de048d52d52982150e Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 13 Apr 2026 21:09:03 +0200 Subject: [PATCH 1156/1893] 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 --- cloud-hypervisor/tests/integration.rs | 444 +++++++++++++------------- 1 file changed, 225 insertions(+), 219 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 82c24a4f8f..3c4987fccb 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -6125,7 +6125,7 @@ mod ivshmem { // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); - common_sequential::snapshot_and_check_events( + snapshot_restore_common::snapshot_and_check_events( &api_socket_source, &snapshot_dir, &event_path, @@ -6224,19 +6224,57 @@ mod ivshmem { fn test_live_migration_ivshmem_local() { _test_live_migration_ivshmem(true); } -} -mod common_sequential { - use std::fs::remove_dir_all; + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_hotplug_virtiomem() { + snapshot_restore_common::_test_snapshot_restore(true, false); + } - use crate::*; + #[test] + #[cfg(not(feature = "mshv"))] // See issue #7437 + fn test_snapshot_restore_basic() { + snapshot_restore_common::_test_snapshot_restore(false, false); + } #[test] #[cfg(not(feature = "mshv"))] - fn test_memory_mergeable_on() { - test_memory_mergeable(true); + fn test_snapshot_restore_with_resume() { + snapshot_restore_common::_test_snapshot_restore(false, true); + } + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_uffd() { + snapshot_restore_common::_test_snapshot_restore_uffd("size=2G", &[], 1_920_000); + } + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_uffd_shared_memory() { + snapshot_restore_common::_test_snapshot_restore_uffd("size=512M,shared=on", &[], 480_000); + } + + #[test] + #[cfg(not(feature = "mshv"))] // See issue #7437 + #[cfg(target_arch = "x86_64")] + fn test_snapshot_restore_pvpanic() { + snapshot_restore_common::_test_snapshot_restore_devices(true); } + #[test] + fn test_virtio_pmem_persist_writes() { + test_virtio_pmem(false, false); + } +} + +#[cfg(not(feature = "mshv"))] +mod snapshot_restore_common { + use std::fs::remove_dir_all; + use std::process::Command; + + use crate::*; + pub(crate) fn snapshot_and_check_events( api_socket: &str, snapshot_dir: &str, @@ -6282,28 +6320,7 @@ mod common_sequential { })); } - // One thing to note about this test. The virtio-net device is heavily used - // through each ssh command. There's no need to perform a dedicated test to - // verify the migration went well for virtio-net. - #[test] - #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_hotplug_virtiomem() { - _test_snapshot_restore(true, false); - } - - #[test] - #[cfg(not(feature = "mshv"))] // See issue #7437 - fn test_snapshot_restore_basic() { - _test_snapshot_restore(false, false); - } - - #[test] - #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_with_resume() { - _test_snapshot_restore(false, true); - } - - fn _test_snapshot_restore(use_hotplug: bool, use_resume_option: bool) { + pub(crate) fn _test_snapshot_restore(use_hotplug: bool, use_resume_option: bool) { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -6427,7 +6444,11 @@ mod common_sequential { thread::sleep(std::time::Duration::new(10, 0)); } - snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); + snapshot_restore_common::snapshot_and_check_events( + &api_socket_source, + &snapshot_dir, + &event_path, + ); }); // Shutdown the source VM and check console output @@ -6587,38 +6608,7 @@ mod common_sequential { handle_child_output(r, &output); } - #[test] - #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_uffd() { - _test_snapshot_restore_uffd("size=2G", &[], 1_920_000); - } - - #[test] - #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_uffd_shared_memory() { - _test_snapshot_restore_uffd("size=512M,shared=on", &[], 480_000); - } - - #[test] - #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_uffd_hugepage_zone() { - if !exec_host_command_status( - "grep -q '^Hugepagesize:[[:space:]]*2048 kB' /proc/meminfo && test $(awk '/HugePages_Free/ {print $2}' /proc/meminfo) -ge 256", - ) - .success() - { - println!("SKIPPED: not enough free 2MiB hugepages for UFFD restore test"); - return; - } - - _test_snapshot_restore_uffd( - "size=0", - &["id=mem0,size=512M,hugepages=on,hugepage_size=2M"], - 480_000, - ); - } - - fn _test_snapshot_restore_uffd( + pub(crate) fn _test_snapshot_restore_uffd( memory_config: &str, memory_zone_config: &[&str], min_total_memory_kib: u32, @@ -6757,6 +6747,169 @@ mod common_sequential { let _ = remove_dir_all(snapshot_dir.as_str()); } + pub(crate) fn _test_snapshot_restore_devices(pvpanic: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); + + let device_params = { + let mut data = vec![]; + if pvpanic { + data.push(String::from("--pvpanic")); + } + data + }; + + let socket = temp_vsock_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_source]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=1G"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .default_disks() + .default_net() + .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args(device_params) + .capture_output() + .spawn() + .unwrap(); + + let console_text = String::from("On a branch floating down river a cricket, singing."); + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + + snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + Command::new("rm") + .arg("-f") + .arg(socket.as_str()) + .output() + .unwrap(); + + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_restored]) + .args([ + "--event-monitor", + format!("path={event_path_restored}").as_str(), + ]) + .args([ + "--restore", + format!("source_url=file://{snapshot_dir}").as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); + + let _ = remove_dir_all(snapshot_dir.as_str()); + + let r = std::panic::catch_unwind(|| { + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); + assert!(remote_command(&api_socket_restored, "resume", None)); + let latest_events = [ + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); + + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + guest.check_devices_common(Some(&socket), Some(&console_text), None); + + if pvpanic { + make_guest_panic(&guest); + thread::sleep(std::time::Duration::new(10, 0)); + + let expected_sequential_events = [&MetaEvent { + event: "panic".to_string(), + device_id: None, + }]; + assert!(check_latest_events_exact( + &expected_sequential_events, + &event_path_restored + )); + } + }); + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); + + handle_child_output(r, &output); + } +} + +mod common_sequential { + #[cfg(not(feature = "mshv"))] + use std::fs::remove_dir_all; + + #[cfg(not(feature = "mshv"))] + use crate::*; + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_memory_mergeable_on() { + test_memory_mergeable(true); + } + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_uffd_hugepage_zone() { + if !exec_host_command_status( + "grep -q '^Hugepagesize:[[:space:]]*2048 kB' /proc/meminfo && test $(awk '/HugePages_Free/ {print $2}' /proc/meminfo) -ge 256", + ) + .success() + { + println!("SKIPPED: not enough free 2MiB hugepages for UFFD restore test"); + return; + } + + snapshot_restore_common::_test_snapshot_restore_uffd( + "size=0", + &["id=mem0,size=512M,hugepages=on,hugepage_size=2M"], + 480_000, + ); + } + #[test] #[cfg(not(feature = "mshv"))] // See issue #7437 #[ignore = "See #6970"] @@ -6843,7 +6996,11 @@ mod common_sequential { // Check the guest virtio-devices, e.g. block, rng, vsock, console, and net guest.check_devices_common(None, Some(&console_text), None); - snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); + snapshot_restore_common::snapshot_and_check_events( + &api_socket_source, + &snapshot_dir, + &event_path, + ); }); // Shutdown the source VM and check console output @@ -6981,155 +7138,6 @@ mod common_sequential { handle_child_output(r, &output); } - #[test] - #[cfg(not(feature = "mshv"))] // See issue #7437 - #[cfg(target_arch = "x86_64")] - fn test_snapshot_restore_pvpanic() { - _test_snapshot_restore_devices(true); - } - - fn _test_snapshot_restore_devices(pvpanic: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - - let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); - - let device_params = { - let mut data = vec![]; - if pvpanic { - data.push(String::from("--pvpanic")); - } - data - }; - - let socket = temp_vsock_path(&guest.tmp_dir); - let event_path = temp_event_monitor_path(&guest.tmp_dir); - - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_source]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .args(["--cpus", "boot=2"]) - .args(["--memory", "size=1G"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .default_disks() - .default_net() - .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(device_params) - .capture_output() - .spawn() - .unwrap(); - - let console_text = String::from("On a branch floating down river a cricket, singing."); - // Create the snapshot directory - let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); - - snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); - }); - - // Shutdown the source VM and check console output - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); - - // Remove the vsock socket file. - Command::new("rm") - .arg("-f") - .arg(socket.as_str()) - .output() - .unwrap(); - - let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); - let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); - - // Restore the VM from the snapshot - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_restored]) - .args([ - "--event-monitor", - format!("path={event_path_restored}").as_str(), - ]) - .args([ - "--restore", - format!("source_url=file://{snapshot_dir}").as_str(), - ]) - .capture_output() - .spawn() - .unwrap(); - - let latest_events = [&MetaEvent { - event: "restored".to_string(), - device_id: None, - }]; - // Wait for the restored event to show up in the monitor file. - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); - - // Remove the snapshot dir - let _ = remove_dir_all(snapshot_dir.as_str()); - - let r = std::panic::catch_unwind(|| { - // Resume the VM - assert!(wait_until(Duration::from_secs(30), || remote_command( - &api_socket_restored, - "info", - None - ))); - assert!(remote_command(&api_socket_restored, "resume", None)); - let latest_events = [ - &MetaEvent { - event: "resuming".to_string(), - device_id: None, - }, - &MetaEvent { - event: "resumed".to_string(), - device_id: None, - }, - ]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); - - // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); - guest.check_devices_common(Some(&socket), Some(&console_text), None); - - if pvpanic { - // Trigger guest a panic - make_guest_panic(&guest); - // Wait a while for guest - thread::sleep(std::time::Duration::new(10, 0)); - - let expected_sequential_events = [&MetaEvent { - event: "panic".to_string(), - device_id: None, - }]; - assert!(check_latest_events_exact( - &expected_sequential_events, - &event_path_restored - )); - } - }); - // Shutdown the target VM and check console output - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); - - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); - }); - - handle_child_output(r, &output); - } - #[test] #[cfg(not(feature = "mshv"))] fn test_snapshot_restore_virtio_fs() { @@ -7189,8 +7197,11 @@ mod common_sequential { "sudo bash -c 'echo snapshot_test_data > mount_dir/snapshot_test_file'", ) .unwrap(); - - snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); + snapshot_restore_common::snapshot_and_check_events( + &api_socket_source, + &snapshot_dir, + &event_path, + ); }); // Shutdown the source VM @@ -7288,11 +7299,6 @@ mod common_sequential { let _ = std::fs::remove_file(shared_dir.join("snapshot_test_file")); let _ = std::fs::remove_file(shared_dir.join("post_restore_file")); } - - #[test] - fn test_virtio_pmem_persist_writes() { - test_virtio_pmem(false, false); - } } mod windows { From 98aa9d9c1212d051b235623aead4ee33666e9d34 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 14 Apr 2026 10:30:26 +0200 Subject: [PATCH 1157/1893] 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 --- cloud-hypervisor/tests/integration.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 3c4987fccb..ebf08d8417 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -8391,8 +8391,10 @@ mod vfio { })); assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); - // Check the VFIO device works when RAM is increased to 6GiB - assert!(guest.check_nvidia_gpu()); + // Check the VFIO device works when RAM is increased to 6GiB. + // After guest memory hotplug, the VMM must refresh VFIO/iommufd DMA + // mappings for the passthrough GPU. + assert!(wait_until(Duration::from_secs(10), || guest.check_nvidia_gpu())); }); let _ = child.kill(); From 73680c38c7d3e69bdfb6269609c58147fa2c499d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 3 Apr 2026 17:25:59 +0200 Subject: [PATCH 1158/1893] 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 --- .github/workflows/integration-windows.yaml | 2 +- scripts/run_integration_tests_windows_x86_64.sh | 2 +- test_infra/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration-windows.yaml b/.github/workflows/integration-windows.yaml index bb1f68158c..1010ab73ec 100644 --- a/.github/workflows/integration-windows.yaml +++ b/.github/workflows/integration-windows.yaml @@ -39,7 +39,7 @@ jobs: run: | set -eufo pipefail 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 }}" + 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 if: ${{ github.event_name != 'pull_request' }} timeout-minutes: 15 diff --git a/scripts/run_integration_tests_windows_x86_64.sh b/scripts/run_integration_tests_windows_x86_64.sh index 56d41c166e..6b358c5c28 100755 --- a/scripts/run_integration_tests_windows_x86_64.sh +++ b/scripts/run_integration_tests_windows_x86_64.sh @@ -13,7 +13,7 @@ test_features="" if [ "$hypervisor" = "mshv" ]; then test_features="--features mshv" fi -WIN_IMAGE_FILE="/root/workloads/windows-server-2022-amd64-2.raw" +WIN_IMAGE_FILE="/root/workloads/windows-server-2025-amd64-1.raw" WORKLOADS_DIR="/root/workloads" diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index ba10cd5b51..e32ee791fc 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -2412,7 +2412,7 @@ pub mod x86_64 { "jammy-server-cloudimg-amd64-custom-20241017-0-backing-uncompressed.qcow2"; pub const JAMMY_IMAGE_NAME_QCOW2_BACKING_RAW_FILE: &str = "jammy-server-cloudimg-amd64-custom-20241017-0-backing-raw.qcow2"; - pub const WINDOWS_IMAGE_NAME: &str = "windows-server-2022-amd64-2.raw"; + pub const WINDOWS_IMAGE_NAME: &str = "windows-server-2025-amd64-1.raw"; pub const OVMF_NAME: &str = "CLOUDHV.fd"; pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'IO-APIC.*ttyS0' /proc/interrupts || true"; } From 7f3dfe2154cbde10e74ae654049b45172949c85e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 13:52:04 +0100 Subject: [PATCH 1159/1893] 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 --- block/src/lib.rs | 1 + block/src/qcow_common.rs | 67 ++++++++++++++++++++++++++++++++++++++++ block/src/qcow_sync.rs | 61 ++---------------------------------- 3 files changed, 70 insertions(+), 59 deletions(-) create mode 100644 block/src/qcow_common.rs diff --git a/block/src/lib.rs b/block/src/lib.rs index 6d093daca4..ad25f4593c 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -18,6 +18,7 @@ pub mod fixed_vhd; pub mod fixed_vhd_async; pub mod fixed_vhd_sync; pub mod qcow; +pub(crate) mod qcow_common; pub mod qcow_sync; #[cfg(feature = "io_uring")] /// Async primitives based on `io-uring` diff --git a/block/src/qcow_common.rs b/block/src/qcow_common.rs new file mode 100644 index 0000000000..f118314257 --- /dev/null +++ b/block/src/qcow_common.rs @@ -0,0 +1,67 @@ +// Copyright © 2021 Intel Corporation +// +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +//! Shared helpers for QCOW2 sync and async backends. +//! +//! Position-independent I/O (`pread_exact`, `pwrite_all`) and iovec +//! scatter/gather helpers used by both `qcow_sync` and `qcow_async`. + +use std::io; +use std::os::fd::RawFd; + +// -- Position independent I/O helpers -- +// +// Duplicated file descriptors share the kernel file description and thus the +// file position. Using seek then read from multiple queues races on that +// shared position. pread64 and pwrite64 are atomic and never touch the position. + +/// Read exactly the requested bytes at offset, looping on short reads. +pub fn pread_exact(fd: RawFd, buf: &mut [u8], offset: u64) -> io::Result<()> { + let mut total = 0usize; + while total < buf.len() { + // SAFETY: buf and fd are valid for the lifetime of the call. + let ret = unsafe { + libc::pread64( + fd, + buf[total..].as_mut_ptr() as *mut libc::c_void, + buf.len() - total, + (offset + total as u64) as libc::off_t, + ) + }; + if ret < 0 { + return Err(io::Error::last_os_error()); + } + if ret == 0 { + return Err(io::Error::from(io::ErrorKind::UnexpectedEof)); + } + total += ret as usize; + } + Ok(()) +} + +/// Write all bytes to fd at offset, looping on short writes. +pub fn pwrite_all(fd: RawFd, buf: &[u8], offset: u64) -> io::Result<()> { + let mut total = 0usize; + while total < buf.len() { + // SAFETY: buf and fd are valid for the lifetime of the call. + let ret = unsafe { + libc::pwrite64( + fd, + buf[total..].as_ptr() as *const libc::c_void, + buf.len() - total, + (offset + total as u64) as libc::off_t, + ) + }; + if ret < 0 { + return Err(io::Error::last_os_error()); + } + if ret == 0 { + return Err(io::Error::other("pwrite64 wrote 0 bytes")); + } + total += ret as usize; + } + Ok(()) +} diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 7340a7aa40..cde534314e 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -5,7 +5,7 @@ use std::cmp::min; use std::collections::VecDeque; use std::fs::File; -use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}; use std::sync::Arc; use std::{fmt, io, ptr, slice}; @@ -22,6 +22,7 @@ use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{ BackingFile, BackingKind, Error as QcowError, MAX_NESTING_DEPTH, RawFile, parse_qcow, }; +use crate::qcow_common::{pread_exact, pwrite_all}; /// Raw backing file using pread64 on a duplicated fd. struct RawBacking { @@ -322,64 +323,6 @@ impl QcowSync { } } -// -- Position independent I/O helpers -- -// -// Duplicated file descriptors share the kernel file description and thus the -// file position. Using seek then read from multiple queues races on that -// shared position. pread64 and pwrite64 are atomic and never touch the position. - -/// Read exactly the requested bytes at offset, looping on short reads. -fn pread_exact(fd: RawFd, buf: &mut [u8], offset: u64) -> io::Result<()> { - let mut total = 0usize; - while total < buf.len() { - // SAFETY: buf and fd are valid for the lifetime of the call. - let ret = unsafe { - libc::pread64( - fd, - buf[total..].as_mut_ptr() as *mut libc::c_void, - buf.len() - total, - (offset + total as u64) as libc::off_t, - ) - }; - if ret < 0 { - return Err(io::Error::last_os_error()); - } - if ret == 0 { - return Err(io::Error::from(io::ErrorKind::UnexpectedEof)); - } - total += ret as usize; - } - Ok(()) -} - -/// Write all bytes to fd at offset, looping on short writes. -fn pwrite_all(fd: RawFd, buf: &[u8], offset: u64) -> io::Result<()> { - let mut total = 0usize; - while total < buf.len() { - // SAFETY: buf and fd are valid for the lifetime of the call. - let ret = unsafe { - libc::pwrite64( - fd, - buf[total..].as_ptr() as *const libc::c_void, - buf.len() - total, - (offset + total as u64) as libc::off_t, - ) - }; - if ret < 0 { - return Err(io::Error::last_os_error()); - } - if ret == 0 { - return Err(io::Error::other("pwrite64 wrote 0 bytes")); - } - total += ret as usize; - } - Ok(()) -} - -// -- iovec helper functions -- -// -// Operate on the iovec array as a flat byte stream. - /// Copy data into iovecs starting at the given byte offset. /// /// # Safety From 81f43f96c3eed97bd13f619738aae223a0fcb607 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 13:55:23 +0100 Subject: [PATCH 1160/1893] 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 --- block/src/qcow_common.rs | 96 +++++++++++++++++++++++++++++++++++++++- block/src/qcow_sync.rs | 95 ++------------------------------------- 2 files changed, 99 insertions(+), 92 deletions(-) diff --git a/block/src/qcow_common.rs b/block/src/qcow_common.rs index f118314257..dc492e36d2 100644 --- a/block/src/qcow_common.rs +++ b/block/src/qcow_common.rs @@ -9,8 +9,9 @@ //! Position-independent I/O (`pread_exact`, `pwrite_all`) and iovec //! scatter/gather helpers used by both `qcow_sync` and `qcow_async`. -use std::io; +use std::cmp::min; use std::os::fd::RawFd; +use std::{io, ptr, slice}; // -- Position independent I/O helpers -- // @@ -65,3 +66,96 @@ pub fn pwrite_all(fd: RawFd, buf: &[u8], offset: u64) -> io::Result<()> { } Ok(()) } + +// -- iovec helper functions -- +// +// Operate on the iovec array as a flat byte stream. + +/// Copy data into iovecs starting at the given byte offset. +/// +/// # Safety +/// Caller must ensure iovecs point to valid, writable memory of sufficient size. +pub unsafe fn scatter_to_iovecs(iovecs: &[libc::iovec], start: usize, data: &[u8]) { + let mut remaining = data; + let mut pos = 0usize; + for iov in iovecs { + let iov_end = pos + iov.iov_len; + if iov_end <= start || remaining.is_empty() { + pos = iov_end; + continue; + } + let iov_start = start.saturating_sub(pos); + let available = iov.iov_len - iov_start; + let count = min(available, remaining.len()); + // SAFETY: iov_base is valid for iov_len bytes per caller contract. + unsafe { + let dst = (iov.iov_base as *mut u8).add(iov_start); + ptr::copy_nonoverlapping(remaining.as_ptr(), dst, count); + } + remaining = &remaining[count..]; + if remaining.is_empty() { + break; + } + pos = iov_end; + } +} + +/// Zero fill iovecs starting at the given byte offset for the given length. +/// +/// # Safety +/// Caller must ensure iovecs point to valid, writable memory of sufficient size. +pub unsafe fn zero_fill_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) { + let mut remaining = len; + let mut pos = 0usize; + for iov in iovecs { + let iov_end = pos + iov.iov_len; + if iov_end <= start || remaining == 0 { + pos = iov_end; + continue; + } + let iov_start = start.saturating_sub(pos); + let available = iov.iov_len - iov_start; + let count = min(available, remaining); + // SAFETY: iov_base is valid for iov_len bytes per caller contract. + unsafe { + let dst = (iov.iov_base as *mut u8).add(iov_start); + ptr::write_bytes(dst, 0, count); + } + remaining -= count; + if remaining == 0 { + break; + } + pos = iov_end; + } +} + +/// Gather bytes from iovecs starting at the given byte offset into a Vec. +/// +/// # Safety +/// Caller must ensure iovecs point to valid, readable memory of sufficient size. +pub unsafe fn gather_from_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) -> Vec { + let mut result = Vec::with_capacity(len); + let mut remaining = len; + let mut pos = 0usize; + for iov in iovecs { + let iov_end = pos + iov.iov_len; + if iov_end <= start || remaining == 0 { + pos = iov_end; + continue; + } + let iov_start = start.saturating_sub(pos); + let available = iov.iov_len - iov_start; + let count = min(available, remaining); + // SAFETY: iov_base is valid for iov_len bytes per caller contract. + unsafe { + let src = (iov.iov_base as *const u8).add(iov_start); + result.extend_from_slice(slice::from_raw_parts(src, count)); + } + remaining -= count; + if remaining == 0 { + break; + } + pos = iov_end; + } + result +} diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index cde534314e..694fe80af0 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -7,7 +7,7 @@ use std::collections::VecDeque; use std::fs::File; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}; use std::sync::Arc; -use std::{fmt, io, ptr, slice}; +use std::{fmt, io}; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; @@ -22,7 +22,9 @@ use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{ BackingFile, BackingKind, Error as QcowError, MAX_NESTING_DEPTH, RawFile, parse_qcow, }; -use crate::qcow_common::{pread_exact, pwrite_all}; +use crate::qcow_common::{ + gather_from_iovecs, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, +}; /// Raw backing file using pread64 on a duplicated fd. struct RawBacking { @@ -323,95 +325,6 @@ impl QcowSync { } } -/// Copy data into iovecs starting at the given byte offset. -/// -/// # Safety -/// Caller must ensure iovecs point to valid, writable memory of sufficient size. -unsafe fn scatter_to_iovecs(iovecs: &[libc::iovec], start: usize, data: &[u8]) { - let mut remaining = data; - let mut pos = 0usize; - for iov in iovecs { - let iov_end = pos + iov.iov_len; - if iov_end <= start || remaining.is_empty() { - pos = iov_end; - continue; - } - let iov_start = start.saturating_sub(pos); - let available = iov.iov_len - iov_start; - let count = min(available, remaining.len()); - // SAFETY: iov_base is valid for iov_len bytes per caller contract. - unsafe { - let dst = (iov.iov_base as *mut u8).add(iov_start); - ptr::copy_nonoverlapping(remaining.as_ptr(), dst, count); - } - remaining = &remaining[count..]; - if remaining.is_empty() { - break; - } - pos = iov_end; - } -} - -/// Zero fill iovecs starting at the given byte offset for the given length. -/// -/// # Safety -/// Caller must ensure iovecs point to valid, writable memory of sufficient size. -unsafe fn zero_fill_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) { - let mut remaining = len; - let mut pos = 0usize; - for iov in iovecs { - let iov_end = pos + iov.iov_len; - if iov_end <= start || remaining == 0 { - pos = iov_end; - continue; - } - let iov_start = start.saturating_sub(pos); - let available = iov.iov_len - iov_start; - let count = min(available, remaining); - // SAFETY: iov_base is valid for iov_len bytes per caller contract. - unsafe { - let dst = (iov.iov_base as *mut u8).add(iov_start); - ptr::write_bytes(dst, 0, count); - } - remaining -= count; - if remaining == 0 { - break; - } - pos = iov_end; - } -} - -/// Gather bytes from iovecs starting at the given byte offset into a Vec. -/// -/// # Safety -/// Caller must ensure iovecs point to valid, readable memory of sufficient size. -unsafe fn gather_from_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) -> Vec { - let mut result = Vec::with_capacity(len); - let mut remaining = len; - let mut pos = 0usize; - for iov in iovecs { - let iov_end = pos + iov.iov_len; - if iov_end <= start || remaining == 0 { - pos = iov_end; - continue; - } - let iov_start = start.saturating_sub(pos); - let available = iov.iov_len - iov_start; - let count = min(available, remaining); - // SAFETY: iov_base is valid for iov_len bytes per caller contract. - unsafe { - let src = (iov.iov_base as *const u8).add(iov_start); - result.extend_from_slice(slice::from_raw_parts(src, count)); - } - remaining -= count; - if remaining == 0 { - break; - } - pos = iov_end; - } - result -} - impl AsyncIo for QcowSync { fn notifier(&self) -> &EventFd { &self.eventfd From e345299f4de1f5af7963aea7050f350d36fc9d68 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 16:03:24 +0100 Subject: [PATCH 1161/1893] 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 --- block/src/qcow/backing.rs | 40 +++++++++++++++++++++++++++++++++++++++ block/src/qcow/mod.rs | 1 + block/src/qcow_sync.rs | 28 +-------------------------- 3 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 block/src/qcow/backing.rs diff --git a/block/src/qcow/backing.rs b/block/src/qcow/backing.rs new file mode 100644 index 0000000000..1ad1b74b9f --- /dev/null +++ b/block/src/qcow/backing.rs @@ -0,0 +1,40 @@ +// 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::io; +use std::os::fd::{AsRawFd, OwnedFd}; + +use crate::qcow::metadata::BackingRead; +use crate::qcow_common::pread_exact; + +/// Raw backing file using pread64 on a duplicated fd. +pub(crate) struct RawBacking { + pub(crate) fd: OwnedFd, + pub(crate) virtual_size: u64, +} + +// SAFETY: The only I/O operation is pread64 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() { + pread_exact(self.fd.as_raw_fd(), buf, address) + } else { + pread_exact(self.fd.as_raw_fd(), &mut buf[..available], address)?; + buf[available..].fill(0); + Ok(()) + } + } +} diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 0c77b865cc..4fc4916f30 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -4,6 +4,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +pub(crate) mod backing; mod decoder; mod header; pub(crate) mod metadata; diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 694fe80af0..5665ad475b 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -15,6 +15,7 @@ use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; +use crate::qcow::backing::RawBacking; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, }; @@ -26,33 +27,6 @@ use crate::qcow_common::{ gather_from_iovecs, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; -/// Raw backing file using pread64 on a duplicated fd. -struct RawBacking { - fd: OwnedFd, - virtual_size: u64, -} - -// SAFETY: The only I/O operation is pread64 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() { - pread_exact(self.fd.as_raw_fd(), buf, address) - } else { - pread_exact(self.fd.as_raw_fd(), &mut buf[..available], address)?; - buf[available..].fill(0); - Ok(()) - } - } -} - /// QCOW2 backing file with RwLock metadata and pread64 data reads. /// /// Read only because backing files never receive writes. Nested backing From 5ec80d45ab5fb609678443ced7c6030166bb1f3a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 16:07:51 +0100 Subject: [PATCH 1162/1893] block: qcow: Move Qcow2MetadataBacking to qcow/backing Move the QCOW2 metadata backed reader into qcow/backing alongside RawBacking. Signed-off-by: Anatol Belski --- block/src/qcow/backing.rs | 92 ++++++++++++++++++++++++++++++++++++++- block/src/qcow_sync.rs | 89 +------------------------------------ 2 files changed, 92 insertions(+), 89 deletions(-) diff --git a/block/src/qcow/backing.rs b/block/src/qcow/backing.rs index 1ad1b74b9f..c688900c5e 100644 --- a/block/src/qcow/backing.rs +++ b/block/src/qcow/backing.rs @@ -8,8 +8,9 @@ use std::io; use std::os::fd::{AsRawFd, OwnedFd}; +use std::sync::Arc; -use crate::qcow::metadata::BackingRead; +use crate::qcow::metadata::{BackingRead, ClusterReadMapping, QcowMetadata}; use crate::qcow_common::pread_exact; /// Raw backing file using pread64 on a duplicated fd. @@ -38,3 +39,92 @@ impl BackingRead for RawBacking { } } } + +/// 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 Qcow2MetadataBacking { + pub(crate) metadata: Arc, + pub(crate) data_fd: OwnedFd, + pub(crate) backing_file: Option>, +} + +// SAFETY: All reads go through QcowMetadata which uses RwLock +// and pread64 which is position independent and thread safe. +unsafe impl Sync for Qcow2MetadataBacking {} + +impl BackingRead for Qcow2MetadataBacking { + 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 Qcow2MetadataBacking { + /// Resolve cluster mappings via metadata then read allocated clusters + /// with pread64. + 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, + } => { + pread_exact( + self.data_fd.as_raw_fd(), + &mut buf[buf_offset..buf_offset + length as usize], + host_offset, + )?; + buf_offset += length as usize; + } + ClusterReadMapping::Compressed { data } => { + let len = data.len(); + buf[buf_offset..buf_offset + len].copy_from_slice(&data); + buf_offset += len; + } + 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 Qcow2MetadataBacking { + fn drop(&mut self) { + self.metadata.shutdown(); + } +} diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 5665ad475b..483db80f50 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -15,7 +15,7 @@ use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; -use crate::qcow::backing::RawBacking; +use crate::qcow::backing::{Qcow2MetadataBacking, RawBacking}; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, }; @@ -27,93 +27,6 @@ use crate::qcow_common::{ gather_from_iovecs, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; -/// QCOW2 backing file with RwLock metadata and pread64 data reads. -/// -/// Read only because backing files never receive writes. Nested backing -/// files are handled recursively. -struct Qcow2MetadataBacking { - metadata: Arc, - data_fd: OwnedFd, - backing_file: Option>, -} - -// SAFETY: All reads go through QcowMetadata which uses RwLock -// and pread64 which is position independent and thread safe. -unsafe impl Sync for Qcow2MetadataBacking {} - -impl BackingRead for Qcow2MetadataBacking { - 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 Qcow2MetadataBacking { - /// Resolve cluster mappings via metadata then read allocated clusters - /// with pread64. - 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, - } => { - pread_exact( - self.data_fd.as_raw_fd(), - &mut buf[buf_offset..buf_offset + length as usize], - host_offset, - )?; - buf_offset += length as usize; - } - ClusterReadMapping::Compressed { data } => { - let len = data.len(); - buf[buf_offset..buf_offset + len].copy_from_slice(&data); - buf_offset += len; - } - 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 Qcow2MetadataBacking { - fn drop(&mut self) { - self.metadata.shutdown(); - } -} - /// Construct a thread safe backing file reader. fn shared_backing_from(bf: BackingFile) -> BlockResult> { let (kind, virtual_size) = bf.into_kind(); From 8cd2c957ef53da1e12cbf0d3ce991e9af2488f70 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 16:13:23 +0100 Subject: [PATCH 1163/1893] 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 --- block/src/qcow/backing.rs | 38 ++++++++++++++++++++++++++++++++++- block/src/qcow_sync.rs | 42 +++------------------------------------ 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/block/src/qcow/backing.rs b/block/src/qcow/backing.rs index c688900c5e..e5e037b0ad 100644 --- a/block/src/qcow/backing.rs +++ b/block/src/qcow/backing.rs @@ -7,10 +7,12 @@ //! Thread safe backing file readers for QCOW2 images. use std::io; -use std::os::fd::{AsRawFd, OwnedFd}; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}; use std::sync::Arc; +use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::metadata::{BackingRead, ClusterReadMapping, QcowMetadata}; +use crate::qcow::{BackingFile, BackingKind, Error as QcowError}; use crate::qcow_common::pread_exact; /// Raw backing file using pread64 on a duplicated fd. @@ -128,3 +130,37 @@ impl Drop for Qcow2MetadataBacking { self.metadata.shutdown(); } } + +/// Construct a thread safe backing file reader. +pub fn shared_backing_from(bf: BackingFile) -> BlockResult> { + let (kind, virtual_size) = bf.into_kind(); + + let dup_fd = |fd: BorrowedFd<'_>| -> BlockResult { + 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 fd = dup_fd(raw_file.as_fd())?; + Ok(Arc::new(RawBacking { fd, virtual_size })) + } + BackingKind::Qcow { inner, backing } => { + let data_fd = dup_fd(inner.raw_file.as_fd())?; + Ok(Arc::new(Qcow2MetadataBacking { + metadata: Arc::new(QcowMetadata::new(*inner)), + data_fd, + backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?, + })) + } + #[cfg(test)] + BackingKind::QcowFile(_) => { + unreachable!("QcowFile variant is only used by set_backing_file() in tests") + } + } +} diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 483db80f50..f948a3b5fd 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -5,7 +5,7 @@ use std::cmp::min; use std::collections::VecDeque; use std::fs::File; -use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}; +use std::os::fd::{AsFd, AsRawFd}; use std::sync::Arc; use std::{fmt, io}; @@ -15,52 +15,16 @@ use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; -use crate::qcow::backing::{Qcow2MetadataBacking, RawBacking}; +use crate::qcow::backing::shared_backing_from; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, }; use crate::qcow::qcow_raw_file::QcowRawFile; -use crate::qcow::{ - BackingFile, BackingKind, Error as QcowError, MAX_NESTING_DEPTH, RawFile, parse_qcow, -}; +use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; use crate::qcow_common::{ gather_from_iovecs, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; -/// Construct a thread safe backing file reader. -fn shared_backing_from(bf: BackingFile) -> BlockResult> { - let (kind, virtual_size) = bf.into_kind(); - - let dup_fd = |fd: BorrowedFd<'_>| -> BlockResult { - 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 fd = dup_fd(raw_file.as_fd())?; - Ok(Arc::new(RawBacking { fd, virtual_size })) - } - BackingKind::Qcow { inner, backing } => { - let data_fd = dup_fd(inner.raw_file.as_fd())?; - Ok(Arc::new(Qcow2MetadataBacking { - metadata: Arc::new(QcowMetadata::new(*inner)), - data_fd, - backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?, - })) - } - #[cfg(test)] - BackingKind::QcowFile(_) => { - unreachable!("QcowFile variant is only used by set_backing_file() in tests") - } - } -} - pub struct QcowDiskSync { metadata: Arc, /// Shared across queues, resolved once at construction. From 3422a8b25853469b8185b87abbedd161ebd7aa0d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 17:37:19 +0100 Subject: [PATCH 1164/1893] 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 --- block/src/lib.rs | 1 + block/src/qcow_async.rs | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 block/src/qcow_async.rs diff --git a/block/src/lib.rs b/block/src/lib.rs index ad25f4593c..c98f20ae87 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -18,6 +18,7 @@ pub mod fixed_vhd; pub mod fixed_vhd_async; pub mod fixed_vhd_sync; pub mod qcow; +pub mod qcow_async; pub(crate) mod qcow_common; pub mod qcow_sync; #[cfg(feature = "io_uring")] diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs new file mode 100644 index 0000000000..71bc6f4742 --- /dev/null +++ b/block/src/qcow_async.rs @@ -0,0 +1,70 @@ +// Copyright © 2021 Intel Corporation +// +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +//! QCOW2 async disk backend. + +use std::fmt; +use std::fs::File; +use std::sync::Arc; + +use crate::error::{BlockErrorKind, BlockResult, ErrorOp}; +use crate::qcow::backing::shared_backing_from; +use crate::qcow::metadata::{BackingRead, QcowMetadata}; +use crate::qcow::qcow_raw_file::QcowRawFile; +use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; + +/// Device level handle for a QCOW2 image. +/// +/// Owns the parsed metadata and backing file chain. One instance is +/// created per disk and shared across virtio queues. +pub struct QcowDiskAsync { + metadata: Arc, + backing_file: Option>, + sparse: bool, + data_raw_file: QcowRawFile, +} + +impl fmt::Debug for QcowDiskAsync { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("QcowDiskAsync") + .field("sparse", &self.sparse) + .field("has_backing", &self.backing_file.is_some()) + .finish_non_exhaustive() + } +} + +impl QcowDiskAsync { + pub fn new( + file: File, + direct_io: bool, + backing_files: bool, + sparse: bool, + ) -> BlockResult { + let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 }; + let (inner, backing_file, sparse) = + parse_qcow(RawFile::new(file, direct_io), 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(QcowDiskAsync { + metadata: Arc::new(QcowMetadata::new(inner)), + backing_file: backing_file.map(shared_backing_from).transpose()?, + sparse, + data_raw_file, + }) + } +} + +impl Drop for QcowDiskAsync { + fn drop(&mut self) { + self.metadata.shutdown(); + } +} From f7771138180bda48c50ce39535b25b388f9d1d42 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 17:42:37 +0100 Subject: [PATCH 1165/1893] block: qcow_async: impl DiskSize for QcowDiskAsync Delegates to QcowMetadata::virtual_size, identical to the sync backend. Signed-off-by: Anatol Belski --- block/src/qcow_async.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 71bc6f4742..d6b14ae773 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -10,6 +10,7 @@ use std::fmt; use std::fs::File; use std::sync::Arc; +use crate::disk_file; use crate::error::{BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; use crate::qcow::metadata::{BackingRead, QcowMetadata}; @@ -68,3 +69,9 @@ impl Drop for QcowDiskAsync { self.metadata.shutdown(); } } + +impl disk_file::DiskSize for QcowDiskAsync { + fn logical_size(&self) -> BlockResult { + Ok(self.metadata.virtual_size()) + } +} From 403b75656cd1fdcb50db0b5262e6d6d2cea1cb95 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 17:43:37 +0100 Subject: [PATCH 1166/1893] block: qcow_async: impl PhysicalSize for QcowDiskAsync Queries the underlying raw file for on disk size. Signed-off-by: Anatol Belski --- block/src/qcow_async.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index d6b14ae773..388dcab551 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -75,3 +75,9 @@ impl disk_file::DiskSize for QcowDiskAsync { Ok(self.metadata.virtual_size()) } } + +impl disk_file::PhysicalSize for QcowDiskAsync { + fn physical_size(&self) -> BlockResult { + Ok(self.data_raw_file.physical_size()?) + } +} From 91d6356db4fb364daa4e4762128e60db3b0092d3 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 17:44:19 +0100 Subject: [PATCH 1167/1893] block: qcow_async: impl DiskFd for QcowDiskAsync Returns a borrowed file descriptor for the underlying data file. Signed-off-by: Anatol Belski --- block/src/qcow_async.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 388dcab551..226ed7bcf3 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -8,8 +8,10 @@ use std::fmt; use std::fs::File; +use std::os::fd::{AsFd, AsRawFd}; use std::sync::Arc; +use crate::async_io::BorrowedDiskFd; use crate::disk_file; use crate::error::{BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; @@ -81,3 +83,9 @@ impl disk_file::PhysicalSize for QcowDiskAsync { Ok(self.data_raw_file.physical_size()?) } } + +impl disk_file::DiskFd for QcowDiskAsync { + fn fd(&self) -> BorrowedDiskFd<'_> { + BorrowedDiskFd::new(self.data_raw_file.as_fd().as_raw_fd()) + } +} From fa700f6493b1fbbe0123e4b9f6480a7785a11fc3 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 17:49:23 +0100 Subject: [PATCH 1168/1893] block: qcow_async: impl Geometry for QcowDiskAsync Uses the default geometry, same as the sync backend. Signed-off-by: Anatol Belski --- block/src/qcow_async.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 226ed7bcf3..f9cd0572e9 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -89,3 +89,5 @@ impl disk_file::DiskFd for QcowDiskAsync { BorrowedDiskFd::new(self.data_raw_file.as_fd().as_raw_fd()) } } + +impl disk_file::Geometry for QcowDiskAsync {} From 52cd45874a7b5d4f8bd84e57b55191f35d2d637e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 17:50:00 +0100 Subject: [PATCH 1169/1893] block: qcow_async: impl SparseCapable for QcowDiskAsync QCOW2 images support both sparse operations and the zero flag. Signed-off-by: Anatol Belski --- block/src/qcow_async.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index f9cd0572e9..62b2872b2d 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -91,3 +91,13 @@ impl disk_file::DiskFd for QcowDiskAsync { } impl disk_file::Geometry for QcowDiskAsync {} + +impl disk_file::SparseCapable for QcowDiskAsync { + fn supports_sparse_operations(&self) -> bool { + true + } + + fn supports_zero_flag(&self) -> bool { + true + } +} From 8c3b6cb04be777ecf2d37fcd1d8c58dc7cc97177 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 17:51:27 +0100 Subject: [PATCH 1170/1893] 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 --- block/src/qcow_async.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 62b2872b2d..a90fc2d08a 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -6,14 +6,14 @@ //! QCOW2 async disk backend. -use std::fmt; use std::fs::File; use std::os::fd::{AsFd, AsRawFd}; use std::sync::Arc; +use std::{fmt, io}; -use crate::async_io::BorrowedDiskFd; +use crate::async_io::{BorrowedDiskFd, DiskFileError}; use crate::disk_file; -use crate::error::{BlockErrorKind, BlockResult, ErrorOp}; +use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; use crate::qcow::metadata::{BackingRead, QcowMetadata}; use crate::qcow::qcow_raw_file::QcowRawFile; @@ -101,3 +101,21 @@ impl disk_file::SparseCapable for QcowDiskAsync { true } } + +impl disk_file::Resizable for QcowDiskAsync { + 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 file", + )), + ) + .with_op(ErrorOp::Resize)); + } + self.metadata.resize(size).map_err(|e| { + BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)) + .with_op(ErrorOp::Resize) + }) + } +} From 896660789825b11c96c6b7e50a249660a98c6e3e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 17:52:25 +0100 Subject: [PATCH 1171/1893] 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 --- block/src/qcow_async.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index a90fc2d08a..4e0826c652 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -119,3 +119,5 @@ impl disk_file::Resizable for QcowDiskAsync { }) } } + +impl disk_file::DiskFile for QcowDiskAsync {} From 8d684cad98957751557dd664d203f45c6144672b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 18:22:58 +0100 Subject: [PATCH 1172/1893] 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 --- block/src/lib.rs | 1 + block/src/qcow_async.rs | 47 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/block/src/lib.rs b/block/src/lib.rs index c98f20ae87..811ee974ef 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -18,6 +18,7 @@ pub mod fixed_vhd; pub mod fixed_vhd_async; pub mod fixed_vhd_sync; pub mod qcow; +#[cfg(feature = "io_uring")] pub mod qcow_async; pub(crate) mod qcow_common; pub mod qcow_sync; diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 4e0826c652..b0d93e6876 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -6,11 +6,15 @@ //! QCOW2 async disk backend. +use std::collections::VecDeque; use std::fs::File; use std::os::fd::{AsFd, AsRawFd}; use std::sync::Arc; use std::{fmt, io}; +use io_uring::IoUring; +use vmm_sys_util::eventfd::EventFd; + use crate::async_io::{BorrowedDiskFd, DiskFileError}; use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; @@ -121,3 +125,46 @@ impl disk_file::Resizable for QcowDiskAsync { } impl disk_file::DiskFile for QcowDiskAsync {} + +/// Per queue QCOW2 I/O worker using io_uring. +/// +/// Reads against fully allocated single mapping clusters are submitted +/// to io_uring for true asynchronous completion. All other cluster +/// types (zero, compressed, backing) and multi mapping reads fall back +/// to synchronous I/O with synthetic completions. +/// +/// Writes are synchronous because metadata allocation must complete +/// before the host offset is known. +pub struct QcowAsync { + metadata: Arc, + data_file: QcowRawFile, + backing_file: Option>, + sparse: bool, + io_uring: IoUring, + eventfd: EventFd, + completion_list: VecDeque<(u64, i32)>, +} + +impl QcowAsync { + fn new( + metadata: Arc, + data_file: QcowRawFile, + backing_file: Option>, + sparse: bool, + ring_depth: u32, + ) -> io::Result { + let io_uring = IoUring::new(ring_depth)?; + let eventfd = EventFd::new(libc::EFD_NONBLOCK)?; + io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?; + + Ok(QcowAsync { + metadata, + data_file, + backing_file, + sparse, + io_uring, + eventfd, + completion_list: VecDeque::new(), + }) + } +} From 85df0fef0df9430323a3d2d773af9918c07fe3fc Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 18:41:18 +0100 Subject: [PATCH 1173/1893] 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 --- block/src/qcow_async.rs | 47 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index b0d93e6876..43b1b6aaaf 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -15,7 +15,7 @@ use std::{fmt, io}; use io_uring::IoUring; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{BorrowedDiskFd, DiskFileError}; +use crate::async_io::{AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; @@ -168,3 +168,48 @@ impl QcowAsync { }) } } + +impl AsyncIo for QcowAsync { + fn notifier(&self) -> &EventFd { + &self.eventfd + } + + fn read_vectored( + &mut self, + offset: libc::off_t, + iovecs: &[libc::iovec], + user_data: u64, + ) -> AsyncIoResult<()> { + unimplemented!() + } + + fn write_vectored( + &mut self, + offset: libc::off_t, + iovecs: &[libc::iovec], + user_data: u64, + ) -> AsyncIoResult<()> { + unimplemented!() + } + + fn fsync(&mut self, user_data: Option) -> AsyncIoResult<()> { + unimplemented!() + } + + fn next_completed_request(&mut self) -> Option<(u64, i32)> { + // Drain io_uring completions first, then synthetic ones. + self.io_uring + .completion() + .next() + .map(|entry| (entry.user_data(), entry.result())) + .or_else(|| self.completion_list.pop_front()) + } + + fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { + unimplemented!() + } + + fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { + unimplemented!() + } +} From e7621abfbdb7daafb2cbd4730b63acff04331f51 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 18:43:55 +0100 Subject: [PATCH 1174/1893] 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 --- block/src/qcow_async.rs | 130 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 43b1b6aaaf..e3bb399c0f 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -8,20 +8,22 @@ use std::collections::VecDeque; use std::fs::File; +use std::io::Error; use std::os::fd::{AsFd, AsRawFd}; use std::sync::Arc; use std::{fmt, io}; -use io_uring::IoUring; +use io_uring::{IoUring, opcode, types}; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFileError}; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; -use crate::qcow::metadata::{BackingRead, QcowMetadata}; +use crate::qcow::metadata::{BackingRead, ClusterReadMapping, QcowMetadata}; use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; +use crate::qcow_common::{pread_exact, scatter_to_iovecs, zero_fill_iovecs}; /// Device level handle for a QCOW2 image. /// @@ -180,7 +182,40 @@ impl AsyncIo for QcowAsync { iovecs: &[libc::iovec], user_data: u64, ) -> AsyncIoResult<()> { - unimplemented!() + let total_len: usize = iovecs.iter().map(|v| v.iov_len).sum(); + + if let Some(host_offset) = Self::resolve_read( + &self.metadata, + &self.data_file, + &self.backing_file, + offset as u64, + iovecs, + total_len, + )? { + let fd = self.data_file.as_raw_fd(); + let (submitter, mut sq, _) = self.io_uring.split(); + + // SAFETY: fd is valid and iovecs point to valid guest memory. + unsafe { + sq.push( + &opcode::Readv::new(types::Fd(fd), iovecs.as_ptr(), iovecs.len() as u32) + .offset(host_offset) + .build() + .user_data(user_data), + ) + .map_err(|_| { + AsyncIoError::ReadVectored(Error::other("Submission queue is full")) + })?; + }; + + sq.sync(); + submitter.submit().map_err(AsyncIoError::ReadVectored)?; + } else { + self.completion_list + .push_back((user_data, total_len as i32)); + self.eventfd.write(1).unwrap(); + } + Ok(()) } fn write_vectored( @@ -213,3 +248,90 @@ impl AsyncIo for QcowAsync { unimplemented!() } } + +impl QcowAsync { + /// Resolves read mappings for a guest read request. + /// + /// Returns `Some(host_offset)` if the entire read falls within a single + /// allocated cluster (fast path). Otherwise handles the read + /// synchronously via `scatter_read_sync` and returns `None`. + fn resolve_read( + metadata: &QcowMetadata, + data_file: &QcowRawFile, + backing_file: &Option>, + address: u64, + iovecs: &[libc::iovec], + total_len: usize, + ) -> AsyncIoResult> { + let has_backing = backing_file.is_some(); + let mappings = metadata + .map_clusters_for_read(address, total_len, has_backing) + .map_err(AsyncIoError::ReadVectored)?; + + if mappings.len() == 1 + && let ClusterReadMapping::Allocated { + offset: host_offset, + length, + } = &mappings[0] + && *length as usize == total_len + { + return Ok(Some(*host_offset)); + } + + Self::scatter_read_sync(mappings, iovecs, data_file, backing_file)?; + Ok(None) + } + + /// Scatter-read cluster mappings synchronously into iovec buffers. + fn scatter_read_sync( + mappings: Vec, + iovecs: &[libc::iovec], + data_file: &QcowRawFile, + backing_file: &Option>, + ) -> AsyncIoResult<()> { + let mut buf_offset = 0usize; + for mapping in mappings { + match mapping { + ClusterReadMapping::Zero { length } => { + // SAFETY: iovecs point to valid guest memory buffers. + unsafe { + zero_fill_iovecs(iovecs, buf_offset, length as usize); + } + buf_offset += length as usize; + } + ClusterReadMapping::Allocated { + offset: host_offset, + length, + } => { + let mut buf = vec![0u8; length as usize]; + pread_exact(data_file.as_raw_fd(), &mut buf, host_offset) + .map_err(AsyncIoError::ReadVectored)?; + // SAFETY: iovecs point to valid guest memory buffers. + unsafe { scatter_to_iovecs(iovecs, buf_offset, &buf) }; + buf_offset += length as usize; + } + ClusterReadMapping::Compressed { data } => { + let len = data.len(); + // SAFETY: iovecs point to valid guest memory buffers. + unsafe { scatter_to_iovecs(iovecs, buf_offset, &data) }; + buf_offset += len; + } + ClusterReadMapping::Backing { + offset: backing_offset, + length, + } => { + let mut buf = vec![0u8; length as usize]; + backing_file + .as_ref() + .unwrap() + .read_at(backing_offset, &mut buf) + .map_err(AsyncIoError::ReadVectored)?; + // SAFETY: iovecs point to valid guest memory buffers. + unsafe { scatter_to_iovecs(iovecs, buf_offset, &buf) }; + buf_offset += length as usize; + } + } + } + Ok(()) + } +} From a97260c5e0ee1728d2e383ac8bb10b9c9076db77 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 20:01:09 +0100 Subject: [PATCH 1175/1893] 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 --- block/src/qcow_async.rs | 77 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index e3bb399c0f..2239867c87 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -6,6 +6,7 @@ //! QCOW2 async disk backend. +use std::cmp::min; use std::collections::VecDeque; use std::fs::File; use std::io::Error; @@ -20,10 +21,12 @@ use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, Disk use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; -use crate::qcow::metadata::{BackingRead, ClusterReadMapping, QcowMetadata}; +use crate::qcow::metadata::{BackingRead, ClusterReadMapping, ClusterWriteMapping, QcowMetadata}; use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; -use crate::qcow_common::{pread_exact, scatter_to_iovecs, zero_fill_iovecs}; +use crate::qcow_common::{ + gather_from_iovecs, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, +}; /// Device level handle for a QCOW2 image. /// @@ -218,13 +221,30 @@ impl AsyncIo for QcowAsync { Ok(()) } + // TODO Make writes async. + // Writes are synchronous. Async writes require a multi step + // state machine for COW (backing read, cluster allocation, data + // write, L2 commit) with per request buffer lifetime tracking + // and write ordering. fn write_vectored( &mut self, offset: libc::off_t, iovecs: &[libc::iovec], user_data: u64, ) -> AsyncIoResult<()> { - unimplemented!() + Self::cow_write_sync( + offset as u64, + iovecs, + &self.metadata, + &self.data_file, + &self.backing_file, + )?; + + let total_len: usize = iovecs.iter().map(|v| v.iov_len).sum(); + self.completion_list + .push_back((user_data, total_len as i32)); + self.eventfd.write(1).unwrap(); + Ok(()) } fn fsync(&mut self, user_data: Option) -> AsyncIoResult<()> { @@ -334,4 +354,55 @@ impl QcowAsync { } Ok(()) } + + /// Write iovec data cluster-by-cluster with COW from backing file. + fn cow_write_sync( + address: u64, + iovecs: &[libc::iovec], + metadata: &QcowMetadata, + data_file: &QcowRawFile, + backing_file: &Option>, + ) -> AsyncIoResult<()> { + let total_len: usize = iovecs.iter().map(|v| v.iov_len).sum(); + let cluster_size = metadata.cluster_size(); + let mut buf_offset = 0usize; + + while buf_offset < total_len { + let curr_addr = address + buf_offset as u64; + let intra_offset = metadata.cluster_offset(curr_addr); + let remaining_in_cluster = (cluster_size - intra_offset) as usize; + let count = min(total_len - buf_offset, remaining_in_cluster); + + let backing_data = if let Some(backing) = backing_file + .as_ref() + .filter(|_| intra_offset != 0 || count < cluster_size as usize) + { + let cluster_begin = curr_addr - intra_offset; + let mut data = vec![0u8; cluster_size as usize]; + backing + .read_at(cluster_begin, &mut data) + .map_err(AsyncIoError::WriteVectored)?; + Some(data) + } else { + None + }; + + let mapping = metadata + .map_cluster_for_write(curr_addr, backing_data) + .map_err(AsyncIoError::WriteVectored)?; + + match mapping { + ClusterWriteMapping::Allocated { + offset: host_offset, + } => { + // SAFETY: iovecs point to valid guest memory buffers. + let buf = unsafe { gather_from_iovecs(iovecs, buf_offset, count) }; + pwrite_all(data_file.as_raw_fd(), &buf, host_offset) + .map_err(AsyncIoError::WriteVectored)?; + } + } + buf_offset += count; + } + Ok(()) + } } From bf70c19857dcdfd13b375b12fba202f30dcf0f70 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 20:07:32 +0100 Subject: [PATCH 1176/1893] 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 --- block/src/qcow_async.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 2239867c87..ad94f74afa 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -248,7 +248,12 @@ impl AsyncIo for QcowAsync { } fn fsync(&mut self, user_data: Option) -> AsyncIoResult<()> { - unimplemented!() + self.metadata.flush().map_err(AsyncIoError::Fsync)?; + if let Some(user_data) = user_data { + self.completion_list.push_back((user_data, 0)); + self.eventfd.write(1).unwrap(); + } + Ok(()) } fn next_completed_request(&mut self) -> Option<(u64, i32)> { From 261361c1b0741be2276b556336349ab738614813 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 20:12:14 +0100 Subject: [PATCH 1177/1893] 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 --- block/src/qcow_async.rs | 65 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index ad94f74afa..629ca8df46 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -16,12 +16,15 @@ use std::{fmt, io}; use io_uring::{IoUring, opcode, types}; use vmm_sys_util::eventfd::EventFd; +use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; -use crate::qcow::metadata::{BackingRead, ClusterReadMapping, ClusterWriteMapping, QcowMetadata}; +use crate::qcow::metadata::{ + BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, +}; use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; use crate::qcow_common::{ @@ -172,6 +175,26 @@ impl QcowAsync { completion_list: VecDeque::new(), }) } + + fn apply_dealloc_action(&mut self, action: &DeallocAction) { + match action { + DeallocAction::PunchHole { + host_offset, + length, + } => { + let _ = self.data_file.file_mut().punch_hole(*host_offset, *length); + } + DeallocAction::WriteZeroes { + host_offset, + length, + } => { + let _ = self + .data_file + .file_mut() + .write_zeroes_at(*host_offset, *length); + } + } + } } impl AsyncIo for QcowAsync { @@ -266,11 +289,47 @@ impl AsyncIo for QcowAsync { } fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { - unimplemented!() + let virtual_size = self.metadata.virtual_size(); + let cluster_size = self.metadata.cluster_size(); + + let result = self + .metadata + .deallocate_bytes( + offset, + length as usize, + self.sparse, + virtual_size, + cluster_size, + self.backing_file.as_deref(), + ) + .map_err(AsyncIoError::PunchHole); + + match result { + Ok(actions) => { + for action in &actions { + self.apply_dealloc_action(action); + } + self.completion_list.push_back((user_data, 0)); + self.eventfd.write(1).unwrap(); + Ok(()) + } + Err(e) => { + let errno = if let AsyncIoError::PunchHole(ref io_err) = e { + -io_err.raw_os_error().unwrap_or(libc::EIO) + } else { + -libc::EIO + }; + self.completion_list.push_back((user_data, errno)); + self.eventfd.write(1).unwrap(); + Ok(()) + } + } } fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { - unimplemented!() + // For QCOW2, zeroing and hole punching are the same operation. + // Both discard guest data so the range reads back as zero. + self.punch_hole(offset, length, user_data) } } From ffc579b913687bdd0ce9191517fe01f695eb7e4f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 20:17:01 +0100 Subject: [PATCH 1178/1893] 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 --- block/src/qcow_async.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 629ca8df46..b5720bdca8 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -134,6 +134,30 @@ impl disk_file::Resizable for QcowDiskAsync { impl disk_file::DiskFile for QcowDiskAsync {} +impl disk_file::AsyncDiskFile for QcowDiskAsync { + fn try_clone(&self) -> BlockResult> { + Ok(Box::new(QcowDiskAsync { + 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(), + })) + } + + fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + 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)))?, + )) + } +} + /// Per queue QCOW2 I/O worker using io_uring. /// /// Reads against fully allocated single mapping clusters are submitted From 3d5a40dfa68e4717044a93cc07c51b072d98d38f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 20:30:53 +0100 Subject: [PATCH 1179/1893] 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 --- vmm/src/device_manager.rs | 60 ++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 91d9509f38..9522be53d2 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -35,6 +35,8 @@ use arch::{NumaNodes, layout}; use block::disk_file::DiskBackend; use block::error::BlockError; use block::fixed_vhd_sync::FixedVhdDiskSync; +#[cfg(feature = "io_uring")] +use block::qcow_async::QcowDiskAsync; use block::qcow_sync::QcowDiskSync; use block::raw_async_aio::RawFileDiskAio; use block::raw_sync::RawFileDiskSync; @@ -591,6 +593,10 @@ pub enum DeviceManagerError { #[error("Failed to create QcowDiskSync")] CreateQcowDiskSync(#[source] BlockError), + /// Failed to create QcowDiskAsync + #[error("Failed to create QcowDiskAsync")] + CreateQcowDiskAsync(#[source] BlockError), + /// Failed to create FixedVhdxDiskSync #[error("Failed to create FixedVhdxDiskSync")] CreateFixedVhdxDiskSync(#[source] BlockError), @@ -2801,20 +2807,46 @@ impl DeviceManager { } } ImageType::Qcow2 => { - info!("Using synchronous QCOW2 disk file"); - DiskBackend::Next(Box::new( - QcowDiskSync::new( - file, - disk_cfg.direct, - disk_cfg.backing_files, - disk_cfg.sparse, - ) - .map_err(|e| match &disk_cfg.path { - Some(p) => e.with_path(p), - None => e, - }) - .map_err(DeviceManagerError::CreateQcowDiskSync)?, - )) + if cfg!(feature = "io_uring") + && !disk_cfg.disable_io_uring + && self.io_uring_is_supported() + { + info!("Using asynchronous QCOW2 disk file (io_uring)"); + + #[cfg(not(feature = "io_uring"))] + unreachable!("Checked in if statement above"); + #[cfg(feature = "io_uring")] + { + DiskBackend::Next(Box::new( + QcowDiskAsync::new( + file, + disk_cfg.direct, + disk_cfg.backing_files, + disk_cfg.sparse, + ) + .map_err(|e| match &disk_cfg.path { + Some(p) => e.with_path(p), + None => e, + }) + .map_err(DeviceManagerError::CreateQcowDiskAsync)?, + )) + } + } else { + info!("Using synchronous QCOW2 disk file"); + DiskBackend::Next(Box::new( + QcowDiskSync::new( + file, + disk_cfg.direct, + disk_cfg.backing_files, + disk_cfg.sparse, + ) + .map_err(|e| match &disk_cfg.path { + Some(p) => e.with_path(p), + None => e, + }) + .map_err(DeviceManagerError::CreateQcowDiskSync)?, + )) + } } ImageType::Vhdx => { info!("Using synchronous VHDX disk file"); From b1d126fdbff5a7d2b843a1d41e489bf68f15f696 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 21 Mar 2026 17:22:30 +0100 Subject: [PATCH 1180/1893] 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 --- block/src/qcow_async.rs | 80 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index b5720bdca8..11e7554618 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -19,7 +19,6 @@ use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; -use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; use crate::qcow::metadata::{ @@ -30,6 +29,7 @@ use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; use crate::qcow_common::{ gather_from_iovecs, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; +use crate::{BatchRequest, RequestType, disk_file}; /// Device level handle for a QCOW2 image. /// @@ -355,6 +355,84 @@ impl AsyncIo for QcowAsync { // Both discard guest data so the range reads back as zero. self.punch_hole(offset, length, user_data) } + + fn batch_requests_enabled(&self) -> bool { + true + } + + fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> { + let (submitter, mut sq, _) = self.io_uring.split(); + let mut needs_submit = false; + let mut sync_completions: Vec<(u64, i32)> = Vec::new(); + + for req in batch_request { + match req.request_type { + RequestType::In => { + let total_len: usize = req.iovecs.iter().map(|v| v.iov_len).sum(); + + if let Some(host_offset) = Self::resolve_read( + &self.metadata, + &self.data_file, + &self.backing_file, + req.offset as u64, + &req.iovecs, + total_len, + )? { + let fd = self.data_file.as_raw_fd(); + // SAFETY: fd is valid and iovecs point to valid guest memory. + unsafe { + sq.push( + &opcode::Readv::new( + types::Fd(fd), + req.iovecs.as_ptr(), + req.iovecs.len() as u32, + ) + .offset(host_offset) + .build() + .user_data(req.user_data), + ) + .map_err(|_| { + AsyncIoError::ReadVectored(Error::other("Submission queue is full")) + })?; + } + needs_submit = true; + } else { + sync_completions.push((req.user_data, total_len as i32)); + } + } + RequestType::Out => { + let total_len: usize = req.iovecs.iter().map(|v| v.iov_len).sum(); + Self::cow_write_sync( + req.offset as u64, + &req.iovecs, + &self.metadata, + &self.data_file, + &self.backing_file, + )?; + sync_completions.push((req.user_data, total_len as i32)); + } + _ => { + unreachable!("Unexpected batch request type: {:?}", req.request_type) + } + } + } + + if needs_submit { + sq.sync(); + submitter + .submit() + .map_err(AsyncIoError::SubmitBatchRequests)?; + } + + if !sync_completions.is_empty() { + for c in sync_completions { + self.completion_list.push_back(c); + } + self.eventfd.write(1).unwrap(); + } + + Ok(()) + } } impl QcowAsync { From 8ffbe9d6feec43e4c06f567133e800bd071165bf Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 22:40:56 +0200 Subject: [PATCH 1181/1893] 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 --- block/src/qcow_async.rs | 85 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 11e7554618..74cf9abef6 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -572,3 +572,88 @@ impl QcowAsync { Ok(()) } } + +#[cfg(test)] +mod unit_tests { + use std::io::{Seek, SeekFrom, Write}; + + use vmm_sys_util::tempfile::TempFile; + + use super::*; + use crate::disk_file::AsyncDiskFile; + use crate::qcow::{QcowFile, RawFile}; + + fn create_disk_with_data( + file_size: u64, + data: &[u8], + offset: u64, + sparse: bool, + ) -> (TempFile, QcowDiskAsync) { + let temp_file = TempFile::new().unwrap(); + { + let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + let mut qcow_file = QcowFile::new(raw_file, 3, file_size, sparse).unwrap(); + qcow_file.seek(SeekFrom::Start(offset)).unwrap(); + qcow_file.write_all(data).unwrap(); + qcow_file.flush().unwrap(); + } + let disk = QcowDiskAsync::new( + temp_file.as_file().try_clone().unwrap(), + false, + false, + sparse, + ) + .unwrap(); + (temp_file, disk) + } + + fn wait_for_completion(async_io: &mut dyn AsyncIo) -> (u64, i32) { + loop { + if let Some(c) = async_io.next_completed_request() { + return c; + } + let fd = async_io.notifier().as_raw_fd(); + let mut val = 0u64; + // SAFETY: reading 8 bytes from a valid eventfd. + unsafe { + libc::read(fd, &mut val as *mut u64 as *mut libc::c_void, 8); + } + } + } + + fn async_read(disk: &QcowDiskAsync, offset: u64, len: usize) -> Vec { + let mut async_io = disk.new_async_io(1).unwrap(); + let mut buf = vec![0xFFu8; len]; + let iovec = libc::iovec { + iov_base: buf.as_mut_ptr() as *mut libc::c_void, + iov_len: buf.len(), + }; + async_io + .read_vectored(offset as libc::off_t, &[iovec], 1) + .unwrap(); + let (user_data, result) = wait_for_completion(async_io.as_mut()); + assert_eq!(user_data, 1); + assert_eq!(result as usize, len, "read should return requested length"); + buf + } + + #[test] + fn test_qcow_async_punch_hole_completion() { + let data = vec![0xDD; 128 * 1024]; + let offset = 0u64; + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); + + let mut async_io = disk.new_async_io(1).unwrap(); + async_io.punch_hole(offset, data.len() as u64, 100).unwrap(); + let (user_data, result) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 100); + assert_eq!(result, 0, "punch_hole should succeed"); + drop(async_io); + + let read_buf = async_read(&disk, offset, data.len()); + assert!( + read_buf.iter().all(|&b| b == 0), + "Punched hole should read as zeros" + ); + } +} From e522d3a0aa41138dc4dd7b342682676488f3e46b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 22:43:01 +0200 Subject: [PATCH 1182/1893] 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 --- block/src/qcow_async.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 74cf9abef6..a01acf604f 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -656,4 +656,26 @@ mod unit_tests { "Punched hole should read as zeros" ); } + + #[test] + fn test_qcow_async_write_zeroes_completion() { + let data = vec![0xAA; 128 * 1024]; + let offset = 0u64; + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); + + let mut async_io = disk.new_async_io(1).unwrap(); + async_io + .write_zeroes(offset, data.len() as u64, 200) + .unwrap(); + let (user_data, result) = async_io.next_completed_request().unwrap(); + assert_eq!(user_data, 200); + assert_eq!(result, 0, "write_zeroes should succeed"); + drop(async_io); + + let read_buf = async_read(&disk, offset, data.len()); + assert!( + read_buf.iter().all(|&b| b == 0), + "Write zeroes region should read as zeros" + ); + } } From 1e66c144f4950e7f3c1999f42cd49c8f7b649414 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 22:55:45 +0200 Subject: [PATCH 1183/1893] 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 --- block/src/qcow_async.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index a01acf604f..31ad2584d6 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -612,6 +612,7 @@ mod unit_tests { if let Some(c) = async_io.next_completed_request() { return c; } + // Block until the eventfd is signaled (io_uring or synthetic). let fd = async_io.notifier().as_raw_fd(); let mut val = 0u64; // SAFETY: reading 8 bytes from a valid eventfd. @@ -621,6 +622,24 @@ mod unit_tests { } } + fn async_write(disk: &QcowDiskAsync, offset: u64, data: &[u8]) { + let mut async_io = disk.new_async_io(1).unwrap(); + let iovec = libc::iovec { + iov_base: data.as_ptr() as *mut libc::c_void, + iov_len: data.len(), + }; + async_io + .write_vectored(offset as libc::off_t, &[iovec], 2) + .unwrap(); + let (user_data, result) = wait_for_completion(async_io.as_mut()); + assert_eq!(user_data, 2); + assert_eq!( + result as usize, + data.len(), + "write should return requested length" + ); + } + fn async_read(disk: &QcowDiskAsync, offset: u64, len: usize) -> Vec { let mut async_io = disk.new_async_io(1).unwrap(); let mut buf = vec![0xFFu8; len]; @@ -678,4 +697,23 @@ mod unit_tests { "Write zeroes region should read as zeros" ); } + + #[test] + fn test_qcow_async_write_read_roundtrip() { + let file_size = 100 * 1024 * 1024; + let temp_file = TempFile::new().unwrap(); + { + let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + QcowFile::new(raw_file, 3, file_size, true).unwrap(); + } + let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) + .unwrap(); + + let pattern: Vec = (0..128 * 1024).map(|i| (i % 251) as u8).collect(); + let offset = 64 * 1024; + + async_write(&disk, offset, &pattern); + let read_buf = async_read(&disk, offset, pattern.len()); + assert_eq!(read_buf, pattern, "read should match written data"); + } } From 093922ff24abcdb86375b2d6f00299b4e48bb417 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 22:59:45 +0200 Subject: [PATCH 1184/1893] 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 --- block/src/qcow_async.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 31ad2584d6..c26fdaea2b 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -716,4 +716,30 @@ mod unit_tests { let read_buf = async_read(&disk, offset, pattern.len()); assert_eq!(read_buf, pattern, "read should match written data"); } + + #[test] + fn test_qcow_async_read_spanning_cluster_boundary() { + let cluster_size: u64 = 65536; + let file_size = 100 * 1024 * 1024; + + // Write distinct patterns into two adjacent clusters. + let pattern_a = vec![0xAA; cluster_size as usize]; + let pattern_b = vec![0xBB; cluster_size as usize]; + let (_temp, disk) = create_disk_with_data(file_size, &pattern_a, 0, true); + async_write(&disk, cluster_size, &pattern_b); + + // Read across the boundary: last 4K of cluster 0 + first 4K of cluster 1. + let read_offset = cluster_size - 4096; + let read_len = 8192; + let buf = async_read(&disk, read_offset, read_len); + + assert!( + buf[..4096].iter().all(|&b| b == 0xAA), + "first half should come from cluster 0" + ); + assert!( + buf[4096..].iter().all(|&b| b == 0xBB), + "second half should come from cluster 1" + ); + } } From 34a9f7246dd3778d4f9ad2797ad5e2a7447ad86a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 23:04:56 +0200 Subject: [PATCH 1185/1893] 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 --- block/src/qcow_async.rs | 97 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index c26fdaea2b..4501502a92 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -582,6 +582,7 @@ mod unit_tests { use super::*; use crate::disk_file::AsyncDiskFile; use crate::qcow::{QcowFile, RawFile}; + use crate::{BatchRequest, RequestType}; fn create_disk_with_data( file_size: u64, @@ -742,4 +743,100 @@ mod unit_tests { "second half should come from cluster 1" ); } + + #[test] + fn test_qcow_async_batch_mixed_requests() { + let file_size = 100 * 1024 * 1024; + let temp_file = TempFile::new().unwrap(); + { + let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + QcowFile::new(raw_file, 3, file_size, true).unwrap(); + } + let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) + .unwrap(); + + let mut async_io = disk.new_async_io(8).unwrap(); + + // Prepare write data for two regions. + let write_a = vec![0xAA; 4096]; + let write_b = vec![0xBB; 4096]; + let offset_a: u64 = 0; + let offset_b: u64 = 65536; + + let iov_a = libc::iovec { + iov_base: write_a.as_ptr() as *mut libc::c_void, + iov_len: write_a.len(), + }; + let iov_b = libc::iovec { + iov_base: write_b.as_ptr() as *mut libc::c_void, + iov_len: write_b.len(), + }; + + let batch = vec![ + BatchRequest { + offset: offset_a as libc::off_t, + iovecs: smallvec::smallvec![iov_a], + user_data: 10, + request_type: RequestType::Out, + }, + BatchRequest { + offset: offset_b as libc::off_t, + iovecs: smallvec::smallvec![iov_b], + user_data: 20, + request_type: RequestType::Out, + }, + ]; + + async_io.submit_batch_requests(&batch).unwrap(); + + let mut completions = [ + wait_for_completion(async_io.as_mut()), + wait_for_completion(async_io.as_mut()), + ]; + completions.sort_by_key(|c| c.0); + assert_eq!(completions[0], (10, 4096)); + assert_eq!(completions[1], (20, 4096)); + drop(async_io); + + // Batch read both regions back. + let mut read_a = vec![0u8; 4096]; + let mut read_b = vec![0u8; 4096]; + let riov_a = libc::iovec { + iov_base: read_a.as_mut_ptr() as *mut libc::c_void, + iov_len: read_a.len(), + }; + let riov_b = libc::iovec { + iov_base: read_b.as_mut_ptr() as *mut libc::c_void, + iov_len: read_b.len(), + }; + + let mut async_io = disk.new_async_io(8).unwrap(); + let read_batch = vec![ + BatchRequest { + offset: offset_a as libc::off_t, + iovecs: smallvec::smallvec![riov_a], + user_data: 30, + request_type: RequestType::In, + }, + BatchRequest { + offset: offset_b as libc::off_t, + iovecs: smallvec::smallvec![riov_b], + user_data: 40, + request_type: RequestType::In, + }, + ]; + + async_io.submit_batch_requests(&read_batch).unwrap(); + + let mut completions = [ + wait_for_completion(async_io.as_mut()), + wait_for_completion(async_io.as_mut()), + ]; + completions.sort_by_key(|c| c.0); + assert_eq!(completions[0], (30, 4096)); + assert_eq!(completions[1], (40, 4096)); + + assert_eq!(read_a, write_a, "batch read A should match written data"); + assert_eq!(read_b, write_b, "batch read B should match written data"); + } } From 6208f3caee063ec1f813d237cd39934b365575bb Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 23:16:36 +0200 Subject: [PATCH 1186/1893] 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 --- block/src/qcow_async.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 4501502a92..bdc4fe341a 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -839,4 +839,22 @@ mod unit_tests { assert_eq!(read_a, write_a, "batch read A should match written data"); assert_eq!(read_b, write_b, "batch read B should match written data"); } + + #[test] + fn test_qcow_async_read_unallocated() { + let file_size = 100 * 1024 * 1024; + let temp_file = TempFile::new().unwrap(); + { + let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + QcowFile::new(raw_file, 3, file_size, true).unwrap(); + } + let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) + .unwrap(); + + let buf = async_read(&disk, 0, 128 * 1024); + assert!( + buf.iter().all(|&b| b == 0), + "unallocated region should read as zeroes" + ); + } } From 48155c4151816ff57aee0bc615aea876b00b3c0b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 23:36:27 +0200 Subject: [PATCH 1187/1893] 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 --- block/src/qcow_async.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index bdc4fe341a..9063fd7d58 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -857,4 +857,42 @@ mod unit_tests { "unallocated region should read as zeroes" ); } + + #[test] + fn test_qcow_async_sub_cluster_write() { + let cluster_size = 65536usize; + let file_size = 100 * 1024 * 1024; + let temp_file = TempFile::new().unwrap(); + { + let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + QcowFile::new(raw_file, 3, file_size, true).unwrap(); + } + let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) + .unwrap(); + + // Write 4K into the middle of a cluster. + let write_offset = 4096u64; + let write_len = 4096; + let pattern = vec![0xCC; write_len]; + async_write(&disk, write_offset, &pattern); + + // Read the entire cluster back. + let buf = async_read(&disk, 0, cluster_size); + + assert!( + buf[..write_offset as usize].iter().all(|&b| b == 0), + "bytes before the write should be zero" + ); + assert_eq!( + &buf[write_offset as usize..write_offset as usize + write_len], + &pattern[..], + "written region should match" + ); + assert!( + buf[write_offset as usize + write_len..] + .iter() + .all(|&b| b == 0), + "bytes after the write should be zero" + ); + } } From 072b3a85d243748202b6be9193978a15ca85ea28 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 23:45:12 +0200 Subject: [PATCH 1188/1893] 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 --- block/src/qcow_async.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 9063fd7d58..daf8d1634c 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -895,4 +895,32 @@ mod unit_tests { "bytes after the write should be zero" ); } + + #[test] + fn test_qcow_async_write_after_punch_hole() { + let data = vec![0xAA; 64 * 1024]; + let offset = 0u64; + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); + + let buf = async_read(&disk, offset, data.len()); + assert!(buf.iter().all(|&b| b == 0xAA)); + + let mut async_io = disk.new_async_io(1).unwrap(); + async_io.punch_hole(offset, data.len() as u64, 10).unwrap(); + let (_, result) = wait_for_completion(async_io.as_mut()); + assert_eq!(result, 0); + drop(async_io); + + let buf = async_read(&disk, offset, data.len()); + assert!( + buf.iter().all(|&b| b == 0), + "should be zero after punch hole" + ); + + let new_data = vec![0xBB; 64 * 1024]; + async_write(&disk, offset, &new_data); + + let buf = async_read(&disk, offset, new_data.len()); + assert_eq!(buf, new_data, "should read new data after rewrite"); + } } From bb833a90e5a26b4bc086fe9539be81b123d5b5fd Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 30 Mar 2026 23:51:03 +0200 Subject: [PATCH 1189/1893] 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 --- block/src/qcow_async.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index daf8d1634c..d58b1b1b75 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -923,4 +923,28 @@ mod unit_tests { let buf = async_read(&disk, offset, new_data.len()); assert_eq!(buf, new_data, "should read new data after rewrite"); } + + #[test] + fn test_qcow_async_large_sequential_io() { + let cluster_size = 64 * 1024; + let num_clusters = 8; + let total_len = cluster_size * num_clusters; + let offset = 0u64; + + let mut data = vec![0u8; total_len]; + for (i, chunk) in data.chunks_mut(cluster_size).enumerate() { + chunk.fill((i + 1) as u8); + } + + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); + + let buf = async_read(&disk, offset, total_len); + assert_eq!(buf.len(), total_len); + for (i, chunk) in buf.chunks(cluster_size).enumerate() { + assert!( + chunk.iter().all(|&b| b == (i + 1) as u8), + "cluster {i} mismatch" + ); + } + } } From d4fc1d38c8e978f420e7a26f5224840131ec0cf7 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 31 Mar 2026 00:56:26 +0200 Subject: [PATCH 1190/1893] 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 --- scripts/dev_cli.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 0c0a2d5e4f..6e9d58eb7b 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -438,6 +438,7 @@ cmd_tests() { --device $exported_device \ --device /dev/net/tun \ --cap-add net_admin \ + --security-opt seccomp=unconfined \ --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ ${exported_volumes:+$exported_volumes} \ --env BUILD_TARGET="$target" \ From a6d3901f3e1d4077703e04f6d32f771da0ae60de Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Tue, 7 Apr 2026 10:43:57 -0700 Subject: [PATCH 1191/1893] misc: return errors from IOMMU address translation instead of panicking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- block/src/lib.rs | 9 ++-- net_util/src/ctrl_queue.rs | 9 ++-- net_util/src/queue_pair.rs | 15 ++++-- virtio-devices/src/console.rs | 14 +++-- virtio-devices/src/pmem.rs | 6 ++- virtio-devices/src/rng.rs | 5 +- .../src/transport/pci_common_config.rs | 33 +++++++++--- virtio-devices/src/vdpa.rs | 35 ++++++++----- virtio-devices/src/vsock/packet.rs | 31 +++++++---- vm-virtio/src/lib.rs | 52 ++++++++++++++----- 10 files changed, 153 insertions(+), 56 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index 811ee974ef..7a55cc4498 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -312,7 +312,8 @@ impl Request { let hdr_desc_addr = hdr_desc .addr() - .translate_gva(access_platform, hdr_desc.len() as usize); + .translate_gva(access_platform, hdr_desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?; let mut req = Request { request_type: request_type(desc_chain.memory(), hdr_desc_addr)?, @@ -353,7 +354,8 @@ impl Request { req.data_descriptors.push(( desc.addr() - .translate_gva(access_platform, desc.len() as usize), + .translate_gva(access_platform, desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?, desc.len(), )); desc = desc_chain @@ -384,7 +386,8 @@ impl Request { req.status_addr = status_desc .addr() - .translate_gva(access_platform, status_desc.len() as usize); + .translate_gva(access_platform, status_desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?; Ok(req) } diff --git a/net_util/src/ctrl_queue.rs b/net_util/src/ctrl_queue.rs index e42b4c0ca5..b14b380364 100644 --- a/net_util/src/ctrl_queue.rs +++ b/net_util/src/ctrl_queue.rs @@ -100,14 +100,16 @@ impl CtrlQueue { .read_obj( ctrl_desc .addr() - .translate_gva(access_platform, ctrl_desc.len() as usize), + .translate_gva(access_platform, ctrl_desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?, ) .map_err(Error::GuestMemory)?; let data_desc = desc_chain.next().ok_or(Error::NoDataDescriptor)?; let data_desc_addr = data_desc .addr() - .translate_gva(access_platform, data_desc.len() as usize); + .translate_gva(access_platform, data_desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?; let status_desc = desc_chain.next().ok_or(Error::NoStatusDescriptor)?; @@ -168,7 +170,8 @@ impl CtrlQueue { if ok { VIRTIO_NET_OK } else { VIRTIO_NET_ERR } as u8, status_desc .addr() - .translate_gva(access_platform, status_desc.len() as usize), + .translate_gva(access_platform, status_desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?, ) .map_err(Error::GuestMemory)?; // Per virtio spec 2.6.8, used_len is the number of bytes written diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index c0b8825e71..a569031815 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -69,7 +69,10 @@ impl TxVirtio { while let Some(desc) = next_desc { let desc_addr = desc .addr() - .translate_gva(access_platform, desc.len() as usize); + .translate_gva(access_platform, desc.len() as usize) + .map_err(|e| { + NetQueuePairError::GuestMemory(vm_memory::GuestMemoryError::IOError(e)) + })?; if !desc.is_write_only() && desc.len() > 0 { let buf = desc_chain .memory() @@ -207,7 +210,10 @@ impl RxVirtio { .memory() .checked_offset( desc.addr() - .translate_gva(access_platform, desc.len() as usize), + .translate_gva(access_platform, desc.len() as usize) + .map_err(|e| { + NetQueuePairError::GuestMemory(vm_memory::GuestMemoryError::IOError(e)) + })?, 10, ) .ok_or(NetQueuePairError::DescriptorInvalidHeader)?; @@ -217,7 +223,10 @@ impl RxVirtio { while let Some(desc) = next_desc { let desc_addr = desc .addr() - .translate_gva(access_platform, desc.len() as usize); + .translate_gva(access_platform, desc.len() as usize) + .map_err(|e| { + NetQueuePairError::GuestMemory(vm_memory::GuestMemoryError::IOError(e)) + })?; if desc.is_write_only() && desc.len() > 0 { let buf = desc_chain .memory() diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index 96282b5228..d2d57b9e50 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -221,7 +221,10 @@ impl ConsoleEpollHandler { .write_slice( &source_slice[..], desc.addr() - .translate_gva(self.access_platform.as_deref(), desc.len() as usize), + .translate_gva(self.access_platform.as_deref(), desc.len() as usize) + .map_err(|e| { + Error::GuestMemoryWrite(vm_memory::GuestMemoryError::IOError(e)) + })?, ) .map_err(Error::GuestMemoryWrite)?; @@ -259,10 +262,11 @@ impl ConsoleEpollHandler { desc_chain .memory() .write_volatile_to( - desc.addr().translate_gva( - self.access_platform.as_deref(), - desc.len() as usize, - ), + desc.addr() + .translate_gva(self.access_platform.as_deref(), desc.len() as usize) + .map_err(|e| { + Error::GuestMemoryRead(vm_memory::GuestMemoryError::IOError(e)) + })?, &mut buf, desc.len() as usize, ) diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index fd995747c2..3abec1c0f0 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -123,7 +123,8 @@ impl Request { .memory() .read_obj( desc.addr() - .translate_gva(access_platform, desc.len() as usize), + .translate_gva(access_platform, desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?, ) .map_err(Error::GuestMemory)?; @@ -147,7 +148,8 @@ impl Request { type_: request_type, status_addr: status_desc .addr() - .translate_gva(access_platform, status_desc.len() as usize), + .translate_gva(access_platform, status_desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?, }) } } diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 2133cb79ee..6bb0269c5e 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -78,7 +78,10 @@ impl RngEpollHandler { .memory() .read_volatile_from( desc.addr() - .translate_gva(self.access_platform.as_deref(), desc.len() as usize), + .translate_gva(self.access_platform.as_deref(), desc.len() as usize) + .map_err(|e| { + Error::GuestMemoryWrite(vm_memory::GuestMemoryError::IOError(e)) + })?, &mut self.random_file, desc.len() as usize, ) diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index c59c454b77..11e1d3ac70 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -287,15 +287,36 @@ impl VirtioPciCommonConfig { q.set_ready(ready); // Translate address of descriptor table and vrings. if ready && let Some(access_platform) = &self.access_platform { - let desc_table = access_platform + let desc_table = match access_platform .translate_gva(q.desc_table(), get_vring_size(VringType::Desc, q.size())) - .unwrap(); - let avail_ring = access_platform + { + Ok(addr) => addr, + Err(e) => { + error!("Failed to translate desc_table GVA: {e}"); + q.set_ready(false); + return; + } + }; + let avail_ring = match access_platform .translate_gva(q.avail_ring(), get_vring_size(VringType::Avail, q.size())) - .unwrap(); - let used_ring = access_platform + { + Ok(addr) => addr, + Err(e) => { + error!("Failed to translate avail_ring GVA: {e}"); + q.set_ready(false); + return; + } + }; + let used_ring = match access_platform .translate_gva(q.used_ring(), get_vring_size(VringType::Used, q.size())) - .unwrap(); + { + Ok(addr) => addr, + Err(e) => { + error!("Failed to translate used_ring GVA: {e}"); + q.set_ready(false); + return; + } + }; q.set_desc_table_address( Some((desc_table & 0xffff_ffff) as u32), Some((desc_table >> 32) as u32), diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 7cd3415181..a35c35eb56 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -88,6 +88,8 @@ pub enum Error { SetVringKick(#[source] vhost::Error), #[error("Failed to set vring size")] SetVringNum(#[source] vhost::Error), + #[error("Failed to translate address")] + TranslateAddress(#[source] std::io::Error), } pub type Result = std::result::Result; @@ -246,18 +248,27 @@ impl Vdpa { queue_max_size, queue_size, flags: 0u32, - desc_table_addr: queue.desc_table().translate_gpa( - self.common.access_platform.as_deref(), - queue_size as usize * std::mem::size_of::(), - ), - used_ring_addr: queue.used_ring().translate_gpa( - self.common.access_platform.as_deref(), - 4 + queue_size as usize * 8, - ), - avail_ring_addr: queue.avail_ring().translate_gpa( - self.common.access_platform.as_deref(), - 4 + queue_size as usize * 2, - ), + desc_table_addr: queue + .desc_table() + .translate_gpa( + self.common.access_platform.as_deref(), + queue_size as usize * std::mem::size_of::(), + ) + .map_err(Error::TranslateAddress)?, + used_ring_addr: queue + .used_ring() + .translate_gpa( + self.common.access_platform.as_deref(), + 4 + queue_size as usize * 8, + ) + .map_err(Error::TranslateAddress)?, + avail_ring_addr: queue + .avail_ring() + .translate_gpa( + self.common.access_platform.as_deref(), + 4 + queue_size as usize * 2, + ) + .map_err(Error::TranslateAddress)?, log_addr: None, }; diff --git a/virtio-devices/src/vsock/packet.rs b/virtio-devices/src/vsock/packet.rs index 57218a5b87..e6b4c5afbb 100644 --- a/virtio-devices/src/vsock/packet.rs +++ b/virtio-devices/src/vsock/packet.rs @@ -142,7 +142,8 @@ impl VsockPacket { let guest_hdr_addr = head .addr() - .translate_gva(access_platform, VSOCK_PKT_HDR_SIZE); + .translate_gva(access_platform, VSOCK_PKT_HDR_SIZE) + .map_err(|_| VsockError::GuestMemory)?; // To avoid TOCTOU issues when reading/writing the VSock packet header in guest memory, // we need to copy the content of the header in the VMM's memory. @@ -178,8 +179,9 @@ impl VsockPacket { desc_chain.memory(), head.addr() .checked_add(VSOCK_PKT_HDR_SIZE as u64) - .unwrap() - .translate_gva(access_platform, buf_size), + .ok_or(VsockError::GuestMemory)? + .translate_gva(access_platform, buf_size) + .map_err(|_| VsockError::GuestMemory)?, buf_size, ) .ok_or(VsockError::GuestMemory)?; @@ -214,7 +216,10 @@ impl VsockPacket { let desc_len = desc.len() as usize; if desc_len > 0 && offset < total_len { let to_copy = std::cmp::min(desc_len, total_len - offset); - let desc_addr = desc.addr().translate_gva(access_platform, desc_len); + let desc_addr = desc + .addr() + .translate_gva(access_platform, desc_len) + .map_err(|_| VsockError::GuestMemory)?; desc_chain .memory() .read_slice(&mut owned[offset..offset + to_copy], desc_addr) @@ -242,7 +247,10 @@ impl VsockPacket { let buf_size = buf_desc.len() as usize; let buf_ptr = get_host_address_range( desc_chain.memory(), - buf_desc.addr().translate_gva(access_platform, buf_size), + buf_desc + .addr() + .translate_gva(access_platform, buf_size) + .map_err(|_| VsockError::GuestMemory)?, buf_size, ) .ok_or(VsockError::GuestMemory)?; @@ -283,7 +291,8 @@ impl VsockPacket { let guest_hdr_addr = head .addr() - .translate_gva(access_platform, VSOCK_PKT_HDR_SIZE); + .translate_gva(access_platform, VSOCK_PKT_HDR_SIZE) + .map_err(|_| VsockError::GuestMemory)?; // To avoid TOCTOU issues when reading/writing the VSock packet header in guest memory, // we need to copy the content of the header in the VMM's memory. @@ -313,7 +322,10 @@ impl VsockPacket { buf: Some(PacketBuffer::Borrowed { ptr: get_host_address_range( desc_chain.memory(), - buf_desc.addr().translate_gva(access_platform, buf_size), + buf_desc + .addr() + .translate_gva(access_platform, buf_size) + .map_err(|_| VsockError::GuestMemory)?, buf_size, ) .ok_or(VsockError::GuestMemory)?, @@ -330,8 +342,9 @@ impl VsockPacket { desc_chain.memory(), head.addr() .checked_add(VSOCK_PKT_HDR_SIZE as u64) - .unwrap() - .translate_gva(access_platform, buf_size), + .ok_or(VsockError::GuestMemory)? + .translate_gva(access_platform, buf_size) + .map_err(|_| VsockError::GuestMemory)?, buf_size, ) .ok_or(VsockError::GuestMemory)?, diff --git a/vm-virtio/src/lib.rs b/vm-virtio/src/lib.rs index c560e5c86e..fbd94b2b72 100644 --- a/vm-virtio/src/lib.rs +++ b/vm-virtio/src/lib.rs @@ -101,32 +101,60 @@ pub trait AccessPlatform: Send + Sync + Debug { } pub trait Translatable { - fn translate_gva(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self; - fn translate_gpa(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self; + fn translate_gva( + &self, + access_platform: Option<&dyn AccessPlatform>, + len: usize, + ) -> std::result::Result + where + Self: Sized; + fn translate_gpa( + &self, + access_platform: Option<&dyn AccessPlatform>, + len: usize, + ) -> std::result::Result + where + Self: Sized; } impl Translatable for GuestAddress { - fn translate_gva(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self { - GuestAddress(self.0.translate_gva(access_platform, len)) + fn translate_gva( + &self, + access_platform: Option<&dyn AccessPlatform>, + len: usize, + ) -> std::result::Result { + Ok(GuestAddress(self.0.translate_gva(access_platform, len)?)) } - fn translate_gpa(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self { - GuestAddress(self.0.translate_gpa(access_platform, len)) + fn translate_gpa( + &self, + access_platform: Option<&dyn AccessPlatform>, + len: usize, + ) -> std::result::Result { + Ok(GuestAddress(self.0.translate_gpa(access_platform, len)?)) } } impl Translatable for u64 { - fn translate_gva(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self { + fn translate_gva( + &self, + access_platform: Option<&dyn AccessPlatform>, + len: usize, + ) -> std::result::Result { if let Some(access_platform) = access_platform { - access_platform.translate_gva(*self, len as u64).unwrap() + access_platform.translate_gva(*self, len as u64) } else { - *self + Ok(*self) } } - fn translate_gpa(&self, access_platform: Option<&dyn AccessPlatform>, len: usize) -> Self { + fn translate_gpa( + &self, + access_platform: Option<&dyn AccessPlatform>, + len: usize, + ) -> std::result::Result { if let Some(access_platform) = access_platform { - access_platform.translate_gpa(*self, len as u64).unwrap() + access_platform.translate_gpa(*self, len as u64) } else { - *self + Ok(*self) } } } From c657ea6e2368c8017525293abe5d5b9ee887507c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:53:59 +0000 Subject: [PATCH 1192/1893] 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] --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a6e8defba4..bc7c3e152e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -86,7 +86,7 @@ jobs: 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: | From 6d0d4bc5e2fdf9fc8da1ff6c6a37ff14f9718e25 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 9 Apr 2026 22:24:37 +0200 Subject: [PATCH 1193/1893] 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 --- vmm/src/cpu.rs | 101 ++++++++++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 39 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 4b15cffc31..061c20d47e 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -682,7 +682,7 @@ pub struct CpuManager { reset_evt: EventFd, #[cfg(feature = "guest_debug")] vm_debug_evt: EventFd, - vcpu_states: Vec, + vcpu_states: Arc>>, selected_cpu: u32, vcpus: Vec>>, seccomp_action: SeccompAction, @@ -741,6 +741,7 @@ impl BusDevice for CpuManager { fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { // The Linux kernel, quite reasonably, doesn't zero the memory it gives us. data.fill(0); + let vcpu_states = self.vcpu_states.lock().unwrap(); match offset { CPU_SELECTION_OFFSET => { @@ -750,7 +751,7 @@ impl BusDevice for CpuManager { } CPU_STATUS_OFFSET => { if self.selected_cpu < self.max_vcpus() { - let state = &self.vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; + let state = &vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; if state.active() { data[0] |= 1 << CPU_ENABLE_FLAG; } @@ -779,23 +780,28 @@ impl BusDevice for CpuManager { } CPU_STATUS_OFFSET => { if self.selected_cpu < self.max_vcpus() { - let state = &mut self.vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; - // The ACPI code writes back a 1 to acknowledge the insertion - if (data[0] & (1 << CPU_INSERTING_FLAG) == 1 << CPU_INSERTING_FLAG) - && state.inserting - { - state.inserting = false; - } - // Ditto for removal - if (data[0] & (1 << CPU_REMOVING_FLAG) == 1 << CPU_REMOVING_FLAG) - && state.removing - { - state.removing = false; - } - // Trigger removal of vCPU - if data[0] & (1 << CPU_EJECT_FLAG) == 1 << CPU_EJECT_FLAG - && let Err(e) = self.remove_vcpu(self.selected_cpu) - { + let eject = { + // This structure is not shared with the vCPU thread, therefore, holding the + // lock for the entire function doesn't cause any deadlock. + let mut vcpu_states = self.vcpu_states.lock().unwrap(); + let state = &mut vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; + + if (data[0] & (1 << CPU_INSERTING_FLAG) == 1 << CPU_INSERTING_FLAG) + && state.inserting + { + state.inserting = false; + } + + if (data[0] & (1 << CPU_REMOVING_FLAG) == 1 << CPU_REMOVING_FLAG) + && state.removing + { + state.removing = false; + } + + data[0] & (1 << CPU_EJECT_FLAG) == 1 << CPU_EJECT_FLAG + }; + + if eject && let Err(e) = self.remove_vcpu(self.selected_cpu) { error!("Error removing vCPU: {e:?}"); } } else { @@ -907,6 +913,7 @@ impl CpuManager { let max_vcpus = usize::try_from(config.max_vcpus).unwrap(); let mut vcpu_states = Vec::with_capacity(max_vcpus); vcpu_states.resize_with(max_vcpus, VcpuState::default); + let vcpu_states = Arc::new(Mutex::new(vcpu_states)); let hypervisor_type = hypervisor.hypervisor_type(); #[cfg(target_arch = "x86_64")] let cpu_vendor = hypervisor.get_cpu_vendor(); @@ -1176,14 +1183,14 @@ impl CpuManager { let vcpus_pause_signalled = self.vcpus_pause_signalled.clone(); let vcpus_kick_signalled = self.vcpus_kick_signalled.clone(); - let vcpu_kill = self.vcpu_states[usize::try_from(vcpu_id).unwrap()] - .kill - .clone(); - let vcpu_run_interrupted = self.vcpu_states[usize::try_from(vcpu_id).unwrap()] + let mut vcpu_states = self.vcpu_states.lock().unwrap(); + + let vcpu_kill = vcpu_states[usize::try_from(vcpu_id).unwrap()].kill.clone(); + let vcpu_run_interrupted = vcpu_states[usize::try_from(vcpu_id).unwrap()] .vcpu_run_interrupted .clone(); let panic_vcpu_run_interrupted = vcpu_run_interrupted.clone(); - let vcpu_paused = self.vcpu_states[usize::try_from(vcpu_id).unwrap()] + let vcpu_paused = vcpu_states[usize::try_from(vcpu_id).unwrap()] .paused .clone(); @@ -1470,8 +1477,8 @@ impl CpuManager { // On hot plug calls into this function entry_point is None. It is for // those hotplug CPU additions that we need to set the inserting flag. - self.vcpu_states[usize::try_from(vcpu_id).unwrap()].handle = handle; - self.vcpu_states[usize::try_from(vcpu_id).unwrap()].inserting = inserting; + vcpu_states[usize::try_from(vcpu_id).unwrap()].handle = handle; + vcpu_states[usize::try_from(vcpu_id).unwrap()].inserting = inserting; Ok(()) } @@ -1515,17 +1522,20 @@ impl CpuManager { } fn mark_vcpus_for_removal(&mut self, desired_vcpus: u32) { + let mut vcpu_states = self.vcpu_states.lock().unwrap(); + let present_vcpus = Self::active_vcpus(&vcpu_states); + // Mark vCPUs for removal, actual removal happens on ejection - for cpu_id in desired_vcpus..self.present_vcpus() { - self.vcpu_states[usize::try_from(cpu_id).unwrap()].removing = true; - self.vcpu_states[usize::try_from(cpu_id).unwrap()] + for cpu_id in desired_vcpus..present_vcpus { + vcpu_states[usize::try_from(cpu_id).unwrap()].removing = true; + vcpu_states[usize::try_from(cpu_id).unwrap()] .pending_removal .store(true, Ordering::SeqCst); } } pub fn check_pending_removed_vcpu(&mut self) -> bool { - for state in self.vcpu_states.iter() { + for state in self.vcpu_states.lock().unwrap().iter() { if state.active() && state.pending_removal.load(Ordering::SeqCst) { return true; } @@ -1535,7 +1545,8 @@ impl CpuManager { fn remove_vcpu(&mut self, cpu_id: u32) -> Result<()> { info!("Removing vCPU: cpu_id = {cpu_id}"); - let state = &mut self.vcpu_states[usize::try_from(cpu_id).unwrap()]; + let mut vcpu_states = self.vcpu_states.lock().unwrap(); + let state = &mut vcpu_states[usize::try_from(cpu_id).unwrap()]; state.kill.store(true, Ordering::SeqCst); state.signal_thread(); state.wait_until_signal_acknowledged()?; @@ -1631,12 +1642,15 @@ impl CpuManager { /// For the vCPU threads this will interrupt the KVM_RUN ioctl() allowing /// the loop to check the shared state booleans. fn signal_vcpus(&mut self) -> Result<()> { + // Holding the lock for the whole operation is correct: + let vcpu_states = self.vcpu_states.lock().unwrap(); + // Splitting this into two loops reduced the time to pause many vCPUs // massively. Example: 254 vCPUs. >254ms -> ~4ms. - for state in self.vcpu_states.iter() { + for state in vcpu_states.iter() { state.signal_thread(); } - for state in self.vcpu_states.iter() { + for state in vcpu_states.iter() { state.wait_until_signal_acknowledged()?; } @@ -1651,14 +1665,14 @@ impl CpuManager { self.vcpus_pause_signalled.store(false, Ordering::SeqCst); // Unpark all the VCPU threads. - for state in self.vcpu_states.iter() { + for state in self.vcpu_states.lock().unwrap().iter() { state.unpark_thread(); } self.signal_vcpus()?; // Wait for all the threads to finish. This removes the state from the vector. - for mut state in self.vcpu_states.drain(..) { + for mut state in self.vcpu_states.lock().unwrap().drain(..) { state.join_thread()?; } @@ -1691,8 +1705,15 @@ impl CpuManager { self.cpuid.clone() } + /// Locks the vCPU states and calls [`Self::active_vcpus`]. fn present_vcpus(&self) -> u32 { - self.vcpu_states + let lock = self.vcpu_states.lock().unwrap(); + Self::active_vcpus(&lock) + } + + /// Counts the number of active vCPUs (running vCPU threads). + fn active_vcpus(vcpu_states: &[VcpuState]) -> u32 { + vcpu_states .iter() .fold(0, |acc, state| acc + state.active() as u32) } @@ -2651,7 +2672,7 @@ impl Pausable for CpuManager { // The vCPU thread will change its paused state before parking, wait here for each // activated vCPU change their state to ensure they have parked. - for state in self.vcpu_states.iter() { + for state in self.vcpu_states.lock().unwrap().iter() { if state.active() { // wait for vCPU to update state while !state.paused.load(Ordering::SeqCst) { @@ -2669,16 +2690,18 @@ impl Pausable for CpuManager { // their run vCPU loop. self.vcpus_pause_signalled.store(false, Ordering::SeqCst); + let vcpu_states = self.vcpu_states.lock().unwrap(); + // Unpark all the vCPU threads. // Step 1/2: signal each thread { - for state in self.vcpu_states.iter() { + for state in vcpu_states.iter() { state.unpark_thread(); } } // Step 2/2: wait for state ACK { - for state in self.vcpu_states.iter() { + for state in vcpu_states.iter() { // wait for vCPU to update state while state.paused.load(Ordering::SeqCst) { // To avoid a priority inversion with the vCPU thread From 5ff4696cea911a5a90ff286c134ff3ebbd3134bd Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 9 Apr 2026 22:17:32 +0200 Subject: [PATCH 1194/1893] 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 --- vmm/src/cpu.rs | 233 +++++++++++++++++++++----------------- vmm/src/device_manager.rs | 13 ++- 2 files changed, 138 insertions(+), 108 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 061c20d47e..eb892552b0 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -682,8 +682,8 @@ pub struct CpuManager { reset_evt: EventFd, #[cfg(feature = "guest_debug")] vm_debug_evt: EventFd, + // Shared with AcpiCpuHotplugController vcpu_states: Arc>>, - selected_cpu: u32, vcpus: Vec>>, seccomp_action: SeccompAction, vm_ops: Arc, @@ -699,14 +699,6 @@ pub struct CpuManager { core_scheduling_group_leader: Arc, } -const CPU_ENABLE_FLAG: usize = 0; -const CPU_INSERTING_FLAG: usize = 1; -const CPU_REMOVING_FLAG: usize = 2; -const CPU_EJECT_FLAG: usize = 3; - -const CPU_STATUS_OFFSET: u64 = 4; -const CPU_SELECTION_OFFSET: u64 = 0; - /// State of the core scheduling group leader election for VM-wide cookie /// sharing. /// @@ -737,85 +729,6 @@ impl TryFrom for CoreSchedulingLeader { } } -impl BusDevice for CpuManager { - fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { - // The Linux kernel, quite reasonably, doesn't zero the memory it gives us. - data.fill(0); - let vcpu_states = self.vcpu_states.lock().unwrap(); - - match offset { - CPU_SELECTION_OFFSET => { - assert!(data.len() >= core::mem::size_of::()); - data[0..core::mem::size_of::()] - .copy_from_slice(&self.selected_cpu.to_le_bytes()); - } - CPU_STATUS_OFFSET => { - if self.selected_cpu < self.max_vcpus() { - let state = &vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; - if state.active() { - data[0] |= 1 << CPU_ENABLE_FLAG; - } - if state.inserting { - data[0] |= 1 << CPU_INSERTING_FLAG; - } - if state.removing { - data[0] |= 1 << CPU_REMOVING_FLAG; - } - } else { - warn!("Out of range vCPU id: {}", self.selected_cpu); - } - } - _ => { - warn!("Unexpected offset for accessing CPU manager device: {offset:#}"); - } - } - } - - fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { - match offset { - CPU_SELECTION_OFFSET => { - assert!(data.len() >= core::mem::size_of::()); - self.selected_cpu = - u32::from_le_bytes(data[0..core::mem::size_of::()].try_into().unwrap()); - } - CPU_STATUS_OFFSET => { - if self.selected_cpu < self.max_vcpus() { - let eject = { - // This structure is not shared with the vCPU thread, therefore, holding the - // lock for the entire function doesn't cause any deadlock. - let mut vcpu_states = self.vcpu_states.lock().unwrap(); - let state = &mut vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; - - if (data[0] & (1 << CPU_INSERTING_FLAG) == 1 << CPU_INSERTING_FLAG) - && state.inserting - { - state.inserting = false; - } - - if (data[0] & (1 << CPU_REMOVING_FLAG) == 1 << CPU_REMOVING_FLAG) - && state.removing - { - state.removing = false; - } - - data[0] & (1 << CPU_EJECT_FLAG) == 1 << CPU_EJECT_FLAG - }; - - if eject && let Err(e) = self.remove_vcpu(self.selected_cpu) { - error!("Error removing vCPU: {e:?}"); - } - } else { - warn!("Out of range vCPU id: {}", self.selected_cpu); - } - } - _ => { - warn!("Unexpected offset for accessing CPU manager device: {offset:#}"); - } - } - None - } -} - #[derive(Default)] struct VcpuState { inserting: bool, @@ -965,7 +878,6 @@ impl CpuManager { reset_evt, #[cfg(feature = "guest_debug")] vm_debug_evt, - selected_cpu: 0, vcpus: Vec::with_capacity(max_vcpus), seccomp_action, vm_ops, @@ -1543,23 +1455,6 @@ impl CpuManager { false } - fn remove_vcpu(&mut self, cpu_id: u32) -> Result<()> { - info!("Removing vCPU: cpu_id = {cpu_id}"); - let mut vcpu_states = self.vcpu_states.lock().unwrap(); - let state = &mut vcpu_states[usize::try_from(cpu_id).unwrap()]; - state.kill.store(true, Ordering::SeqCst); - state.signal_thread(); - state.wait_until_signal_acknowledged()?; - state.join_thread()?; - state.handle = None; - - // Once the thread has exited, clear the "kill" so that it can reused - state.kill.store(false, Ordering::SeqCst); - state.pending_removal.store(false, Ordering::SeqCst); - - Ok(()) - } - pub fn create_boot_vcpus( &mut self, snapshot: Option<&Snapshot>, @@ -3203,6 +3098,132 @@ impl CpuElf64Writable for CpuManager { } } +/// MMIO-accessible controller for handling ACPI hotplug and unplug events. +/// +/// Shares state about the vCPUs with the [`CpuManager`]. +pub struct AcpiCpuHotplugController { + /// The currently selected CPU by the guest. + selected_cpu: u32, + /// Shared vCPU state with [`CpuManager`]. + vcpu_states: Arc>>, + /// Maximum number of vCPUS of the VM. + max_vcpus: u32, +} + +impl AcpiCpuHotplugController { + const CPU_ENABLE_FLAG: usize = 0; + const CPU_INSERTING_FLAG: usize = 1; + const CPU_REMOVING_FLAG: usize = 2; + const CPU_EJECT_FLAG: usize = 3; + + const CPU_SELECTION_OFFSET: u64 = 0; + const CPU_STATUS_OFFSET: u64 = 4; + + /// Creates a new [`AcpiCpuHotplugController`]. + pub fn new(cpu_manager: &CpuManager) -> AcpiCpuHotplugController { + Self { + max_vcpus: cpu_manager.config.max_vcpus, + selected_cpu: 0, + vcpu_states: cpu_manager.vcpu_states.clone(), + } + } + + /// Removes a vCPU from the guest. + /// + /// The corresponding vCPU thread will be gracefully stopped and joined. + fn remove_vcpu(cpu_id: u32, state: &mut VcpuState) -> Result<()> { + info!("Removing vCPU: cpu_id = {cpu_id}"); + state.kill.store(true, Ordering::SeqCst); + state.signal_thread(); + state.wait_until_signal_acknowledged()?; + state.join_thread()?; + state.handle = None; + + // Once the thread has exited, clear the "kill" so that it can reused + state.kill.store(false, Ordering::SeqCst); + state.pending_removal.store(false, Ordering::SeqCst); + + Ok(()) + } +} + +impl BusDevice for AcpiCpuHotplugController { + fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { + // The Linux kernel, quite reasonably, doesn't zero the memory it gives us. + data.fill(0); + let vcpu_states = self.vcpu_states.lock().unwrap(); + + match offset { + Self::CPU_SELECTION_OFFSET => { + assert!(data.len() >= core::mem::size_of::()); + data[0..core::mem::size_of::()] + .copy_from_slice(&self.selected_cpu.to_le_bytes()); + } + Self::CPU_STATUS_OFFSET => { + if self.selected_cpu < self.max_vcpus { + let state = &vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; + if state.active() { + data[0] |= 1 << Self::CPU_ENABLE_FLAG; + } + if state.inserting { + data[0] |= 1 << Self::CPU_INSERTING_FLAG; + } + if state.removing { + data[0] |= 1 << Self::CPU_REMOVING_FLAG; + } + } else { + warn!("Out of range vCPU id: {}", self.selected_cpu); + } + } + _ => { + warn!("Unexpected offset for accessing CPU manager device: {offset:#}"); + } + } + } + + fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { + match offset { + Self::CPU_SELECTION_OFFSET => { + assert!(data.len() >= core::mem::size_of::()); + self.selected_cpu = + u32::from_le_bytes(data[0..core::mem::size_of::()].try_into().unwrap()); + } + Self::CPU_STATUS_OFFSET => { + if self.selected_cpu < self.max_vcpus { + // This structure is not shared with the vCPU thread, therefore, holding the + // lock for the entire function doesn't cause any deadlock. + let mut vcpu_states = self.vcpu_states.lock().unwrap(); + let state = &mut vcpu_states[usize::try_from(self.selected_cpu).unwrap()]; + // The ACPI code writes back a 1 to acknowledge the insertion + if (data[0] & (1 << Self::CPU_INSERTING_FLAG) == 1 << Self::CPU_INSERTING_FLAG) + && state.inserting + { + state.inserting = false; + } + // Ditto for removal + if (data[0] & (1 << Self::CPU_REMOVING_FLAG) == 1 << Self::CPU_REMOVING_FLAG) + && state.removing + { + state.removing = false; + } + // Trigger removal of vCPU: + if data[0] & (1 << Self::CPU_EJECT_FLAG) == 1 << Self::CPU_EJECT_FLAG + && let Err(e) = Self::remove_vcpu(self.selected_cpu, state) + { + error!("Error removing vCPU: {e:?}"); + } + } else { + warn!("Out of range vCPU id: {}", self.selected_cpu); + } + } + _ => { + warn!("Unexpected offset for accessing CPU manager device: {offset:#}"); + } + } + None + } +} + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] #[cfg(test)] mod unit_tests { diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 9522be53d2..39281e53ed 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -124,7 +124,7 @@ use vm_virtio::{AccessPlatform, VirtioDeviceType}; use vmm_sys_util::eventfd::EventFd; use crate::console_devices::{ConsoleDeviceError, ConsoleInfo, ConsoleTransport}; -use crate::cpu::{CPU_MANAGER_ACPI_SIZE, CpuManager}; +use crate::cpu::{AcpiCpuHotplugController, CPU_MANAGER_ACPI_SIZE, CpuManager}; use crate::device_tree::{DeviceNode, DeviceTree}; use crate::interrupt::{LegacyUserspaceInterruptManager, MsiInterruptManager}; use crate::memory_manager::{Error as MemoryManagerError, MEMORY_MANAGER_ACPI_SIZE, MemoryManager}; @@ -1026,6 +1026,10 @@ pub struct DeviceManager { // CPU Manager cpu_manager: Arc>, + /// Owned version needed to keep the bus device alive (the bus only holds + /// a weak reference). + _acpi_cpu_hotplug_controller: Arc>, + // The virtio devices on the system virtio_devices: Vec, @@ -1324,6 +1328,10 @@ impl DeviceManager { )?); } + let acpi_cpu_hotplug_controller = + AcpiCpuHotplugController::new(&cpu_manager.lock().unwrap()); + let acpi_cpu_hotplug_controller = Arc::new(Mutex::new(acpi_cpu_hotplug_controller)); + if dynamic { let acpi_address = address_manager .allocator @@ -1335,7 +1343,7 @@ impl DeviceManager { address_manager .mmio_bus .insert( - cpu_manager.clone(), + acpi_cpu_hotplug_controller.clone(), acpi_address.0, CPU_MANAGER_ACPI_SIZE as u64, ) @@ -1429,6 +1437,7 @@ impl DeviceManager { fw_cfg: None, #[cfg(feature = "ivshmem")] ivshmem_device: None, + _acpi_cpu_hotplug_controller: acpi_cpu_hotplug_controller, }; let device_manager = Arc::new(Mutex::new(device_manager)); From 7eab5901adf733932d0e856b9cd5595e220e4e20 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 9 Apr 2026 22:24:50 +0200 Subject: [PATCH 1195/1893] vmm: improve misc documentation This improves the documentation at various places. On-behalf-of: SAP philipp.schuster@sap.com Signed-off-by: Philipp Schuster --- vm-device/src/bus.rs | 3 +++ vmm/src/cpu.rs | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/vm-device/src/bus.rs b/vm-device/src/bus.rs index 2897ac303e..eacca24987 100644 --- a/vm-device/src/bus.rs +++ b/vm-device/src/bus.rs @@ -147,6 +147,9 @@ impl Bus { None } + /// Inserts a bus device into the bus. + /// + /// The bus will only hold a weak reference to the object. #[allow(clippy::needless_pass_by_value)] pub fn insert(&self, device: Arc, base: u64, len: u64) -> Result<()> { if len == 0 { diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index eb892552b0..1450e0a8e2 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -729,13 +729,17 @@ impl TryFrom for CoreSchedulingLeader { } } +/// Management structure for a vCPU (thread). #[derive(Default)] struct VcpuState { inserting: bool, removing: bool, pending_removal: Arc, + /// Handle to the vCPU thread. handle: Option>, + /// Instructs the thread to exit the run-vCPU loop. kill: Arc, + /// Used to ACK interruption from the run vCPU loop to the CPU Manager. vcpu_run_interrupted: Arc, /// Used to ACK state changes from the run vCPU loop to the CPU Manager. paused: Arc, @@ -750,6 +754,13 @@ impl VcpuState { /// /// Please call [`Self::wait_until_signal_acknowledged`] afterward to block /// until the vCPU thread has acknowledged the signal. + /// + /// If the thread is in KVM_RUN (or MSHV_RUN_VP or equivalent), this kicks + /// the thread out of kernel space. If the thread is in user-space, the + /// thread will just handle the event eventually. If the thread is in + /// user-space but about to enter kernel-space, the user-space signal + /// handler will make sure that the next kernel entry of the given + /// vCPU thread immediately exits to handle the event in user-space. fn signal_thread(&self) { if let Some(handle) = self.handle.as_ref() { // SAFETY: FFI call with correct arguments @@ -1532,10 +1543,10 @@ impl CpuManager { } } - /// Signal to the spawned threads (vCPUs and console signal handler). + /// Signals all vCPU threads and waits for them to ACK the interruption. /// - /// For the vCPU threads this will interrupt the KVM_RUN ioctl() allowing - /// the loop to check the shared state booleans. + /// Calls [`VcpuState::signal_thread`] and + /// [`VcpuState::wait_until_signal_acknowledged`] for each vCPU. fn signal_vcpus(&mut self) -> Result<()> { // Holding the lock for the whole operation is correct: let vcpu_states = self.vcpu_states.lock().unwrap(); From edfd597993583dd728c4102b67eae04b3c46059b Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 10 Apr 2026 15:23:45 +0200 Subject: [PATCH 1196/1893] tests: fix weird "console=ttyS0rw" string On-behalf-of: SAP philipp.schuster@sap.com Signed-off-by: Philipp Schuster --- cloud-hypervisor/tests/integration.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index ebf08d8417..c4bccc46ad 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1941,7 +1941,7 @@ mod common_parallel { .args([ "--cmdline", DIRECT_KERNEL_BOOT_CMDLINE - .replace("console=hvc0 ", console_str) + .replace("console=hvc0", console_str) .as_str(), ]) .default_disks() @@ -1997,7 +1997,7 @@ mod common_parallel { .args([ "--cmdline", DIRECT_KERNEL_BOOT_CMDLINE - .replace("console=hvc0 ", console_str) + .replace("console=hvc0", console_str) .as_str(), ]) .default_disks() @@ -2055,7 +2055,7 @@ mod common_parallel { .args([ "--cmdline", DIRECT_KERNEL_BOOT_CMDLINE - .replace("console=hvc0 ", console_str) + .replace("console=hvc0", console_str) .as_str(), ]) .default_disks() @@ -2613,7 +2613,7 @@ mod common_parallel { .args([ "--cmdline", DIRECT_KERNEL_BOOT_CMDLINE - .replace("console=hvc0 ", console_str) + .replace("console=hvc0", console_str) .as_str(), ]) .args(["--serial", "tty"]) @@ -5621,7 +5621,7 @@ mod common_parallel { .args([ "--cmdline", DIRECT_KERNEL_BOOT_CMDLINE - .replace("console=hvc0 ", tty_str) + .replace("console=hvc0", tty_str) .as_str(), ]) .capture_output() From 39844e883943146fff19f8d3450b0882ac069d6f Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 15 Apr 2026 10:22:14 +0200 Subject: [PATCH 1197/1893] 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 --- CONTRIBUTING.md | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cdda170b4a..943f53c39d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,11 +11,30 @@ license of those projects. New code should be under the [Apache v2 License](https://opensource.org/licenses/Apache-2.0). -## Coding Style +## Coding Style & Code Comments -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). +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. + +[Rust Style]: https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src ## Basic Checks From c35749fb3959f2d09b233571b2a3a36ce40c933a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 15 Apr 2026 15:37:55 +0200 Subject: [PATCH 1198/1893] 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 Signed-off-by: Anatol Belski --- block/src/qcow/backing.rs | 12 ++++++------ block/src/qcow_sync.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/block/src/qcow/backing.rs b/block/src/qcow/backing.rs index e5e037b0ad..754618f132 100644 --- a/block/src/qcow/backing.rs +++ b/block/src/qcow/backing.rs @@ -48,7 +48,7 @@ impl BackingRead for RawBacking { /// 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 Qcow2MetadataBacking { +pub(crate) struct Qcow2Backing { pub(crate) metadata: Arc, pub(crate) data_fd: OwnedFd, pub(crate) backing_file: Option>, @@ -56,9 +56,9 @@ pub(crate) struct Qcow2MetadataBacking { // SAFETY: All reads go through QcowMetadata which uses RwLock // and pread64 which is position independent and thread safe. -unsafe impl Sync for Qcow2MetadataBacking {} +unsafe impl Sync for Qcow2Backing {} -impl BackingRead for Qcow2MetadataBacking { +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 { @@ -75,7 +75,7 @@ impl BackingRead for Qcow2MetadataBacking { } } -impl Qcow2MetadataBacking { +impl Qcow2Backing { /// Resolve cluster mappings via metadata then read allocated clusters /// with pread64. fn read_clusters(&self, address: u64, buf: &mut [u8]) -> io::Result<()> { @@ -125,7 +125,7 @@ impl Qcow2MetadataBacking { } } -impl Drop for Qcow2MetadataBacking { +impl Drop for Qcow2Backing { fn drop(&mut self) { self.metadata.shutdown(); } @@ -152,7 +152,7 @@ pub fn shared_backing_from(bf: BackingFile) -> BlockResult> } BackingKind::Qcow { inner, backing } => { let data_fd = dup_fd(inner.raw_file.as_fd())?; - Ok(Arc::new(Qcow2MetadataBacking { + Ok(Arc::new(Qcow2Backing { metadata: Arc::new(QcowMetadata::new(*inner)), data_fd, backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?, diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index f948a3b5fd..ec1637543a 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -783,7 +783,7 @@ mod unit_tests { #[test] fn test_multi_queue_concurrent_reads_qcow2_backing() { - // Same as above but reads go through a Qcow2MetadataBacking, + // Same as above but reads go through a Qcow2Backing, // exercising concurrent metadata resolution + pread64 in the backing. let backing_temp = TempFile::new().unwrap(); let cluster_size = 1u64 << 16; @@ -1123,7 +1123,7 @@ mod unit_tests { #[test] fn test_qcow2_backing_cross_cluster_read() { // Read spanning a cluster boundary through qcow2 backing. - // Exercises the read_clusters loop in Qcow2MetadataBacking. + // Exercises the read_clusters loop in Qcow2Backing. let cluster_size = 1u64 << 16; let file_size = cluster_size * 4; let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); From 6c1da4f5c1172edf21ea349167189ef7049d5723 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 19:50:15 +0200 Subject: [PATCH 1199/1893] 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 --- block/src/qcow/raw_file.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index 56ec797355..c2a01811f8 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -125,6 +125,10 @@ impl RawFile { self.direct_io } + pub fn alignment(&self) -> usize { + self.alignment + } + /// Returns true if the file was opened with write access. pub fn is_writable(&self) -> bool { // SAFETY: fcntl with F_GETFL is safe and doesn't modify the file descriptor From f50b5ab1f2a0accea210d6bafafe7860e274ffcc Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 15 Apr 2026 12:17:24 +0200 Subject: [PATCH 1200/1893] 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 Co-authored-by: Anatol Belski Signed-off-by: Anatol Belski --- block/src/qcow_common.rs | 121 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 112 insertions(+), 9 deletions(-) diff --git a/block/src/qcow_common.rs b/block/src/qcow_common.rs index dc492e36d2..08a1a9ca3e 100644 --- a/block/src/qcow_common.rs +++ b/block/src/qcow_common.rs @@ -9,6 +9,7 @@ //! Position-independent I/O (`pread_exact`, `pwrite_all`) and iovec //! scatter/gather helpers used by both `qcow_sync` and `qcow_async`. +use std::alloc::{Layout, alloc_zeroed, dealloc}; use std::cmp::min; use std::os::fd::RawFd; use std::{io, ptr, slice}; @@ -67,6 +68,98 @@ pub fn pwrite_all(fd: RawFd, buf: &[u8], offset: u64) -> io::Result<()> { Ok(()) } +/// RAII wrapper for an aligned heap buffer required by O_DIRECT. +pub struct AlignedBuf { + ptr: *mut u8, + layout: Layout, +} + +impl AlignedBuf { + pub fn new(size: usize, alignment: usize) -> io::Result { + let size = size.max(1).next_multiple_of(alignment); + let layout = Layout::from_size_align(size, 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(AlignedBuf { ptr, layout }) + } + + pub fn as_mut_slice(&mut self, len: usize) -> &mut [u8] { + let len = len.min(self.layout.size()); + // SAFETY: ptr is valid for layout.size() bytes; len <= layout.size(). + unsafe { slice::from_raw_parts_mut(self.ptr, len) } + } + + pub fn as_slice(&self, len: usize) -> &[u8] { + let len = len.min(self.layout.size()); + // SAFETY: ptr is valid for layout.size() bytes; len <= layout.size(). + unsafe { slice::from_raw_parts(self.ptr, len) } + } + + #[cfg(test)] + pub fn layout(&self) -> &Layout { + &self.layout + } + + #[cfg(test)] + pub fn ptr(&self) -> *const u8 { + self.ptr + } +} + +impl Drop for AlignedBuf { + fn drop(&mut self) { + // SAFETY: ptr was allocated by alloc_zeroed with self.layout. + unsafe { dealloc(self.ptr, self.layout) }; + } +} + +/// Read into `buf` via an aligned bounce buffer when O_DIRECT requires it. +pub fn aligned_pread(fd: RawFd, buf: &mut [u8], offset: u64, alignment: usize) -> io::Result<()> { + if alignment == 0 + || ((buf.as_ptr() as usize).is_multiple_of(alignment) + && buf.len().is_multiple_of(alignment) + && (offset as usize).is_multiple_of(alignment)) + { + return pread_exact(fd, buf, offset); + } + + let aligned_offset = offset & !(alignment as u64 - 1); + let head = (offset - aligned_offset) as usize; + let aligned_len = (head + buf.len()).next_multiple_of(alignment); + let mut bounce = AlignedBuf::new(aligned_len, alignment)?; + pread_exact(fd, bounce.as_mut_slice(aligned_len), aligned_offset)?; + buf.copy_from_slice(&bounce.as_slice(aligned_len)[head..head + buf.len()]); + Ok(()) +} + +/// Write `buf` via an aligned bounce buffer when O_DIRECT requires it. +pub fn aligned_pwrite(fd: RawFd, buf: &[u8], offset: u64, alignment: usize) -> io::Result<()> { + if alignment == 0 + || ((buf.as_ptr() as usize).is_multiple_of(alignment) + && buf.len().is_multiple_of(alignment) + && (offset as usize).is_multiple_of(alignment)) + { + return pwrite_all(fd, buf, offset); + } + + let aligned_offset = offset & !(alignment as u64 - 1); + let head = (offset - aligned_offset) as usize; + let aligned_len = (head + buf.len()).next_multiple_of(alignment); + let mut bounce = AlignedBuf::new(aligned_len, alignment)?; + + // Read-modify-write: read the existing aligned region, overlay our data. + pread_exact(fd, bounce.as_mut_slice(aligned_len), aligned_offset)?; + bounce.as_mut_slice(aligned_len)[head..head + buf.len()].copy_from_slice(buf); + pwrite_all(fd, bounce.as_slice(aligned_len), aligned_offset) +} + // -- iovec helper functions -- // // Operate on the iovec array as a flat byte stream. @@ -129,33 +222,43 @@ pub unsafe fn zero_fill_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) } } -/// Gather bytes from iovecs starting at the given byte offset into a Vec. +/// Gather bytes from iovecs starting at the given byte offset into `dst`. /// /// # Safety /// Caller must ensure iovecs point to valid, readable memory of sufficient size. -pub unsafe fn gather_from_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) -> Vec { - let mut result = Vec::with_capacity(len); - let mut remaining = len; +pub unsafe fn gather_from_iovecs_into(iovecs: &[libc::iovec], start: usize, dst: &mut [u8]) { + let len = dst.len(); + let mut written = 0usize; let mut pos = 0usize; for iov in iovecs { let iov_end = pos + iov.iov_len; - if iov_end <= start || remaining == 0 { + if iov_end <= start || written == len { pos = iov_end; continue; } let iov_start = start.saturating_sub(pos); let available = iov.iov_len - iov_start; - let count = min(available, remaining); + let count = min(available, len - written); // SAFETY: iov_base is valid for iov_len bytes per caller contract. unsafe { let src = (iov.iov_base as *const u8).add(iov_start); - result.extend_from_slice(slice::from_raw_parts(src, count)); + ptr::copy_nonoverlapping(src, dst.as_mut_ptr().add(written), count); } - remaining -= count; - if remaining == 0 { + written += count; + if written == len { break; } pos = iov_end; } +} + +/// Gather bytes from iovecs starting at the given byte offset into a Vec. +/// +/// # Safety +/// Caller must ensure iovecs point to valid, readable memory of sufficient size. +pub unsafe fn gather_from_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) -> Vec { + let mut result = vec![0u8; len]; + // SAFETY: caller guarantees iovecs are valid; result has len bytes. + unsafe { gather_from_iovecs_into(iovecs, start, &mut result) }; result } From fd8495b342238f35b98f61c27917a2849befd0cb Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 15 Apr 2026 12:18:06 +0200 Subject: [PATCH 1201/1893] 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 --- block/src/qcow_sync.rs | 58 +++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index ec1637543a..648f8d6445 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -22,7 +22,8 @@ use crate::qcow::metadata::{ use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; use crate::qcow_common::{ - gather_from_iovecs, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, + AlignedBuf, aligned_pread, aligned_pwrite, gather_from_iovecs, gather_from_iovecs_into, + pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; pub struct QcowDiskSync { @@ -153,6 +154,8 @@ pub struct QcowSync { /// See the backing_file field on QcowDiskSync. backing_file: Option>, sparse: bool, + /// O_DIRECT alignment requirement (0 = no alignment needed). + alignment: usize, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, } @@ -164,11 +167,13 @@ impl QcowSync { backing_file: Option>, sparse: bool, ) -> Self { + let alignment = data_file.file().alignment(); QcowSync { metadata, data_file, backing_file, sparse, + alignment, eventfd: EventFd::new(libc::EFD_NONBLOCK) .expect("Failed creating EventFd for QcowSync"), completion_list: VecDeque::new(), @@ -208,12 +213,29 @@ impl AsyncIo for QcowSync { offset: host_offset, length, } => { - let mut buf = vec![0u8; length as usize]; - pread_exact(self.data_file.as_raw_fd(), &mut buf, host_offset) + let len = length as usize; + if self.alignment > 0 { + // O_DIRECT, aligned buffer avoids bounce copy. + let mut abuf = AlignedBuf::new(len, self.alignment) + .map_err(AsyncIoError::ReadVectored)?; + aligned_pread( + self.data_file.as_raw_fd(), + abuf.as_mut_slice(len), + host_offset, + self.alignment, + ) .map_err(AsyncIoError::ReadVectored)?; - // SAFETY: iovecs point to valid guest memory buffers - unsafe { scatter_to_iovecs(iovecs, buf_offset, &buf) }; - buf_offset += length as usize; + // SAFETY: iovecs point to valid guest memory buffers + unsafe { scatter_to_iovecs(iovecs, buf_offset, abuf.as_slice(len)) }; + } else { + // No O_DIRECT, plain buffer is fine. + let mut buf = vec![0u8; len]; + pread_exact(self.data_file.as_raw_fd(), &mut buf, host_offset) + .map_err(AsyncIoError::ReadVectored)?; + // SAFETY: iovecs point to valid guest memory buffers + unsafe { scatter_to_iovecs(iovecs, buf_offset, &buf) }; + } + buf_offset += len; } ClusterReadMapping::Compressed { data } => { let len = data.len(); @@ -287,10 +309,28 @@ impl AsyncIo for QcowSync { ClusterWriteMapping::Allocated { offset: host_offset, } => { - // SAFETY: iovecs point to valid guest memory buffers - let buf = unsafe { gather_from_iovecs(iovecs, buf_offset, count) }; - pwrite_all(self.data_file.as_raw_fd(), &buf, host_offset) + if self.alignment > 0 { + // O_DIRECT, gather directly into aligned buffer. + let mut abuf = AlignedBuf::new(count, self.alignment) + .map_err(AsyncIoError::WriteVectored)?; + // SAFETY: iovecs point to valid guest memory buffers + unsafe { + gather_from_iovecs_into(iovecs, buf_offset, abuf.as_mut_slice(count)); + } + aligned_pwrite( + self.data_file.as_raw_fd(), + abuf.as_slice(count), + host_offset, + self.alignment, + ) .map_err(AsyncIoError::WriteVectored)?; + } else { + // No O_DIRECT, plain buffer is fine. + // SAFETY: iovecs point to valid guest memory buffers + let buf = unsafe { gather_from_iovecs(iovecs, buf_offset, count) }; + pwrite_all(self.data_file.as_raw_fd(), &buf, host_offset) + .map_err(AsyncIoError::WriteVectored)?; + } } } buf_offset += count; From cfa60a95b98683c514dc9aa5a241b98a9cf8aee0 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 15 Apr 2026 12:18:36 +0200 Subject: [PATCH 1202/1893] 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 --- block/src/qcow_async.rs | 68 +++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index d58b1b1b75..e65a4ac757 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -27,7 +27,8 @@ use crate::qcow::metadata::{ use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; use crate::qcow_common::{ - gather_from_iovecs, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, + AlignedBuf, aligned_pread, aligned_pwrite, gather_from_iovecs_into, pread_exact, pwrite_all, + scatter_to_iovecs, zero_fill_iovecs, }; use crate::{BatchRequest, RequestType, disk_file}; @@ -172,6 +173,8 @@ pub struct QcowAsync { data_file: QcowRawFile, backing_file: Option>, sparse: bool, + /// O_DIRECT alignment requirement (0 = no alignment needed). + alignment: usize, io_uring: IoUring, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, @@ -185,6 +188,7 @@ impl QcowAsync { sparse: bool, ring_depth: u32, ) -> io::Result { + let alignment = data_file.file().alignment(); let io_uring = IoUring::new(ring_depth)?; let eventfd = EventFd::new(libc::EFD_NONBLOCK)?; io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?; @@ -194,6 +198,7 @@ impl QcowAsync { data_file, backing_file, sparse, + alignment, io_uring, eventfd, completion_list: VecDeque::new(), @@ -241,6 +246,7 @@ impl AsyncIo for QcowAsync { offset as u64, iovecs, total_len, + self.alignment, )? { let fd = self.data_file.as_raw_fd(); let (submitter, mut sq, _) = self.io_uring.split(); @@ -285,6 +291,7 @@ impl AsyncIo for QcowAsync { &self.metadata, &self.data_file, &self.backing_file, + self.alignment, )?; let total_len: usize = iovecs.iter().map(|v| v.iov_len).sum(); @@ -377,6 +384,7 @@ impl AsyncIo for QcowAsync { req.offset as u64, &req.iovecs, total_len, + self.alignment, )? { let fd = self.data_file.as_raw_fd(); // SAFETY: fd is valid and iovecs point to valid guest memory. @@ -408,6 +416,7 @@ impl AsyncIo for QcowAsync { &self.metadata, &self.data_file, &self.backing_file, + self.alignment, )?; sync_completions.push((req.user_data, total_len as i32)); } @@ -448,6 +457,7 @@ impl QcowAsync { address: u64, iovecs: &[libc::iovec], total_len: usize, + alignment: usize, ) -> AsyncIoResult> { let has_backing = backing_file.is_some(); let mappings = metadata @@ -464,7 +474,7 @@ impl QcowAsync { return Ok(Some(*host_offset)); } - Self::scatter_read_sync(mappings, iovecs, data_file, backing_file)?; + Self::scatter_read_sync(mappings, iovecs, data_file, backing_file, alignment)?; Ok(None) } @@ -474,6 +484,7 @@ impl QcowAsync { iovecs: &[libc::iovec], data_file: &QcowRawFile, backing_file: &Option>, + alignment: usize, ) -> AsyncIoResult<()> { let mut buf_offset = 0usize; for mapping in mappings { @@ -489,12 +500,27 @@ impl QcowAsync { offset: host_offset, length, } => { - let mut buf = vec![0u8; length as usize]; - pread_exact(data_file.as_raw_fd(), &mut buf, host_offset) + let len = length as usize; + if alignment > 0 { + let mut abuf = + AlignedBuf::new(len, alignment).map_err(AsyncIoError::ReadVectored)?; + aligned_pread( + data_file.as_raw_fd(), + abuf.as_mut_slice(len), + host_offset, + alignment, + ) .map_err(AsyncIoError::ReadVectored)?; - // SAFETY: iovecs point to valid guest memory buffers. - unsafe { scatter_to_iovecs(iovecs, buf_offset, &buf) }; - buf_offset += length as usize; + // SAFETY: iovecs point to valid guest memory buffers. + unsafe { scatter_to_iovecs(iovecs, buf_offset, abuf.as_slice(len)) }; + } else { + let mut buf = vec![0u8; len]; + pread_exact(data_file.as_raw_fd(), &mut buf, host_offset) + .map_err(AsyncIoError::ReadVectored)?; + // SAFETY: iovecs point to valid guest memory buffers. + unsafe { scatter_to_iovecs(iovecs, buf_offset, &buf) }; + } + buf_offset += len; } ClusterReadMapping::Compressed { data } => { let len = data.len(); @@ -528,6 +554,7 @@ impl QcowAsync { metadata: &QcowMetadata, data_file: &QcowRawFile, backing_file: &Option>, + alignment: usize, ) -> AsyncIoResult<()> { let total_len: usize = iovecs.iter().map(|v| v.iov_len).sum(); let cluster_size = metadata.cluster_size(); @@ -561,10 +588,31 @@ impl QcowAsync { ClusterWriteMapping::Allocated { offset: host_offset, } => { - // SAFETY: iovecs point to valid guest memory buffers. - let buf = unsafe { gather_from_iovecs(iovecs, buf_offset, count) }; - pwrite_all(data_file.as_raw_fd(), &buf, host_offset) + if alignment > 0 { + // O_DIRECT, gather directly into aligned buffer. + let mut abuf = AlignedBuf::new(count, alignment) + .map_err(AsyncIoError::WriteVectored)?; + // SAFETY: iovecs point to valid guest memory buffers + unsafe { + gather_from_iovecs_into(iovecs, buf_offset, abuf.as_mut_slice(count)); + } + aligned_pwrite( + data_file.as_raw_fd(), + abuf.as_slice(count), + host_offset, + alignment, + ) .map_err(AsyncIoError::WriteVectored)?; + } else { + // No O_DIRECT, plain buffer is fine. + let mut buf = vec![0u8; count]; + // SAFETY: iovecs point to valid guest memory buffers. + unsafe { + gather_from_iovecs_into(iovecs, buf_offset, &mut buf); + } + pwrite_all(data_file.as_raw_fd(), &buf, host_offset) + .map_err(AsyncIoError::WriteVectored)?; + } } } buf_offset += count; From b62525f79226e5455772d4a79b53b7b00cada42c Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 22:15:56 +0200 Subject: [PATCH 1203/1893] 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 --- block/src/qcow_sync.rs | 227 +++++++++++++++++++++++++++++++++-------- 1 file changed, 186 insertions(+), 41 deletions(-) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 648f8d6445..e60392df10 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -432,6 +432,7 @@ mod unit_tests { data: &[u8], offset: u64, sparse: bool, + direct_io: bool, ) -> (TempFile, QcowDiskSync) { let temp_file = TempFile::new().unwrap(); { @@ -443,7 +444,7 @@ mod unit_tests { } let disk = QcowDiskSync::new( temp_file.as_file().try_clone().unwrap(), - false, + direct_io, false, sparse, ) @@ -485,7 +486,7 @@ mod unit_tests { fn test_qcow_async_punch_hole_completion() { let data = vec![0xDD; 128 * 1024]; let offset = 0u64; - let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true, false); let mut async_io = disk.new_async_io(1).unwrap(); async_io.punch_hole(offset, data.len() as u64, 100).unwrap(); @@ -505,7 +506,7 @@ mod unit_tests { fn test_qcow_async_write_zeroes_completion() { let data = vec![0xEE; 256 * 1024]; let offset = 64 * 1024u64; - let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true, false); let mut async_io = disk.new_async_io(1).unwrap(); async_io @@ -526,7 +527,7 @@ mod unit_tests { #[test] fn test_qcow_async_multiple_operations() { let data = vec![0xFF; 64 * 1024]; - let (_temp, _) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); + let (_temp, _) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true, false); // Write data at multiple offsets via QcowFile first, then punch { @@ -567,7 +568,7 @@ mod unit_tests { // Verify that after punch_hole, a second async_io sees zeros. let data = vec![0xAB; 128 * 1024]; let offset = 0u64; - let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true, false); let mut async_io1 = disk.new_async_io(1).unwrap(); async_io1 @@ -591,7 +592,7 @@ mod unit_tests { // Simulates the real usage pattern of write data, punch hole, then read back. let data = vec![0xCD; 64 * 1024]; // one cluster let offset = 1024 * 1024u64; // 1MB offset - let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true, false); // Punch hole to simulate DISCARD let mut async_io1 = disk.new_async_io(1).unwrap(); @@ -609,9 +610,8 @@ mod unit_tests { ); } - #[test] - fn test_qcow_async_read_write_roundtrip() { - let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); + fn test_qcow_async_read_write_roundtrip_impl(direct_io: bool) { + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true, direct_io); let data = vec![0x42u8; 64 * 1024]; let offset = 0u64; @@ -630,9 +630,18 @@ mod unit_tests { } #[test] - fn test_qcow_async_read_unallocated() { + fn test_qcow_async_read_write_roundtrip() { + test_qcow_async_read_write_roundtrip_impl(false); + } + + #[test] + fn test_qcow_async_read_write_roundtrip_direct_io() { + test_qcow_async_read_write_roundtrip_impl(true); + } + + fn test_qcow_async_read_unallocated_impl(direct_io: bool) { // Reading from an unallocated region should return zeros. - let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true, direct_io); let read_buf = async_read(&disk, 0, 64 * 1024); assert!( read_buf.iter().all(|&b| b == 0), @@ -641,8 +650,17 @@ mod unit_tests { } #[test] - fn test_qcow_async_cross_cluster_read_write() { - let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); + fn test_qcow_async_read_unallocated() { + test_qcow_async_read_unallocated_impl(false); + } + + #[test] + fn test_qcow_async_read_unallocated_direct_io() { + test_qcow_async_read_unallocated_impl(true); + } + + fn test_qcow_async_cross_cluster_read_write_impl(direct_io: bool) { + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true, direct_io); // Default cluster size is 64KB. Write 96KB starting at 32KB to cross the boundary. let data: Vec = (0..96 * 1024).map(|i| (i % 251) as u8).collect(); @@ -662,7 +680,16 @@ mod unit_tests { } #[test] - fn test_backing_file_read() { + fn test_qcow_async_cross_cluster_read_write() { + test_qcow_async_cross_cluster_read_write_impl(false); + } + + #[test] + fn test_qcow_async_cross_cluster_read_write_direct_io() { + test_qcow_async_cross_cluster_read_write_impl(true); + } + + fn test_backing_file_read_impl(direct_io: bool) { let backing_temp = TempFile::new().unwrap(); let cluster_size = 1u64 << 16; let file_size = cluster_size * 4; @@ -683,7 +710,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); // Read first cluster - should come from backing file let buf = async_read(&disk, 0, cluster_size as usize); @@ -719,7 +746,16 @@ mod unit_tests { } #[test] - fn test_backing_file_read_qcow2_backing() { + fn test_backing_file_read() { + test_backing_file_read_impl(false); + } + + #[test] + fn test_backing_file_read_direct_io() { + test_backing_file_read_impl(true); + } + + fn test_backing_file_read_qcow2_backing_impl(direct_io: bool) { let backing_temp = TempFile::new().unwrap(); let cluster_size = 1u64 << 16; let file_size = cluster_size * 4; @@ -745,7 +781,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); // Read first cluster - should come from QCOW2 backing let buf = async_read(&disk, 0, cluster_size as usize); @@ -786,14 +822,23 @@ mod unit_tests { } #[test] - fn test_multi_queue_concurrent_reads() { + fn test_backing_file_read_qcow2_backing() { + test_backing_file_read_qcow2_backing_impl(false); + } + + #[test] + fn test_backing_file_read_qcow2_backing_direct_io() { + test_backing_file_read_qcow2_backing_impl(true); + } + + fn test_multi_queue_concurrent_reads_impl(direct_io: bool) { // Verify that multiple queues (threads) can read simultaneously. // This exercises the RwLock + pread64 design: concurrent L2 cache hits // proceed in parallel and data reads are position independent. let cluster_size = 1u64 << 16; let file_size = cluster_size * 16; let pattern: Vec = (0..file_size as usize).map(|i| (i % 251) as u8).collect(); - let (_temp, disk) = create_disk_with_data(file_size, &pattern, 0, true); + let (_temp, disk) = create_disk_with_data(file_size, &pattern, 0, true, direct_io); let disk = Arc::new(disk); let threads: Vec<_> = (0..8) @@ -822,7 +867,16 @@ mod unit_tests { } #[test] - fn test_multi_queue_concurrent_reads_qcow2_backing() { + fn test_multi_queue_concurrent_reads() { + test_multi_queue_concurrent_reads_impl(false); + } + + #[test] + fn test_multi_queue_concurrent_reads_direct_io() { + test_multi_queue_concurrent_reads_impl(true); + } + + fn test_multi_queue_concurrent_reads_qcow2_backing_impl(direct_io: bool) { // Same as above but reads go through a Qcow2Backing, // exercising concurrent metadata resolution + pread64 in the backing. let backing_temp = TempFile::new().unwrap(); @@ -850,7 +904,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = Arc::new(QcowDiskSync::new(file, false, true, true).unwrap()); + let disk = Arc::new(QcowDiskSync::new(file, direct_io, true, true).unwrap()); let threads: Vec<_> = (0..8) .map(|t| { @@ -877,7 +931,16 @@ mod unit_tests { } #[test] - fn test_three_layer_backing_chain() { + fn test_multi_queue_concurrent_reads_qcow2_backing() { + test_multi_queue_concurrent_reads_qcow2_backing_impl(false); + } + + #[test] + fn test_multi_queue_concurrent_reads_qcow2_backing_direct_io() { + test_multi_queue_concurrent_reads_qcow2_backing_impl(true); + } + + fn test_three_layer_backing_chain_impl(direct_io: bool) { // raw base -> qcow2 mid -> qcow2 overlay // Tests recursive shared_backing_from() with nested backing. let cluster_size = 1u64 << 16; @@ -924,7 +987,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); // Cluster 0: mid wrote 0xBB let buf = async_read(&disk, 0, cluster_size as usize); @@ -960,7 +1023,16 @@ mod unit_tests { } #[test] - fn test_backing_cow_preserves_all_unwritten_clusters() { + fn test_three_layer_backing_chain() { + test_three_layer_backing_chain_impl(false); + } + + #[test] + fn test_three_layer_backing_chain_direct_io() { + test_three_layer_backing_chain_impl(true); + } + + fn test_backing_cow_preserves_all_unwritten_clusters_impl(direct_io: bool) { // Write to specific clusters in the overlay, verify all others still // read from the qcow2 backing correctly. let cluster_size = 1u64 << 16; @@ -990,7 +1062,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); let written = vec![0xFFu8; cluster_size as usize]; for &idx in &[0u64, 3, 7] { @@ -1025,7 +1097,16 @@ mod unit_tests { } #[test] - fn test_qcow2_backing_read_beyond_virtual_size() { + fn test_backing_cow_preserves_all_unwritten_clusters() { + test_backing_cow_preserves_all_unwritten_clusters_impl(false); + } + + #[test] + fn test_backing_cow_preserves_all_unwritten_clusters_direct_io() { + test_backing_cow_preserves_all_unwritten_clusters_impl(true); + } + + fn test_qcow2_backing_read_beyond_virtual_size_impl(direct_io: bool) { // Read starting past the backing file virtual_size should return zeros. let cluster_size = 1u64 << 16; let backing_size = cluster_size * 2; @@ -1053,7 +1134,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); // Read cluster 2 (past backing virtual_size) - should be zeros let buf = async_read(&disk, backing_size, cluster_size as usize); @@ -1064,7 +1145,16 @@ mod unit_tests { } #[test] - fn test_qcow2_backing_read_spanning_virtual_size() { + fn test_qcow2_backing_read_beyond_virtual_size() { + test_qcow2_backing_read_beyond_virtual_size_impl(false); + } + + #[test] + fn test_qcow2_backing_read_beyond_virtual_size_direct_io() { + test_qcow2_backing_read_beyond_virtual_size_impl(true); + } + + fn test_qcow2_backing_read_spanning_virtual_size_impl(direct_io: bool) { // Read that starts within backing bounds but extends past virtual_size. // First part should have backing data, remainder should be zeros. let cluster_size = 1u64 << 16; @@ -1094,7 +1184,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); // Read 2 clusters starting at cluster 1 (spans backing boundary) let read_len = cluster_size as usize * 2; @@ -1114,7 +1204,16 @@ mod unit_tests { } #[test] - fn test_raw_backing_read_beyond_virtual_size() { + fn test_qcow2_backing_read_spanning_virtual_size() { + test_qcow2_backing_read_spanning_virtual_size_impl(false); + } + + #[test] + fn test_qcow2_backing_read_spanning_virtual_size_direct_io() { + test_qcow2_backing_read_spanning_virtual_size_impl(true); + } + + fn test_raw_backing_read_beyond_virtual_size_impl(direct_io: bool) { // Read past raw backing file virtual_size should return zeros. let cluster_size = 1u64 << 16; let backing_size = cluster_size * 2; @@ -1138,7 +1237,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); // Read cluster 2 (past backing size) - should be zeros let buf = async_read(&disk, backing_size, cluster_size as usize); @@ -1161,7 +1260,16 @@ mod unit_tests { } #[test] - fn test_qcow2_backing_cross_cluster_read() { + fn test_raw_backing_read_beyond_virtual_size() { + test_raw_backing_read_beyond_virtual_size_impl(false); + } + + #[test] + fn test_raw_backing_read_beyond_virtual_size_direct_io() { + test_raw_backing_read_beyond_virtual_size_impl(true); + } + + fn test_qcow2_backing_cross_cluster_read_impl(direct_io: bool) { // Read spanning a cluster boundary through qcow2 backing. // Exercises the read_clusters loop in Qcow2Backing. let cluster_size = 1u64 << 16; @@ -1190,7 +1298,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); // Read spanning clusters 1-2 boundary: 512 bytes before + 512 after let mid = cluster_size - 512; @@ -1214,7 +1322,16 @@ mod unit_tests { } #[test] - fn test_punch_hole_with_backing_fallthrough() { + fn test_qcow2_backing_cross_cluster_read() { + test_qcow2_backing_cross_cluster_read_impl(false); + } + + #[test] + fn test_qcow2_backing_cross_cluster_read_direct_io() { + test_qcow2_backing_cross_cluster_read_impl(true); + } + + fn test_punch_hole_with_backing_fallthrough_impl(direct_io: bool) { // Write to overlay, then punch hole. After punch, the cluster should // fall through to backing data (not zeros). let cluster_size = 1u64 << 16; @@ -1238,7 +1355,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); let written = vec![0xFFu8; cluster_size as usize]; async_write(&disk, 0, &written); @@ -1277,10 +1394,19 @@ mod unit_tests { } #[test] - fn test_rewrite_allocated_cluster() { + fn test_punch_hole_with_backing_fallthrough() { + test_punch_hole_with_backing_fallthrough_impl(false); + } + + #[test] + fn test_punch_hole_with_backing_fallthrough_direct_io() { + test_punch_hole_with_backing_fallthrough_impl(true); + } + + fn test_rewrite_allocated_cluster_impl(direct_io: bool) { // Write to a cluster, then overwrite it. The second write should hit // the already allocated path in map_write (no new cluster allocation). - let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true); + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true, direct_io); let cluster_size = 1u64 << 16; let data1 = vec![0xAAu8; cluster_size as usize]; @@ -1306,7 +1432,16 @@ mod unit_tests { } #[test] - fn test_partial_cluster_write_with_backing_cow() { + fn test_rewrite_allocated_cluster() { + test_rewrite_allocated_cluster_impl(false); + } + + #[test] + fn test_rewrite_allocated_cluster_direct_io() { + test_rewrite_allocated_cluster_impl(true); + } + + fn test_partial_cluster_write_with_backing_cow_impl(direct_io: bool) { // Partial cluster write to an overlay with a backing file triggers COW. // The unwritten part of the cluster must be copied from backing. let cluster_size = 1u64 << 16; @@ -1330,7 +1465,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); // Write 4KB at offset 4KB within cluster 0 (partial cluster) let write_offset = 4096u64; @@ -1366,6 +1501,16 @@ mod unit_tests { ); } + #[test] + fn test_partial_cluster_write_with_backing_cow() { + test_partial_cluster_write_with_backing_cow_impl(false); + } + + #[test] + fn test_partial_cluster_write_with_backing_cow_direct_io() { + test_partial_cluster_write_with_backing_cow_impl(true); + } + #[test] fn test_partial_cluster_deallocate() { // Punch hole on a partial cluster range. The deallocate_bytes path @@ -1376,7 +1521,7 @@ mod unit_tests { let data: Vec = (0..2 * cluster_size as usize) .map(|i| (i % 251) as u8) .collect(); - let (_temp, disk) = create_disk_with_data(file_size, &data, 0, true); + let (_temp, disk) = create_disk_with_data(file_size, &data, 0, true, false); // Punch a partial range: last 4KB of cluster 0 + first 4KB of cluster 1 let punch_offset = cluster_size - 4096; @@ -1420,7 +1565,7 @@ mod unit_tests { let cluster_size = 1u64 << 16; let initial_size = cluster_size * 4; let data = vec![0xAA; cluster_size as usize]; - let (_temp, mut disk) = create_disk_with_data(initial_size, &data, 0, true); + let (_temp, mut disk) = create_disk_with_data(initial_size, &data, 0, true, false); assert_eq!(disk.logical_size().unwrap(), initial_size); From 7fd5e74f0ef75e728ba5f8ec24f7a61930a9f5ef Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 22:19:36 +0200 Subject: [PATCH 1204/1893] 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 --- block/src/qcow_sync.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index e60392df10..8b5f5c3f60 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -1636,4 +1636,87 @@ mod unit_tests { "size should be unchanged after failed resize" ); } + + fn test_multi_iovec_read_write_impl(direct_io: bool) { + // Exercise scatter/gather with multiple iovecs per operation. + let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &[], 0, true, direct_io); + + // Write: 3 iovecs with distinct patterns + let a = vec![0xAAu8; 16 * 1024]; + let b = vec![0xBBu8; 32 * 1024]; + let c = vec![0xCCu8; 16 * 1024]; + let iovecs_w = [ + libc::iovec { + iov_base: a.as_ptr() as *mut libc::c_void, + iov_len: a.len(), + }, + libc::iovec { + iov_base: b.as_ptr() as *mut libc::c_void, + iov_len: b.len(), + }, + libc::iovec { + iov_base: c.as_ptr() as *mut libc::c_void, + iov_len: c.len(), + }, + ]; + let total = a.len() + b.len() + c.len(); + + let mut aio = disk.new_async_io(1).unwrap(); + aio.write_vectored(0, &iovecs_w, 1).unwrap(); + let (ud, res) = aio.next_completed_request().unwrap(); + assert_eq!(ud, 1); + assert_eq!(res as usize, total); + aio.fsync(Some(2)).unwrap(); + drop(aio); + + // Read back into 3 iovecs of different sizes + let mut r1 = vec![0u8; 8 * 1024]; + let mut r2 = vec![0u8; 48 * 1024]; + let mut r3 = vec![0u8; 8 * 1024]; + let iovecs_r = [ + libc::iovec { + iov_base: r1.as_mut_ptr() as *mut libc::c_void, + iov_len: r1.len(), + }, + libc::iovec { + iov_base: r2.as_mut_ptr() as *mut libc::c_void, + iov_len: r2.len(), + }, + libc::iovec { + iov_base: r3.as_mut_ptr() as *mut libc::c_void, + iov_len: r3.len(), + }, + ]; + + let mut aio = disk.new_async_io(1).unwrap(); + aio.read_vectored(0, &iovecs_r, 10).unwrap(); + let (ud, res) = aio.next_completed_request().unwrap(); + assert_eq!(ud, 10); + assert_eq!(res as usize, total); + drop(aio); + + // Reassemble the read buffers into a flat vec + let mut got = Vec::with_capacity(total); + got.extend_from_slice(&r1); + got.extend_from_slice(&r2); + got.extend_from_slice(&r3); + + // Build expected from the write buffers + let mut expected = Vec::with_capacity(total); + expected.extend_from_slice(&a); + expected.extend_from_slice(&b); + expected.extend_from_slice(&c); + + assert_eq!(got, expected, "Multi iovec read should match written data"); + } + + #[test] + fn test_multi_iovec_read_write() { + test_multi_iovec_read_write_impl(false); + } + + #[test] + fn test_multi_iovec_read_write_direct_io() { + test_multi_iovec_read_write_impl(true); + } } From fe711b3a0b63bb09496c35c7419a4dfbc2753517 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 22:29:05 +0200 Subject: [PATCH 1205/1893] 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 --- block/src/qcow_sync.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 8b5f5c3f60..c3e57da2b9 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -419,6 +419,7 @@ impl AsyncIo for QcowSync { #[cfg(test)] mod unit_tests { use std::io::{Seek, SeekFrom, Write}; + use std::os::fd::RawFd; use std::thread; use vmm_sys_util::tempfile::TempFile; @@ -1719,4 +1720,36 @@ mod unit_tests { fn test_multi_iovec_read_write_direct_io() { test_multi_iovec_read_write_impl(true); } + + // -- Low level aligned I/O function tests -- + // + // Test aligned_pread and aligned_pwrite directly with controlled + // alignment values on a plain temp file. + + /// Create a temp file filled with a repeating pattern of the given size. + /// Returns the TempFile (must be kept alive) and the raw fd. + fn create_pattern_file(size: usize) -> (TempFile, RawFd) { + let tf = TempFile::new().unwrap(); + let pattern: Vec = (0..size).map(|i| (i % 251) as u8).collect(); + tf.as_file().write_all(&pattern).unwrap(); + tf.as_file().sync_all().unwrap(); + let fd = tf.as_file().as_raw_fd(); + (tf, fd) + } + + #[test] + fn test_aligned_pread_pass_through() { + // When buffer address, length, and offset are all aligned, + // aligned_pread should take the fast path (no bounce buffer). + let size = 4096usize; + let (_tf, fd) = create_pattern_file(size); + let alignment = 512; + + // Use AlignedBuf to guarantee buffer address alignment. + let mut abuf = AlignedBuf::new(size, alignment).unwrap(); + aligned_pread(fd, abuf.as_mut_slice(size), 0, alignment).unwrap(); + + let expected: Vec = (0..size).map(|i| (i % 251) as u8).collect(); + assert_eq!(abuf.as_slice(size), &expected[..]); + } } From 021838b63cb963b4bb8e323f8d2ab74a1c54557b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 22:30:25 +0200 Subject: [PATCH 1206/1893] 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 --- block/src/qcow_sync.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index c3e57da2b9..cf3b27c254 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -1752,4 +1752,21 @@ mod unit_tests { let expected: Vec = (0..size).map(|i| (i % 251) as u8).collect(); assert_eq!(abuf.as_slice(size), &expected[..]); } + + #[test] + fn test_aligned_pread_bounce_unaligned_buffer() { + // Force a misaligned buffer so aligned_pread must take the + // bounce path. A plain vec![0u8; 4096] is often page-aligned + // by the allocator, which would skip the bounce entirely. + let size = 4096usize; + let (_tf, fd) = create_pattern_file(size); + let alignment = 512; + + let mut backing = vec![0u8; size + 1]; + let buf = &mut backing[1..size + 1]; + aligned_pread(fd, buf, 0, alignment).unwrap(); + + let expected: Vec = (0..size).map(|i| (i % 251) as u8).collect(); + assert_eq!(buf, &expected[..]); + } } From 05d52d035371047f0e82a77b422ef20a0a9659a1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 22:31:35 +0200 Subject: [PATCH 1207/1893] 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 --- block/src/qcow_sync.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index cf3b27c254..5b4a6b2168 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -1769,4 +1769,24 @@ mod unit_tests { let expected: Vec = (0..size).map(|i| (i % 251) as u8).collect(); assert_eq!(buf, &expected[..]); } + + #[test] + fn test_aligned_pread_unaligned_offset() { + // Read at an offset that is not a multiple of alignment. + // aligned_pread should round down the offset, read an aligned + // region, then copy the correct slice into the caller buffer. + let file_size = 8192usize; + let (_tf, fd) = create_pattern_file(file_size); + let alignment = 512; + + let offset = 100u64; + let len = 200usize; + let mut buf = vec![0u8; len]; + aligned_pread(fd, &mut buf, offset, alignment).unwrap(); + + let expected: Vec = (offset as usize..offset as usize + len) + .map(|i| (i % 251) as u8) + .collect(); + assert_eq!(buf, expected); + } } From 32af2f2a22bbc49d4c8bd07e0bc09809cd7180cd Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 22:35:44 +0200 Subject: [PATCH 1208/1893] 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 --- block/src/qcow_sync.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 5b4a6b2168..af6bfc1ba7 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -1789,4 +1789,22 @@ mod unit_tests { .collect(); assert_eq!(buf, expected); } + + #[test] + fn test_aligned_pwrite_pass_through() { + // When buffer address, length, and offset are all aligned, + // aligned_pwrite should take the fast path. + let size = 4096usize; + let (_tf, fd) = create_pattern_file(size); + let alignment = 512; + + let data: Vec = (0..size).map(|i| ((i + 1) % 251) as u8).collect(); + let mut abuf = AlignedBuf::new(size, alignment).unwrap(); + abuf.as_mut_slice(size).copy_from_slice(&data); + aligned_pwrite(fd, abuf.as_slice(size), 0, alignment).unwrap(); + + let mut readback = vec![0u8; size]; + pread_exact(fd, &mut readback, 0).unwrap(); + assert_eq!(readback, data); + } } From 5ed9f2e3d8a480f11e06594182e924a13702be53 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 22:37:40 +0200 Subject: [PATCH 1209/1893] block: qcow: Test aligned_pwrite bounce unaligned buffer Write 4096 bytes via plain Vec 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 --- block/src/qcow_sync.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index af6bfc1ba7..8989354c3f 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -1807,4 +1807,22 @@ mod unit_tests { pread_exact(fd, &mut readback, 0).unwrap(); assert_eq!(readback, data); } + + #[test] + fn test_aligned_pwrite_bounce_unaligned_buffer() { + // Force a misaligned buffer so aligned_pwrite must take the + // bounce path. A plain vec![0u8; 4096] is often page-aligned + // by the allocator, which would skip the bounce entirely. + let size = 4096usize; + let (_tf, fd) = create_pattern_file(size); + let alignment = 512; + + let backing: Vec = (0..size + 1).map(|i| ((i + 1) % 251) as u8).collect(); + let data = &backing[1..size + 1]; + aligned_pwrite(fd, data, 0, alignment).unwrap(); + + let mut readback = vec![0u8; size]; + pread_exact(fd, &mut readback, 0).unwrap(); + assert_eq!(readback, data); + } } From 7aa477936e16dd6867b6a9c5c8e8db7b8fc1e35a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 22:39:19 +0200 Subject: [PATCH 1210/1893] 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 --- block/src/qcow_sync.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 8989354c3f..14b9c7c362 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -1825,4 +1825,35 @@ mod unit_tests { pread_exact(fd, &mut readback, 0).unwrap(); assert_eq!(readback, data); } + + #[test] + fn test_aligned_pwrite_unaligned_offset() { + // Write at an offset that is not a multiple of alignment. + // aligned_pwrite should do read-modify-write and preserve + // surrounding data. + let file_size = 8192usize; + let (_tf, fd) = create_pattern_file(file_size); + let alignment = 512; + + let offset = 100u64; + let len = 200usize; + let data: Vec = (0..len).map(|i| ((i + 1) % 239) as u8).collect(); + aligned_pwrite(fd, &data, offset, alignment).unwrap(); + + // Read entire file and verify the written region plus untouched areas. + let mut whole = vec![0u8; file_size]; + pread_exact(fd, &mut whole, 0).unwrap(); + + // Before the write region: original pattern. + let before: Vec = (0..offset as usize).map(|i| (i % 251) as u8).collect(); + assert_eq!(&whole[..offset as usize], &before[..]); + + // The written region. + assert_eq!(&whole[offset as usize..offset as usize + len], &data[..]); + + // After the write region: original pattern. + let after_start = offset as usize + len; + let after: Vec = (after_start..file_size).map(|i| (i % 251) as u8).collect(); + assert_eq!(&whole[after_start..], &after[..]); + } } From ab811126189cdaf113f98cda31aa33118a1fd650 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 22:40:26 +0200 Subject: [PATCH 1211/1893] 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 --- block/src/qcow_sync.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 14b9c7c362..a98557abe5 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -1856,4 +1856,32 @@ mod unit_tests { let after: Vec = (after_start..file_size).map(|i| (i % 251) as u8).collect(); assert_eq!(&whole[after_start..], &after[..]); } + + #[test] + fn test_aligned_pread_pwrite_4096_alignment() { + // Exercise aligned I/O with 4096 byte alignment. + let file_size = 16384usize; + let (_tf, fd) = create_pattern_file(file_size); + let alignment = 4096; + + // Write 4096 bytes at offset 4096 via unaligned Vec. + let offset = 4096u64; + let len = 4096usize; + let data: Vec = (0..len).map(|i| ((i + 1) % 239) as u8).collect(); + aligned_pwrite(fd, &data, offset, alignment).unwrap(); + + // Read back the written region via unaligned Vec. + let mut buf = vec![0u8; len]; + aligned_pread(fd, &mut buf, offset, alignment).unwrap(); + assert_eq!(buf, data); + + // Verify untouched regions. + let mut whole = vec![0u8; file_size]; + pread_exact(fd, &mut whole, 0).unwrap(); + let before: Vec = (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 = (after_start..file_size).map(|i| (i % 251) as u8).collect(); + assert_eq!(&whole[after_start..], &after[..]); + } } From e5ad85d7def285d093f85916b7096ec5cb412297 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 23:34:26 +0200 Subject: [PATCH 1212/1893] 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 --- test_infra/src/lib.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index e32ee791fc..091fef7c8f 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -226,6 +226,9 @@ pub trait DiskConfig { fn prepare_files(&mut self, tmp_dir: &TempDir, network: &GuestNetworkConfig); fn prepare_cloudinit(&self, tmp_dir: &TempDir, network: &GuestNetworkConfig) -> String; fn disk(&self, disk_type: DiskType) -> Option; + fn qcow2_disk(&self) -> Option { + None + } } #[derive(Clone)] @@ -248,6 +251,7 @@ impl UbuntuDiskConfig { pub struct WindowsDiskConfig { image_name: String, osdisk_path: String, + osdisk_qcow2_path: String, loopback_device: String, windows_snapshot_cow: String, windows_snapshot: String, @@ -258,6 +262,7 @@ impl WindowsDiskConfig { WindowsDiskConfig { image_name, osdisk_path: String::new(), + osdisk_qcow2_path: String::new(), loopback_device: String::new(), windows_snapshot_cow: String::new(), windows_snapshot: String::new(), @@ -286,6 +291,10 @@ impl Drop for WindowsDiskConfig { .args(["-d", self.loopback_device.as_str()]) .output() .expect("Expect removing loopback device to succeed"); + + if !self.osdisk_qcow2_path.is_empty() { + let _ = fs::remove_file(&self.osdisk_qcow2_path); + } } } @@ -451,7 +460,7 @@ impl DiskConfig for WindowsDiskConfig { let mut osdisk_path = workload_path; osdisk_path.push(&self.image_name); - let osdisk_blk_size = fs::metadata(osdisk_path) + let osdisk_blk_size = fs::metadata(&osdisk_path) .expect("Expect retrieving Windows image metadata") .len() >> 9; @@ -530,6 +539,27 @@ impl DiskConfig for WindowsDiskConfig { self.osdisk_path = format!("/dev/mapper/{windows_snapshot}"); self.windows_snapshot_cow = windows_snapshot_cow; self.windows_snapshot = windows_snapshot; + + // Create a qcow2 overlay backed by the raw image. + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + let qcow2_name = format!("windows-qcow2-{}.qcow2", random_extension.to_str().unwrap()); + let qcow2_path = workload_path.join(&qcow2_name); + let output = Command::new("qemu-img") + .args([ + "create", + "-f", + "qcow2", + "-b", + osdisk_path.to_str().unwrap(), + "-F", + "raw", + qcow2_path.to_str().unwrap(), + ]) + .output() + .expect("Expect creating qcow2 overlay to succeed"); + assert!(output.status.success(), "qemu-img create failed"); + self.osdisk_qcow2_path = qcow2_path.to_str().unwrap().to_string(); } fn disk(&self, disk_type: DiskType) -> Option { @@ -538,6 +568,10 @@ impl DiskConfig for WindowsDiskConfig { DiskType::CloudInit => None, } } + + fn qcow2_disk(&self) -> Option { + Some(self.osdisk_qcow2_path.clone()) + } } pub fn rate_limited_copy, Q: AsRef>(from: P, to: Q) -> io::Result { From 11cf332114028a2f1d33357527e8a741b23eaeab Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 13 Apr 2026 23:44:45 +0200 Subject: [PATCH 1213/1893] 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 --- cloud-hypervisor/tests/integration.rs | 67 +++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index c4bccc46ad..cc24cb8ebb 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -8337,6 +8337,73 @@ mod windows { handle_child_output(r, &output); } + + #[test] + fn test_windows_guest_qcow2_backing_direct() { + let windows_guest = WindowsGuest::new(); + + let qcow2_path = windows_guest.guest().disk_config.qcow2_disk().unwrap(); + + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--cpus", "boot=2,kvm_hyperv=on"]) + .args(["--memory", "size=4G"]) + .args(["--kernel", edk2_path().to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) + .args([ + "--disk", + format!("path={qcow2_path},image_type=qcow2,backing_files=on,direct=on").as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let fd = child.stdout.as_ref().unwrap().as_raw_fd(); + let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; + let fd = child.stderr.as_ref().unwrap().as_raw_fd(); + let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; + + assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE); + + let mut child_dnsmasq = windows_guest.run_dnsmasq(); + + let r = std::panic::catch_unwind(|| { + windows_guest.wait_for_boot().unwrap(); + + // Write and read back files through qcow2 + direct I/O. + for i in 0..5 { + let fname = format!("c:\\test-dio-{i}.bin"); + let fname2 = format!("c:\\test-dio-{i}-copy.bin"); + let size = (i + 1) * 4 * 1024 * 1024; + windows_guest.ssh_cmd(&format!( + "powershell -Command \"\ + $r = New-Object byte[] {size}; \ + (New-Object Random {i}).NextBytes($r); \ + [IO.File]::WriteAllBytes('{fname}', $r)\"" + )); + let hash_write = windows_guest.ssh_cmd(&format!( + "powershell -Command \"(Get-FileHash '{fname}' -Algorithm SHA256).Hash\"" + )); + windows_guest.ssh_cmd(&format!("copy {fname} {fname2}")); + let hash_read = windows_guest.ssh_cmd(&format!( + "powershell -Command \"(Get-FileHash '{fname2}' -Algorithm SHA256).Hash\"" + )); + assert_eq!(hash_write.trim(), hash_read.trim()); + } + + windows_guest.shutdown(); + }); + + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); + + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); + + handle_child_output(r, &output); + } } #[cfg(target_arch = "x86_64")] From a84a0b8b25b2eb5c9dc7c343318a544bdb2fc1e4 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 14 Apr 2026 18:18:25 +0200 Subject: [PATCH 1214/1893] 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 --- block/src/qcow_sync.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index a98557abe5..e696638eb4 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -1884,4 +1884,23 @@ mod unit_tests { let after: Vec = (after_start..file_size).map(|i| (i % 251) as u8).collect(); assert_eq!(&whole[after_start..], &after[..]); } + + #[test] + fn test_aligned_buf_allocation_and_access() { + for alignment in [512, 4096] { + let size = 1024usize; + let mut abuf = AlignedBuf::new(size, alignment).unwrap(); + let aligned_size = size.next_multiple_of(alignment); + + assert!( + (abuf.ptr() as usize).is_multiple_of(alignment), + "ptr not aligned to {alignment}" + ); + assert!(abuf.as_slice(aligned_size).iter().all(|&b| b == 0)); + + let pattern: Vec = (0..size).map(|i| (i % 251) as u8).collect(); + abuf.as_mut_slice(size).copy_from_slice(&pattern); + assert_eq!(abuf.as_slice(size), &pattern[..]); + } + } } From dc0e003be05133cabfa00b157319c758f1381877 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 14 Apr 2026 18:19:02 +0200 Subject: [PATCH 1215/1893] 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 --- block/src/qcow_sync.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index e696638eb4..44b8efaf5a 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -1903,4 +1903,13 @@ mod unit_tests { assert_eq!(abuf.as_slice(size), &pattern[..]); } } + + #[test] + fn test_aligned_buf_size_rounds_up() { + let abuf = AlignedBuf::new(1, 512).unwrap(); + assert_eq!(abuf.layout().size(), 512); + + let abuf = AlignedBuf::new(513, 512).unwrap(); + assert_eq!(abuf.layout().size(), 1024); + } } From c698075157c231a75c5fee0ba9e1c61290157364 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Mon, 9 Mar 2026 16:02:40 +0100 Subject: [PATCH 1216/1893] 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 --- vmm/src/device_manager.rs | 7 +++++++ vmm/src/lib.rs | 35 +++++++++++++++++++++++++++++++++++ vmm/src/vm.rs | 6 ++++++ 3 files changed, 48 insertions(+) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 39281e53ed..193b411d45 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1080,6 +1080,7 @@ pub struct DeviceManager { // Exit event exit_evt: EventFd, reset_evt: EventFd, + guest_exit_evt: EventFd, #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] id_to_dev_info: HashMap<(DeviceType, String), MmioDeviceInfo>, @@ -1206,6 +1207,7 @@ impl DeviceManager { cpu_manager: Arc>, exit_evt: EventFd, reset_evt: EventFd, + guest_exit_evt: EventFd, seccomp_action: SeccompAction, numa_nodes: NumaNodes, activate_evt: &EventFd, @@ -1402,6 +1404,7 @@ impl DeviceManager { device_tree, exit_evt, reset_evt, + guest_exit_evt, #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] id_to_dev_info: HashMap::new(), seccomp_action, @@ -1519,6 +1522,9 @@ impl DeviceManager { self.exit_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, + self.guest_exit_evt + .try_clone() + .map_err(DeviceManagerError::EventFd)?, )?; } @@ -1889,6 +1895,7 @@ impl DeviceManager { interrupt_manager: &dyn InterruptManager, reset_evt: EventFd, exit_evt: EventFd, + _guest_exit_evt: EventFd, ) -> DeviceManagerResult>>> { let vcpus_kill_signalled = self .cpu_manager diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 66a2a104d0..eec56c5c1f 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -250,6 +250,7 @@ pub enum EpollDispatch { Api = 2, ActivateVirtioDevices = 3, Debug = 4, + GuestExit = 5, Unknown, } @@ -262,6 +263,7 @@ impl From for EpollDispatch { 2 => Api, 3 => ActivateVirtioDevices, 4 => Debug, + 5 => GuestExit, _ => Unknown, } } @@ -608,6 +610,7 @@ pub struct Vmm { epoll: EpollContext, exit_evt: EventFd, reset_evt: EventFd, + guest_exit_evt: EventFd, api_evt: EventFd, #[cfg(feature = "guest_debug")] debug_evt: EventFd, @@ -781,6 +784,7 @@ impl Vmm { ) -> Result { let mut epoll = EpollContext::new().map_err(Error::Epoll)?; let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; + let guest_exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; let activate_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; epoll @@ -791,6 +795,10 @@ impl Vmm { .add_event(&reset_evt, EpollDispatch::Reset) .map_err(Error::Epoll)?; + epoll + .add_event(&guest_exit_evt, EpollDispatch::GuestExit) + .map_err(Error::Epoll)?; + epoll .add_event(&activate_evt, EpollDispatch::ActivateVirtioDevices) .map_err(Error::Epoll)?; @@ -808,6 +816,7 @@ impl Vmm { epoll, exit_evt, reset_evt, + guest_exit_evt, api_evt, #[cfg(feature = "guest_debug")] debug_evt, @@ -1109,6 +1118,9 @@ impl Vmm { let reset_evt = self.reset_evt.try_clone().map_err(|e| { MigratableError::MigrateReceive(anyhow!("Error cloning reset EventFd: {e}")) })?; + let guest_exit_evt = self.guest_exit_evt.try_clone().map_err(|e| { + MigratableError::MigrateReceive(anyhow!("Error cloning guest exit EventFd: {e}")) + })?; #[cfg(feature = "guest_debug")] let debug_evt = self.vm_debug_evt.try_clone().map_err(|e| { MigratableError::MigrateReceive(anyhow!("Error cloning debug EventFd: {e}")) @@ -1128,6 +1140,7 @@ impl Vmm { hypervisor_vm, exit_evt, reset_evt, + guest_exit_evt, #[cfg(feature = "guest_debug")] debug_evt, &self.seccomp_action, @@ -1569,6 +1582,10 @@ impl Vmm { let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?; let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?; + let guest_exit_evt = self + .guest_exit_evt + .try_clone() + .map_err(VmError::EventFdClone)?; #[cfg(feature = "guest_debug")] let debug_evt = self .vm_debug_evt @@ -1583,6 +1600,7 @@ impl Vmm { vm_config, exit_evt, reset_evt, + guest_exit_evt, #[cfg(feature = "guest_debug")] debug_evt, &self.seccomp_action, @@ -1667,6 +1685,13 @@ impl Vmm { self.reset_evt.read().map_err(Error::EventFdRead)?; self.vm_reboot().map_err(Error::VmReboot)?; } + EpollDispatch::GuestExit => { + info!("VM guest exit event"); + self.guest_exit_evt.read().map_err(Error::EventFdRead)?; + self.vmm_shutdown().map_err(Error::VmmShutdown)?; + + break 'outer; + } EpollDispatch::ActivateVirtioDevices => { if let Some(ref vm) = self.vm { let count = self.activate_evt.read().map_err(Error::EventFdRead)?; @@ -1775,6 +1800,10 @@ impl RequestHandler for Vmm { if self.vm.is_none() { let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?; let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?; + let guest_exit_evt = self + .guest_exit_evt + .try_clone() + .map_err(VmError::EventFdClone)?; #[cfg(feature = "guest_debug")] let vm_debug_evt = self .vm_debug_evt @@ -1790,6 +1819,7 @@ impl RequestHandler for Vmm { Arc::clone(vm_config), exit_evt, reset_evt, + guest_exit_evt, #[cfg(feature = "guest_debug")] vm_debug_evt, &self.seccomp_action, @@ -1955,6 +1985,10 @@ impl RequestHandler for Vmm { let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?; let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?; + let guest_exit_evt = self + .guest_exit_evt + .try_clone() + .map_err(VmError::EventFdClone)?; #[cfg(feature = "guest_debug")] let debug_evt = self .vm_debug_evt @@ -1980,6 +2014,7 @@ impl RequestHandler for Vmm { config, exit_evt, reset_evt, + guest_exit_evt, #[cfg(feature = "guest_debug")] debug_evt, &self.seccomp_action, diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 04bd2d595a..6b3f6c42e1 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -536,6 +536,7 @@ impl Vm { vm: Arc, exit_evt: EventFd, reset_evt: EventFd, + guest_exit_evt: EventFd, #[cfg(feature = "guest_debug")] vm_debug_evt: EventFd, seccomp_action: &SeccompAction, hypervisor: Arc, @@ -604,6 +605,7 @@ impl Vm { cpu_manager.clone(), exit_evt.try_clone().map_err(Error::EventFdClone)?, reset_evt, + guest_exit_evt, seccomp_action.clone(), numa_nodes.clone(), &activate_evt, @@ -790,6 +792,7 @@ impl Vm { cpu_manager: Arc>, exit_evt: EventFd, reset_evt: EventFd, + guest_exit_evt: EventFd, seccomp_action: SeccompAction, numa_nodes: NumaNodes, activate_evt: &EventFd, @@ -812,6 +815,7 @@ impl Vm { cpu_manager, exit_evt, reset_evt, + guest_exit_evt, seccomp_action, numa_nodes, activate_evt, @@ -1247,6 +1251,7 @@ impl Vm { vm_config: Arc>, exit_evt: EventFd, reset_evt: EventFd, + guest_exit_evt: EventFd, #[cfg(feature = "guest_debug")] vm_debug_evt: EventFd, seccomp_action: &SeccompAction, hypervisor: Arc, @@ -1319,6 +1324,7 @@ impl Vm { vm, exit_evt, reset_evt, + guest_exit_evt, #[cfg(feature = "guest_debug")] vm_debug_evt, seccomp_action, From a159152e4139b048ed412a8aedecb19895f2a893 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Mon, 9 Mar 2026 16:27:41 +0100 Subject: [PATCH 1217/1893] 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 --- devices/src/acpi.rs | 8 ++++---- vmm/src/device_manager.rs | 8 ++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs index 69bcb80d76..a9c86aa18e 100644 --- a/devices/src/acpi.rs +++ b/devices/src/acpi.rs @@ -21,7 +21,7 @@ pub const GED_DEVICE_ACPI_SIZE: usize = 0x1; /// A device for handling ACPI shutdown and reboot pub struct AcpiShutdownDevice { - exit_evt: EventFd, + guest_exit_evt: EventFd, reset_evt: EventFd, vcpus_kill_signalled: Arc, } @@ -29,12 +29,12 @@ pub struct AcpiShutdownDevice { impl AcpiShutdownDevice { /// Constructs a device that will signal the given event when the guest requests it. pub fn new( - exit_evt: EventFd, + guest_exit_evt: EventFd, reset_evt: EventFd, vcpus_kill_signalled: Arc, ) -> AcpiShutdownDevice { AcpiShutdownDevice { - exit_evt, + guest_exit_evt, reset_evt, vcpus_kill_signalled, } @@ -68,7 +68,7 @@ impl BusDevice for AcpiShutdownDevice { const SLEEP_VALUE_BIT: u8 = 2; if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) { info!("ACPI Shutdown signalled"); - if let Err(e) = self.exit_evt.write(1) { + if let Err(e) = self.guest_exit_evt.write(1) { error!("Error triggering ACPI shutdown event: {e}"); } // Spin until we are sure the reset_evt has been handled and that when diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 193b411d45..2ea8efe350 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1519,9 +1519,6 @@ impl DeviceManager { self.reset_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, - self.exit_evt - .try_clone() - .map_err(DeviceManagerError::EventFd)?, self.guest_exit_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, @@ -1894,8 +1891,7 @@ impl DeviceManager { &mut self, interrupt_manager: &dyn InterruptManager, reset_evt: EventFd, - exit_evt: EventFd, - _guest_exit_evt: EventFd, + guest_exit_evt: EventFd, ) -> DeviceManagerResult>>> { let vcpus_kill_signalled = self .cpu_manager @@ -1904,7 +1900,7 @@ impl DeviceManager { .vcpus_kill_signalled() .clone(); let shutdown_device = Arc::new(Mutex::new(devices::AcpiShutdownDevice::new( - exit_evt, + guest_exit_evt, reset_evt, vcpus_kill_signalled, ))); From 005ce38ffd9b53aefca411184e4fa38d9548c9c5 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Mon, 9 Mar 2026 16:35:34 +0100 Subject: [PATCH 1218/1893] 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 --- cloud-hypervisor/src/main.rs | 8 ++++++++ vmm/src/lib.rs | 19 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 06ef1fe30f..51b1f38fdd 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -373,6 +373,12 @@ fn get_cli_options_sorted( .num_args(1..) .action(ArgAction::Append) .group("vm-config"), + Arg::new("no-shutdown") + .long("no-shutdown") + .help("Do not exit the VMM when the guest shuts down") + .num_args(0) + .action(ArgAction::SetTrue) + .group("vmm-config"), Arg::new("numa") .long("numa") .help(NumaConfig::SYNTAX) @@ -637,6 +643,7 @@ fn start_vmm( let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?; let landlock_enable = cmd_arguments.get_flag("landlock"); + let no_shutdown = cmd_arguments.get_flag("no-shutdown"); #[allow(unused_mut)] let mut event_monitor = cmd_arguments @@ -733,6 +740,7 @@ fn start_vmm( exit_evt.try_clone().unwrap(), &seccomp_action, hypervisor, + no_shutdown, landlock_enable, ) .map_err(Error::StartVmmThread)?; diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index eec56c5c1f..5dfea07e78 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -162,6 +162,10 @@ pub enum Error { #[error("Error rebooting VM")] VmReboot(#[source] VmError), + /// Cannot shut the VM down + #[error("Error shutting down VM")] + VmShutdown(#[source] VmError), + /// Cannot create VMM thread #[error("Error spawning VMM thread")] VmmThreadSpawn(#[source] io::Error), @@ -449,6 +453,7 @@ pub fn start_vmm_thread( exit_event: EventFd, seccomp_action: &SeccompAction, hypervisor: Arc, + no_shutdown: bool, landlock_enable: bool, ) -> Result { #[cfg(feature = "guest_debug")] @@ -488,6 +493,7 @@ pub fn start_vmm_thread( vmm_seccomp_action, hypervisor, exit_event, + no_shutdown, )?; vmm.setup_signal_handler(landlock_enable)?; @@ -627,6 +633,7 @@ pub struct Vmm { original_termios_opt: Arc>>, console_resize_pipe: Option>, console_info: Option, + no_shutdown: bool, } /// Just a wrapper for the data that goes into @@ -781,6 +788,7 @@ impl Vmm { seccomp_action: SeccompAction, hypervisor: Arc, exit_evt: EventFd, + no_shutdown: bool, ) -> Result { let mut epoll = EpollContext::new().map_err(Error::Epoll)?; let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; @@ -833,6 +841,7 @@ impl Vmm { original_termios_opt: Arc::new(Mutex::new(None)), console_resize_pipe: None, console_info: None, + no_shutdown, }) } @@ -1688,9 +1697,12 @@ impl Vmm { EpollDispatch::GuestExit => { info!("VM guest exit event"); self.guest_exit_evt.read().map_err(Error::EventFdRead)?; - self.vmm_shutdown().map_err(Error::VmmShutdown)?; - - break 'outer; + if self.no_shutdown { + self.vm_shutdown().map_err(Error::VmShutdown)?; + } else { + self.vmm_shutdown().map_err(Error::VmmShutdown)?; + break 'outer; + } } EpollDispatch::ActivateVirtioDevices => { if let Some(ref vm) = self.vm { @@ -2630,6 +2642,7 @@ mod unit_tests { SeccompAction::Allow, hypervisor::new().unwrap(), EventFd::new(EFD_NONBLOCK).unwrap(), + false, ) .unwrap() } From c7a152ee7950005ca6e20e775226e23f640382f6 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Thu, 16 Apr 2026 10:02:00 +0000 Subject: [PATCH 1219/1893] 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 --- performance-metrics/src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 622d8793c7..c18ef70dc1 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -1297,8 +1297,9 @@ fn run_test_with_timeout( } fn cleanup_stale_processes() { - for proc in &["cloud-hypervisor", "iperf3", "ethr"] { - let _ = Command::new("pkill").args(["-9", "-f", proc]).status(); + // "cloud-hyperviso" - process name truncated to 15 chars by the kernel + for proc in &["cloud-hyperviso", "iperf3", "ethr"] { + let _ = Command::new("pkill").args(["-9", proc]).status(); } thread::sleep(Duration::from_secs(2)); } From 07b77b0f4b6aa8734aaf9213453cd00d13f0bf9c Mon Sep 17 00:00:00 2001 From: Keith Adler Date: Wed, 15 Apr 2026 23:02:07 -0500 Subject: [PATCH 1220/1893] 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 --- .github/workflows/quality.yaml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 776cd8eb42..47156beaba 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -53,7 +53,6 @@ jobs: 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 @@ -62,7 +61,6 @@ jobs: 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 @@ -71,7 +69,6 @@ jobs: 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 @@ -80,7 +77,6 @@ jobs: 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 @@ -89,7 +85,6 @@ jobs: 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 @@ -98,7 +93,6 @@ jobs: 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 @@ -107,7 +101,6 @@ jobs: 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 @@ -122,7 +115,6 @@ jobs: 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 @@ -132,7 +124,6 @@ jobs: 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 @@ -142,7 +133,6 @@ jobs: 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 @@ -152,7 +142,6 @@ jobs: 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 From c1b4fcc7505b4deebbfa68caf13bfc89f3ff36c0 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Thu, 16 Apr 2026 09:43:16 -0700 Subject: [PATCH 1221/1893] 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 --- virtio-devices/src/vhost_user/blk.rs | 11 +++++++++-- virtio-devices/src/vhost_user/fs.rs | 5 ++++- virtio-devices/src/vhost_user/generic_vhost_user.rs | 5 ++++- virtio-devices/src/vhost_user/mod.rs | 8 +++++--- virtio-devices/src/vhost_user/net.rs | 5 ++++- virtio-devices/src/vhost_user/vu_common_ctrl.rs | 4 +++- 6 files changed, 29 insertions(+), 9 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 8221b7b501..83653147bd 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -254,7 +254,11 @@ impl VirtioDevice for Blk { .set_config(offset as u32, VhostUserConfigFlags::WRITABLE, data) .map_err(Error::VhostUserSetConfig) { - error!("Failed setting vhost-user-blk configuration: {e:?}"); + error!( + "Failed setting vhost-user-blk configuration for socket {} at offset 0x{offset:x} with length {}: {e:?}", + self.vu_common.socket_path, + data.len() + ); } } @@ -313,7 +317,10 @@ impl VirtioDevice for Blk { if let Some(vu) = &self.vu_common.vu && let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {e:?}"); + error!( + "Failed to reset vhost-user daemon for socket {}: {e:?}", + self.vu_common.socket_path + ); return None; } diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 967fdecf99..509a7a34f3 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -294,7 +294,10 @@ impl VirtioDevice for Fs { if let Some(vu) = &self.vu_common.vu && let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {e:?}"); + error!( + "Failed to reset vhost-user daemon for socket {}: {e:?}", + self.vu_common.socket_path + ); return None; } diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 1778834431..aed24b082d 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -317,7 +317,10 @@ impl VirtioDevice for GenericVhostUser { if let Some(vu) = &self.vu_common.vu && let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {e:?}"); + error!( + "Failed to reset vhost-user daemon for socket {}: {e:?}", + self.vu_common.socket_path + ); return None; } diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 01a75f0575..abca12c058 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -231,7 +231,8 @@ impl VhostUserEpollHandler { ) .map_err(|e| { EpollHelperError::IoError(std::io::Error::other(format!( - "failed connecting vhost-user backend {e:?}" + "failed connecting vhost-user backend for socket {}: {e:?}", + self.socket_path ))) })?; @@ -282,7 +283,8 @@ impl EpollHelperHandler for VhostUserEpollHandle HUP_CONNECTION_EVENT => { self.reconnect(helper).map_err(|e| { EpollHelperError::HandleEvent(anyhow!( - "failed to reconnect vhost-user backend: {e:?}" + "failed to reconnect vhost-user backend for socket {}: {e:?}", + self.socket_path )) })?; } @@ -370,7 +372,7 @@ impl VhostUserCommon { }; if self.vu.is_none() { - error!("Missing vhost-user handle"); + error!("Missing vhost-user handle for socket {}", self.socket_path); return Err(ActivateError::BadActivate); } let vu = self.vu.as_ref().unwrap(); diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 1c85b5f38e..4803ade33c 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -374,7 +374,10 @@ impl VirtioDevice for Net { if let Some(vu) = &self.vu_common.vu && let Err(e) = vu.lock().unwrap().reset_vhost_user() { - error!("Failed to reset vhost-user daemon: {e:?}"); + error!( + "Failed to reset vhost-user daemon for socket {}: {e:?}", + self.vu_common.socket_path + ); return None; } diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 23e06a3c0a..5ad9425c5a 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -428,7 +428,9 @@ impl VhostUserHandle { } }; - error!("Failed connecting the backend after trying for 1 minute: {err:?}"); + error!( + "Failed connecting the backend after trying for 1 minute for socket {socket_path}: {err:?}" + ); Err(Error::VhostUserConnect) } } From 4a607ed82ddaede31d414f88dc4762a515974271 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 23:12:08 +0100 Subject: [PATCH 1222/1893] 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 --- performance-metrics/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/performance-metrics/Cargo.toml b/performance-metrics/Cargo.toml index 516ea9e0a2..776a46fc2c 100644 --- a/performance-metrics/Cargo.toml +++ b/performance-metrics/Cargo.toml @@ -5,7 +5,7 @@ name = "performance-metrics" version = "0.1.0" [dependencies] -block = { path = "../block" } +block = { path = "../block", features = ["io_uring"] } clap = { workspace = true, features = ["wrap_help"] } dirs = { workspace = true } libc = { workspace = true } From ab1dddb62a69a6e948b5ea2c808a89155c85f9bd Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 20:09:01 +0100 Subject: [PATCH 1223/1893] 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 --- performance-metrics/src/util.rs | 45 ++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index dcc2257501..98fd56828d 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -4,14 +4,18 @@ //! Shared benchmark helpers. -use std::io::ErrorKind; +use std::io::{ErrorKind, Seek, SeekFrom, Write}; use std::thread; use std::time::Duration; +use block::qcow::{QcowFile, RawFile}; +use block::qcow_async::QcowDiskAsync; +use block::qcow_sync::QcowDiskSync; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::tempfile::TempFile; pub const BLOCK_SIZE: u64 = 4096; +pub const QCOW_CLUSTER_SIZE: u64 = 65536; /// Create a temporary file pre sized to hold `num_blocks` blocks. pub fn sized_tempfile(num_blocks: usize) -> TempFile { @@ -22,6 +26,45 @@ pub fn sized_tempfile(num_blocks: usize) -> TempFile { tmp } +/// Create a QCOW2 image with `num_clusters` allocated clusters and return +/// the tempfile handle. +/// +/// Each cluster is default QCOW2 cluster size of 64 KiB. The image is +/// created via `QcowFile::new` then populated with writes so that the +/// clusters are actually allocated in the L2 / refcount tables. +fn create_qcow_tempfile(num_clusters: usize) -> TempFile { + let tmp = TempFile::new().expect("failed to create tempfile"); + let virtual_size = QCOW_CLUSTER_SIZE * num_clusters as u64; + let raw = RawFile::new(tmp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw, 3, virtual_size, true).expect("failed to create QCOW2 file"); + let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; + for i in 0..num_clusters { + qcow.seek(SeekFrom::Start(i as u64 * QCOW_CLUSTER_SIZE)) + .expect("seek failed"); + qcow.write_all(&buf).expect("write failed"); + } + qcow.flush().expect("flush failed"); + tmp +} + +/// Create a QCOW2 image with `num_clusters` allocated clusters opened +/// via `QcowDiskSync` (blocking I/O backend). +pub fn qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) { + let tmp = create_qcow_tempfile(num_clusters); + let disk = QcowDiskSync::new(tmp.as_file().try_clone().unwrap(), false, false, true) + .expect("failed to open QCOW2 via QcowDiskSync"); + (tmp, disk) +} + +/// Create a QCOW2 image with `num_clusters` allocated clusters opened +/// via `QcowDiskAsync` (io_uring backend). +pub fn qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDiskAsync) { + let tmp = create_qcow_tempfile(num_clusters); + let disk = QcowDiskAsync::new(tmp.as_file().try_clone().unwrap(), false, false, true) + .expect("failed to open QCOW2 via QcowDiskAsync"); + (tmp, disk) +} + /// Spin and wait until the given eventfd becomes readable. pub fn wait_for_eventfd(notifier: &EventFd) { loop { From 97b109bc89eee86d67afe53aaa7cb9ff703bf341 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 22:32:07 +0100 Subject: [PATCH 1224/1893] performance-metrics: Add sync drain completions helper Signed-off-by: Anatol Belski --- performance-metrics/src/util.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 98fd56828d..0b42667df6 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -8,6 +8,7 @@ use std::io::{ErrorKind, Seek, SeekFrom, Write}; use std::thread; use std::time::Duration; +use block::async_io::AsyncIo; use block::qcow::{QcowFile, RawFile}; use block::qcow_async::QcowDiskAsync; use block::qcow_sync::QcowDiskSync; @@ -65,6 +66,13 @@ pub fn qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDiskAsync) { (tmp, disk) } +/// Drain `count` completions from a synchronous async_io backend. +pub fn drain_completions(async_io: &mut dyn AsyncIo, count: usize) { + for _ in 0..count { + async_io.next_completed_request(); + } +} + /// Spin and wait until the given eventfd becomes readable. pub fn wait_for_eventfd(notifier: &EventFd) { loop { From f8deeb8a1cee3e26c97178484d452b7872eae495 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 28 Mar 2026 11:39:56 +0100 Subject: [PATCH 1225/1893] performance-metrics: Add submit_reads helper Signed-off-by: Anatol Belski --- performance-metrics/src/util.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 0b42667df6..9ec9102b03 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -73,6 +73,15 @@ pub fn drain_completions(async_io: &mut dyn AsyncIo, count: usize) { } } +/// Submit `count` sequential read_vectored calls at `stride`-byte intervals. +pub fn submit_reads(async_io: &mut dyn AsyncIo, count: usize, stride: u64, iovec: &[libc::iovec]) { + for i in 0..count { + async_io + .read_vectored((i as u64 * stride) as libc::off_t, iovec, i as u64) + .expect("read_vectored failed"); + } +} + /// Spin and wait until the given eventfd becomes readable. pub fn wait_for_eventfd(notifier: &EventFd) { loop { From beaa98728cf915d1e21eb4f34157ca3c922e074e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 28 Mar 2026 12:43:11 +0100 Subject: [PATCH 1226/1893] performance-metrics: Add iovec construction helpers Signed-off-by: Anatol Belski --- performance-metrics/src/util.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 9ec9102b03..ce480aa4f4 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -73,6 +73,22 @@ pub fn drain_completions(async_io: &mut dyn AsyncIo, count: usize) { } } +/// Build an iovec suitable for a read into `buf`. +pub fn read_iovec(buf: &mut [u8]) -> libc::iovec { + libc::iovec { + iov_base: buf.as_mut_ptr() as *mut libc::c_void, + iov_len: buf.len(), + } +} + +/// Build an iovec suitable for a write from `buf`. +pub fn write_iovec(buf: &[u8]) -> libc::iovec { + libc::iovec { + iov_base: buf.as_ptr() as *mut libc::c_void, + iov_len: buf.len(), + } +} + /// Submit `count` sequential read_vectored calls at `stride`-byte intervals. pub fn submit_reads(async_io: &mut dyn AsyncIo, count: usize, stride: u64, iovec: &[libc::iovec]) { for i in 0..count { From 6cd3395a55d3193ceb7bd9d76113202acf3bc680 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 20:15:10 +0100 Subject: [PATCH 1227/1893] 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 --- performance-metrics/src/main.rs | 26 ++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 30 +++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index c18ef70dc1..1668bc288c 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 62] = [ +const TEST_LIST: [PerformanceTest; 64] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1253,6 +1253,30 @@ const TEST_LIST: [PerformanceTest; 62] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 6dc51af658..1a8dea3e15 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -11,10 +11,13 @@ use std::os::unix::io::AsRawFd; use std::time::Instant; use block::async_io::AsyncIo; +use block::disk_file::AsyncDiskFile; use block::raw_async_aio::RawFileAsyncAio; use crate::PerformanceTestControl; -use crate::util::{self, BLOCK_SIZE}; +use crate::util::{ + self, BLOCK_SIZE, QCOW_CLUSTER_SIZE, drain_completions, read_iovec, submit_reads, +}; /// Submit num_ops AIO writes, wait for them all to land, then time /// how long it takes to drain every completion via next_completed_request(). @@ -51,3 +54,28 @@ pub fn micro_bench_aio_drain(control: &PerformanceTestControl) -> f64 { } start.elapsed().as_secs_f64() } + +/// Read num_ops clusters from a prepopulated qcow2 image through the +/// QcowSync async_io path and time the total read_vectored wall clock. +/// +/// This exercises the hot read path: L2 lookup via map_clusters_for_read, +/// pread64 for allocated data, and iovec scatter. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_read(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::qcow_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + let elapsed = start.elapsed().as_secs_f64(); + + // Drain completions so Drop is clean. + drain_completions(async_io.as_mut(), num_ops); + + elapsed +} From 0854c3e082726edd156b65079c4c6c62f58bc352 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 20:17:20 +0100 Subject: [PATCH 1228/1893] 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 --- performance-metrics/src/util.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index ce480aa4f4..345e62a41e 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -98,6 +98,24 @@ pub fn submit_reads(async_io: &mut dyn AsyncIo, count: usize, stride: u64, iovec } } +/// Create an empty QCOW2 image sized for `num_clusters` clusters. +/// No data clusters are allocated. +fn create_empty_qcow_tempfile(num_clusters: usize) -> TempFile { + let tmp = TempFile::new().expect("failed to create tempfile"); + let virtual_size = QCOW_CLUSTER_SIZE * num_clusters as u64; + let raw = RawFile::new(tmp.as_file().try_clone().unwrap(), false); + QcowFile::new(raw, 3, virtual_size, true).expect("failed to create qcow2 file"); + tmp +} + +/// Empty QCOW2 opened via QcowDiskSync. +pub fn empty_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) { + let tmp = create_empty_qcow_tempfile(num_clusters); + let disk = QcowDiskSync::new(tmp.as_file().try_clone().unwrap(), false, false, true) + .expect("failed to open qcow2 via QcowDiskSync"); + (tmp, disk) +} + /// Spin and wait until the given eventfd becomes readable. pub fn wait_for_eventfd(notifier: &EventFd) { loop { From 9f317895b573c80d121173b264ad23ae686e3e8d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 28 Mar 2026 11:41:15 +0100 Subject: [PATCH 1229/1893] performance-metrics: Add submit_writes helper Signed-off-by: Anatol Belski --- performance-metrics/src/util.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 345e62a41e..bbafea0d34 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -98,6 +98,15 @@ pub fn submit_reads(async_io: &mut dyn AsyncIo, count: usize, stride: u64, iovec } } +/// Submit `count` sequential write_vectored calls at `stride`-byte intervals. +pub fn submit_writes(async_io: &mut dyn AsyncIo, count: usize, stride: u64, iovec: &[libc::iovec]) { + for i in 0..count { + async_io + .write_vectored((i as u64 * stride) as libc::off_t, iovec, i as u64) + .expect("write_vectored failed"); + } +} + /// Create an empty QCOW2 image sized for `num_clusters` clusters. /// No data clusters are allocated. fn create_empty_qcow_tempfile(num_clusters: usize) -> TempFile { From f7e40eec9af2aa89a4a12a642ee56e4ba351e549 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 20:19:46 +0100 Subject: [PATCH 1230/1893] 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 --- performance-metrics/src/main.rs | 26 ++++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 27 ++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 1668bc288c..225212b8f9 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 64] = [ +const TEST_LIST: [PerformanceTest; 66] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1277,6 +1277,30 @@ const TEST_LIST: [PerformanceTest; 64] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_write_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_write, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_write_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_write, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 1a8dea3e15..d02e997626 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -17,6 +17,7 @@ use block::raw_async_aio::RawFileAsyncAio; use crate::PerformanceTestControl; use crate::util::{ self, BLOCK_SIZE, QCOW_CLUSTER_SIZE, drain_completions, read_iovec, submit_reads, + submit_writes, write_iovec, }; /// Submit num_ops AIO writes, wait for them all to land, then time @@ -79,3 +80,29 @@ pub fn micro_bench_qcow_read(control: &PerformanceTestControl) -> f64 { elapsed } + +/// Write num_ops clusters into an empty qcow2 image through the +/// QcowSync async_io path and time the total write_vectored wall clock. +/// +/// This exercises the write allocation path: map_cluster_for_write +/// allocates a new cluster and bumps refcounts, then pwrite_all writes +/// the data. +/// +/// Returns the total write wall clock time in seconds. +pub fn micro_bench_qcow_write(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::empty_qcow_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = write_iovec(&buf); + + let start = Instant::now(); + submit_writes(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + let elapsed = start.elapsed().as_secs_f64(); + + // Drain completions so Drop is clean. + drain_completions(async_io.as_mut(), num_ops); + + elapsed +} From c3312dca3ff0cf275f50095e1ee6775c9ee525db Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 15 Mar 2026 14:13:46 +0100 Subject: [PATCH 1231/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 26 ++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 225212b8f9..93ab45e316 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 66] = [ +const TEST_LIST: [PerformanceTest; 68] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1301,6 +1301,30 @@ const TEST_LIST: [PerformanceTest; 66] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_punch_hole_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_punch_hole, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_punch_hole_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_punch_hole, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index d02e997626..2ebde205fa 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -106,3 +106,29 @@ pub fn micro_bench_qcow_write(control: &PerformanceTestControl) -> f64 { elapsed } + +/// Punch holes for num_ops clusters in a prepopulated qcow2 image through +/// the QcowSync async_io path and time the total punch_hole wall clock. +/// +/// This exercises the discard path: deallocate_bytes decrements refcounts, +/// frees clusters and issues fallocate punch_hole on the host file. +/// +/// Returns the total punch_hole wall clock time in seconds. +pub fn micro_bench_qcow_punch_hole(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::qcow_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + let start = Instant::now(); + for i in 0..num_ops { + async_io + .punch_hole(i as u64 * QCOW_CLUSTER_SIZE, QCOW_CLUSTER_SIZE, i as u64) + .expect("punch_hole failed"); + } + let elapsed = start.elapsed().as_secs_f64(); + + // Drain completions so Drop is clean. + drain_completions(async_io.as_mut(), num_ops); + + elapsed +} From e802c0d8b975fb38145509cd0daae452938d6e0f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 15 Mar 2026 14:17:55 +0100 Subject: [PATCH 1232/1893] 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 --- performance-metrics/src/main.rs | 26 ++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 30 ++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 93ab45e316..14ecfb12be 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 68] = [ +const TEST_LIST: [PerformanceTest; 70] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1325,6 +1325,30 @@ const TEST_LIST: [PerformanceTest; 68] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_fsync_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_fsync, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_fsync_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_fsync, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 2ebde205fa..30a737780e 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -132,3 +132,33 @@ pub fn micro_bench_qcow_punch_hole(control: &PerformanceTestControl) -> f64 { elapsed } + +/// Write num_ops clusters into an empty qcow2 image to dirty L2 and +/// refcount metadata, then time a single fsync 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. +/// +/// Returns the fsync wall clock time in seconds. +pub fn micro_bench_qcow_fsync(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::empty_qcow_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + // Write num_ops clusters to dirty L2 and refcount metadata. + let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = write_iovec(&buf); + submit_writes(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + // Drain write completions. + drain_completions(async_io.as_mut(), num_ops); + + // Time the flush. + let start = Instant::now(); + async_io.fsync(Some(num_ops as u64)).expect("fsync failed"); + let elapsed = start.elapsed().as_secs_f64(); + + drain_completions(async_io.as_mut(), 1); + + elapsed +} From 638cb3d7f2bcf36a81f9d5d2f34de75901aa1823 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 15 Apr 2026 19:09:48 +0200 Subject: [PATCH 1233/1893] 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 --- performance-metrics/src/util.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index bbafea0d34..09d6ad7e64 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -89,6 +89,24 @@ pub fn write_iovec(buf: &[u8]) -> libc::iovec { } } +/// Build a deterministic pseudo-random permutation of `[0, n)`. +/// +/// Uses a Fisher-Yates shuffle seeded by `DefaultHasher` so the +/// permutation is identical across runs. +pub fn deterministic_permutation(n: usize) -> Vec { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut indices: Vec = (0..n).collect(); + for i in (1..n).rev() { + let mut h = DefaultHasher::new(); + i.hash(&mut h); + let j = h.finish() as usize % (i + 1); + indices.swap(i, j); + } + indices +} + /// Submit `count` sequential read_vectored calls at `stride`-byte intervals. pub fn submit_reads(async_io: &mut dyn AsyncIo, count: usize, stride: u64, iovec: &[libc::iovec]) { for i in 0..count { From 99b43202797cd2e2c3973d8c29cd6bcc3b72c701 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 19:20:18 +0100 Subject: [PATCH 1234/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++- performance-metrics/src/micro_bench_block.rs | 38 ++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 14ecfb12be..3020d5827c 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 70] = [ +const TEST_LIST: [PerformanceTest; 72] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1277,6 +1277,30 @@ const TEST_LIST: [PerformanceTest; 70] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_random_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_random_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_random_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_random_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, PerformanceTest { name: "micro_block_qcow_write_128_us", func_ptr: micro_bench_block::micro_bench_qcow_write, diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 30a737780e..40869afbc0 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -16,8 +16,8 @@ use block::raw_async_aio::RawFileAsyncAio; use crate::PerformanceTestControl; use crate::util::{ - self, BLOCK_SIZE, QCOW_CLUSTER_SIZE, drain_completions, read_iovec, submit_reads, - submit_writes, write_iovec, + self, BLOCK_SIZE, QCOW_CLUSTER_SIZE, deterministic_permutation, drain_completions, read_iovec, + submit_reads, submit_writes, write_iovec, }; /// Submit num_ops AIO writes, wait for them all to land, then time @@ -81,6 +81,40 @@ pub fn micro_bench_qcow_read(control: &PerformanceTestControl) -> f64 { elapsed } +/// Read num_ops clusters from a prepopulated qcow2 image in random order. +/// +/// Unlike micro_bench_qcow_read which reads sequentially, this shuffles +/// the cluster indices to exercise L2 cache miss and eviction behaviour +/// under random access patterns. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_random_read(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::qcow_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + let indices = deterministic_permutation(num_ops); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let start = Instant::now(); + for (seq, &cluster_idx) in indices.iter().enumerate() { + async_io + .read_vectored( + (cluster_idx as u64 * QCOW_CLUSTER_SIZE) as libc::off_t, + &[iovec], + seq as u64, + ) + .expect("read_vectored failed"); + } + let elapsed = start.elapsed().as_secs_f64(); + + drain_completions(async_io.as_mut(), num_ops); + + elapsed +} + /// Write num_ops clusters into an empty qcow2 image through the /// QcowSync async_io path and time the total write_vectored wall clock. /// From bdce007aacd447b8cd5bca6bbadda63078d59191 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 21:08:14 +0100 Subject: [PATCH 1235/1893] 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 --- performance-metrics/src/util.rs | 41 ++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 09d6ad7e64..3ba9a9eeef 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -5,11 +5,12 @@ //! Shared benchmark helpers. use std::io::{ErrorKind, Seek, SeekFrom, Write}; +use std::os::unix::fs::FileExt; use std::thread; use std::time::Duration; use block::async_io::AsyncIo; -use block::qcow::{QcowFile, RawFile}; +use block::qcow::{BackingFileConfig, ImageType, QcowFile, RawFile}; use block::qcow_async::QcowDiskAsync; use block::qcow_sync::QcowDiskSync; use vmm_sys_util::eventfd::EventFd; @@ -143,6 +144,44 @@ pub fn empty_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) { (tmp, disk) } +/// Create a QCOW2 overlay backed by a raw file with `num_clusters` +/// pre-populated clusters. Returns (backing_tempfile, overlay_tempfile). +fn create_overlay_tempfiles(num_clusters: usize) -> (TempFile, TempFile) { + let virtual_size = QCOW_CLUSTER_SIZE * num_clusters as u64; + + let backing = TempFile::new().expect("failed to create backing tempfile"); + { + let f = backing.as_file(); + f.set_len(virtual_size).expect("set_len failed"); + let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; + for i in 0..num_clusters { + f.write_at(&buf, i as u64 * QCOW_CLUSTER_SIZE) + .expect("write_at failed"); + } + } + + let overlay = TempFile::new().expect("failed to create overlay tempfile"); + { + let raw = RawFile::new(overlay.as_file().try_clone().unwrap(), false); + let backing_config = BackingFileConfig { + path: backing.as_path().to_str().unwrap().to_string(), + format: Some(ImageType::Raw), + }; + QcowFile::new_from_backing(raw, 3, virtual_size, &backing_config, true) + .expect("failed to create overlay qcow2"); + } + + (backing, overlay) +} + +/// QCOW2 overlay with raw backing opened via QcowDiskSync. +pub fn qcow_overlay_tempfile(num_clusters: usize) -> (TempFile, TempFile, QcowDiskSync) { + let (backing, overlay) = create_overlay_tempfiles(num_clusters); + let disk = QcowDiskSync::new(overlay.as_file().try_clone().unwrap(), false, true, true) + .expect("failed to open overlay qcow2 via QcowDiskSync"); + (backing, overlay, disk) +} + /// Spin and wait until the given eventfd becomes readable. pub fn wait_for_eventfd(notifier: &EventFd) { loop { From 79b58c0fafe77ae03913fd6ab3661821577a91cc Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 21:10:21 +0100 Subject: [PATCH 1236/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 25 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 3020d5827c..e40323788c 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 72] = [ +const TEST_LIST: [PerformanceTest; 74] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1373,6 +1373,30 @@ const TEST_LIST: [PerformanceTest; 72] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_backing_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_backing_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_backing_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_backing_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 40869afbc0..efb73559d8 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -196,3 +196,28 @@ pub fn micro_bench_qcow_fsync(control: &PerformanceTestControl) -> f64 { elapsed } + +/// Read num_ops clusters from a QCOW2 overlay whose data lives entirely +/// in a raw backing file. +/// +/// This exercises the backing file read path: L2 lookup finds no +/// allocated cluster and falls through to the backing file for every +/// read. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_backing_read(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_backing, _overlay, disk) = util::qcow_overlay_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + let elapsed = start.elapsed().as_secs_f64(); + + drain_completions(async_io.as_mut(), num_ops); + + elapsed +} From b2430d701bc7df964de751f13295fd65372fd448 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 21:13:09 +0100 Subject: [PATCH 1237/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 26 ++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index e40323788c..879cce9262 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 74] = [ +const TEST_LIST: [PerformanceTest; 76] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1397,6 +1397,30 @@ const TEST_LIST: [PerformanceTest; 74] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_cow_write_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_cow_write, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_cow_write_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_cow_write, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index efb73559d8..035ca1d435 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -221,3 +221,29 @@ pub fn micro_bench_qcow_backing_read(control: &PerformanceTestControl) -> f64 { elapsed } + +/// Write num_ops clusters into a QCOW2 overlay backed by a raw file. +/// +/// Each write triggers copy-on-write: the overlay must allocate a new +/// cluster, update L2 and refcount tables, then write the data. This +/// measures the COW allocation overhead compared to writing into an +/// empty image (no backing read needed since we overwrite the full +/// cluster). +/// +/// Returns the total write wall clock time in seconds. +pub fn micro_bench_qcow_cow_write(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_backing, _overlay, disk) = util::qcow_overlay_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + let buf = vec![0xBBu8; QCOW_CLUSTER_SIZE as usize]; + let iovec = write_iovec(&buf); + + let start = Instant::now(); + submit_writes(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + let elapsed = start.elapsed().as_secs_f64(); + + drain_completions(async_io.as_mut(), num_ops); + + elapsed +} From aca64ced8f60a11f1f432846b2fde19912431113 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 21:20:14 +0100 Subject: [PATCH 1238/1893] 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 --- performance-metrics/src/util.rs | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 3ba9a9eeef..b859c8764b 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -4,8 +4,10 @@ //! Shared benchmark helpers. +use std::fs::File; use std::io::{ErrorKind, Seek, SeekFrom, Write}; use std::os::unix::fs::FileExt; +use std::process::Command; use std::thread; use std::time::Duration; @@ -182,6 +184,59 @@ pub fn qcow_overlay_tempfile(num_clusters: usize) -> (TempFile, TempFile, QcowDi (backing, overlay, disk) } +/// Create a zlib compressed QCOW2 image with `num_clusters` clusters +/// via `qemu-img convert -c`. +fn create_compressed_qcow_tempfile(num_clusters: usize) -> TempFile { + let virtual_size = QCOW_CLUSTER_SIZE * num_clusters as u64; + + let raw_tmp = TempFile::new().expect("failed to create raw tempfile"); + { + let f = raw_tmp.as_file(); + f.set_len(virtual_size).expect("set_len failed"); + let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; + for i in 0..num_clusters { + f.write_at(&buf, i as u64 * QCOW_CLUSTER_SIZE) + .expect("write_at failed"); + } + } + + let qcow_tmp = TempFile::new().expect("failed to create qcow2 tempfile"); + let qcow_path = qcow_tmp.as_path().to_str().unwrap().to_string(); + let raw_path = raw_tmp.as_path().to_str().unwrap().to_string(); + let status = Command::new("qemu-img") + .args([ + "convert", + "-f", + "raw", + "-O", + "qcow2", + "-c", + "-o", + "compression_type=zlib", + &raw_path, + &qcow_path, + ]) + .status() + .expect("failed to run qemu-img"); + assert!(status.success(), "qemu-img convert failed"); + + qcow_tmp +} + +/// Compressed QCOW2 opened via QcowDiskSync. +pub fn compressed_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) { + let tmp = create_compressed_qcow_tempfile(num_clusters); + let path = tmp.as_path().to_str().unwrap().to_string(); + let disk = QcowDiskSync::new( + File::open(&path).expect("failed to open compressed qcow2"), + false, + false, + true, + ) + .expect("failed to open compressed qcow2 via QcowDiskSync"); + (tmp, disk) +} + /// Spin and wait until the given eventfd becomes readable. pub fn wait_for_eventfd(notifier: &EventFd) { loop { From 7dd1978fceed7d6c9e178c7474bc9781f790b2b7 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 21:25:35 +0100 Subject: [PATCH 1239/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 24 ++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 879cce9262..d78e451b8b 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 76] = [ +const TEST_LIST: [PerformanceTest; 78] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1421,6 +1421,30 @@ const TEST_LIST: [PerformanceTest; 76] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_compressed_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_compressed_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_compressed_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_compressed_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 035ca1d435..09cb093577 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -247,3 +247,27 @@ pub fn micro_bench_qcow_cow_write(control: &PerformanceTestControl) -> f64 { elapsed } + +/// Read num_ops clusters from a zlib compressed QCOW2 image. +/// +/// Every cluster is stored compressed, so each read triggers +/// decompression. This isolates the decompression overhead from +/// the normal allocated-cluster read path. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_compressed_read(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::compressed_qcow_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + let elapsed = start.elapsed().as_secs_f64(); + + drain_completions(async_io.as_mut(), num_ops); + + elapsed +} From 94f78edcf06321189d48e9961bb4bc12e2e3c389 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 22:53:32 +0100 Subject: [PATCH 1240/1893] 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 --- performance-metrics/src/main.rs | 26 ++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 30 ++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index d78e451b8b..ffb42edfdd 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 78] = [ +const TEST_LIST: [PerformanceTest; 80] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1445,6 +1445,30 @@ const TEST_LIST: [PerformanceTest; 78] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_multi_cluster_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_multi_cluster_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_multi_cluster_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_multi_cluster_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 09cb093577..1b9ff3b8ef 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -271,3 +271,33 @@ pub fn micro_bench_qcow_compressed_read(control: &PerformanceTestControl) -> f64 elapsed } + +/// Issue large multicluster reads from a prepopulated QCOW2 image. +/// +/// Each read_vectored call spans `CLUSTERS_PER_READ` contiguous clusters +/// (8 x 64 KiB = 512 KiB). This exercises the mapping coalesce path +/// where multiple L2 entries are merged into fewer host I/O operations. +/// `num_ops` is the total number of clusters; reads are issued in +/// chunks of CLUSTERS_PER_READ. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_multi_cluster_read(control: &PerformanceTestControl) -> f64 { + const CLUSTERS_PER_READ: usize = 8; + + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::qcow_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + let read_size = CLUSTERS_PER_READ * QCOW_CLUSTER_SIZE as usize; + let mut buf = vec![0u8; read_size]; + let iovec = read_iovec(&mut buf); + + let num_reads = num_ops / CLUSTERS_PER_READ; + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_reads, read_size as u64, &[iovec]); + let elapsed = start.elapsed().as_secs_f64(); + + drain_completions(async_io.as_mut(), num_reads); + + elapsed +} From 73d99c044c94232040857e28889b824029c97a54 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 22:58:48 +0100 Subject: [PATCH 1241/1893] 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 --- performance-metrics/src/util.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index b859c8764b..e4604f22d8 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -237,6 +237,35 @@ pub fn compressed_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) (tmp, disk) } +/// Number of data clusters covered by a single L2 table (64 KiB cluster, +/// 8-byte entries -> 8192 entries per L2 table). +pub const L2_ENTRIES_PER_TABLE: usize = QCOW_CLUSTER_SIZE as usize / 8; + +/// Create a sparse QCOW2 image with one allocated cluster per L2 table, +/// spanning `num_l2_tables` L2 tables. +fn create_sparse_qcow_tempfile(num_l2_tables: usize) -> TempFile { + let virtual_size = QCOW_CLUSTER_SIZE * (num_l2_tables as u64 * L2_ENTRIES_PER_TABLE as u64); + let tmp = TempFile::new().expect("failed to create tempfile"); + let raw = RawFile::new(tmp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw, 3, virtual_size, true).expect("failed to create qcow2 file"); + let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; + for i in 0..num_l2_tables { + let offset = i as u64 * L2_ENTRIES_PER_TABLE as u64 * QCOW_CLUSTER_SIZE; + qcow.seek(SeekFrom::Start(offset)).expect("seek failed"); + qcow.write_all(&buf).expect("write failed"); + } + qcow.flush().expect("flush failed"); + tmp +} + +/// Sparse QCOW2 opened via QcowDiskSync. +pub fn sparse_qcow_tempfile(num_l2_tables: usize) -> (TempFile, QcowDiskSync) { + let tmp = create_sparse_qcow_tempfile(num_l2_tables); + let disk = QcowDiskSync::new(tmp.as_file().try_clone().unwrap(), false, false, true) + .expect("failed to open qcow2 via QcowDiskSync"); + (tmp, disk) +} + /// Spin and wait until the given eventfd becomes readable. pub fn wait_for_eventfd(notifier: &EventFd) { loop { From faf6f7b6353aa5c07e09a2bf710b7ed91b4050fd Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 23:03:38 +0100 Subject: [PATCH 1242/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++- performance-metrics/src/micro_bench_block.rs | 31 ++++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index ffb42edfdd..2c33bf9a83 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 80] = [ +const TEST_LIST: [PerformanceTest; 82] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1469,6 +1469,30 @@ const TEST_LIST: [PerformanceTest; 80] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_l2_cache_miss_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_l2_cache_miss, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_l2_cache_miss_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_l2_cache_miss, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 1b9ff3b8ef..2b83ab495e 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -16,8 +16,8 @@ use block::raw_async_aio::RawFileAsyncAio; use crate::PerformanceTestControl; use crate::util::{ - self, BLOCK_SIZE, QCOW_CLUSTER_SIZE, deterministic_permutation, drain_completions, read_iovec, - submit_reads, submit_writes, write_iovec, + self, BLOCK_SIZE, L2_ENTRIES_PER_TABLE, QCOW_CLUSTER_SIZE, deterministic_permutation, + drain_completions, read_iovec, submit_reads, submit_writes, write_iovec, }; /// Submit num_ops AIO writes, wait for them all to land, then time @@ -301,3 +301,30 @@ pub fn micro_bench_qcow_multi_cluster_read(control: &PerformanceTestControl) -> elapsed } + +/// Read one cluster from each of num_ops distinct L2 tables in a +/// sparsely allocated QCOW2 image. +/// +/// The clusters are spaced L2_ENTRIES_PER_TABLE apart so every read +/// touches a different L2 table. With num_ops exceeding the L2 cache +/// capacity (100 entries), this forces eviction on nearly every read +/// and measures the cold L2 cache miss overhead. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_l2_cache_miss(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::sparse_qcow_tempfile(num_ops); + let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let stride = L2_ENTRIES_PER_TABLE as u64 * QCOW_CLUSTER_SIZE; + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_ops, stride, &[iovec]); + let elapsed = start.elapsed().as_secs_f64(); + + drain_completions(async_io.as_mut(), num_ops); + + elapsed +} From 3cb116fcfcec332778cbf520253091a33f3a6538 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 23:11:34 +0100 Subject: [PATCH 1243/1893] performance-metrics: Add async drain completions helper Signed-off-by: Anatol Belski --- performance-metrics/src/util.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index e4604f22d8..f9f322f22f 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -128,6 +128,18 @@ pub fn submit_writes(async_io: &mut dyn AsyncIo, count: usize, stride: u64, iove } } +/// Drain `count` completions from an asynchronous I/O backend that delivers +/// results via eventfd notification (e.g. io_uring). +pub fn drain_async_completions(async_io: &mut dyn AsyncIo, count: usize) { + let mut drained = 0usize; + while drained < count { + wait_for_eventfd(async_io.notifier()); + while async_io.next_completed_request().is_some() { + drained += 1; + } + } +} + /// Create an empty QCOW2 image sized for `num_clusters` clusters. /// No data clusters are allocated. fn create_empty_qcow_tempfile(num_clusters: usize) -> TempFile { From f8dbec0abbcab8058c2a0a8884195ea02a54d53f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 26 Mar 2026 23:26:35 +0100 Subject: [PATCH 1244/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 29 +++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 2c33bf9a83..1177b54f4f 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 82] = [ +const TEST_LIST: [PerformanceTest; 84] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1493,6 +1493,30 @@ const TEST_LIST: [PerformanceTest; 82] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_async_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_async_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 2b83ab495e..5ab907a87a 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -17,7 +17,8 @@ use block::raw_async_aio::RawFileAsyncAio; use crate::PerformanceTestControl; use crate::util::{ self, BLOCK_SIZE, L2_ENTRIES_PER_TABLE, QCOW_CLUSTER_SIZE, deterministic_permutation, - drain_completions, read_iovec, submit_reads, submit_writes, write_iovec, + drain_async_completions, drain_completions, read_iovec, submit_reads, submit_writes, + write_iovec, }; /// Submit num_ops AIO writes, wait for them all to land, then time @@ -328,3 +329,29 @@ pub fn micro_bench_qcow_l2_cache_miss(control: &PerformanceTestControl) -> f64 { elapsed } + +/// Read num_ops clusters from a prepopulated qcow2 image through the +/// QcowAsync io_uring path and time the total wall clock. +/// +/// Unlike micro_bench_qcow_read which uses QcowDiskSync (blocking), +/// this uses QcowDiskAsync where single-allocated-cluster reads go +/// through io_uring for true asynchronous completion. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_async_read(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::qcow_async_tempfile(num_ops); + let mut async_io = disk + .new_async_io(num_ops as u32) + .expect("new_async_io failed"); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + + // Drain all io_uring completions before stopping the clock. + drain_async_completions(async_io.as_mut(), num_ops); + start.elapsed().as_secs_f64() +} From e9d1ffd24f0112477dd35a137316d746d5a920f8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 09:30:13 +0100 Subject: [PATCH 1245/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++- performance-metrics/src/micro_bench_block.rs | 42 ++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 1177b54f4f..f68471fd6d 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 84] = [ +const TEST_LIST: [PerformanceTest; 86] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1517,6 +1517,30 @@ const TEST_LIST: [PerformanceTest; 84] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_batch_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_batch_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_batch_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_batch_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 5ab907a87a..5f2eabc28f 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -13,6 +13,7 @@ use std::time::Instant; use block::async_io::AsyncIo; use block::disk_file::AsyncDiskFile; use block::raw_async_aio::RawFileAsyncAio; +use block::{BatchRequest, RequestType}; use crate::PerformanceTestControl; use crate::util::{ @@ -355,3 +356,44 @@ pub fn micro_bench_qcow_async_read(control: &PerformanceTestControl) -> f64 { drain_async_completions(async_io.as_mut(), num_ops); start.elapsed().as_secs_f64() } + +/// Measure QCOW2 batch read submission via io_uring. +/// +/// Builds a batch of `num_ops` read requests and submits them all at once +/// through `submit_batch_requests`, which packs multiple SQEs into a single +/// io_uring submission. Returns the total wall clock time in seconds. +pub fn micro_bench_qcow_batch_read(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::qcow_async_tempfile(num_ops); + let mut async_io = disk + .new_async_io(num_ops as u32) + .expect("new_async_io failed"); + + let mut buf = vec![0u8; num_ops * QCOW_CLUSTER_SIZE as usize]; + + let batch: Vec = (0..num_ops) + .map(|i| { + let slice = + &mut buf[i * QCOW_CLUSTER_SIZE as usize..(i + 1) * QCOW_CLUSTER_SIZE as usize]; + BatchRequest { + offset: (i as u64 * QCOW_CLUSTER_SIZE) as libc::off_t, + iovecs: vec![libc::iovec { + iov_base: slice.as_mut_ptr() as *mut libc::c_void, + iov_len: QCOW_CLUSTER_SIZE as usize, + }] + .into(), + user_data: i as u64, + request_type: RequestType::In, + } + }) + .collect(); + + let start = Instant::now(); + async_io + .submit_batch_requests(&batch) + .expect("submit_batch_requests failed"); + + // Drain all io_uring completions before stopping the clock. + drain_async_completions(async_io.as_mut(), num_ops); + start.elapsed().as_secs_f64() +} From 377c260196bbeb2d1eeab31be50da364192d8fbb Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 10:23:27 +0100 Subject: [PATCH 1246/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++- performance-metrics/src/micro_bench_block.rs | 31 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index f68471fd6d..94896ae907 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 86] = [ +const TEST_LIST: [PerformanceTest; 88] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1541,6 +1541,30 @@ const TEST_LIST: [PerformanceTest; 86] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_async_random_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_random_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_async_random_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_random_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 5f2eabc28f..ae204906ec 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -397,3 +397,34 @@ pub fn micro_bench_qcow_batch_read(control: &PerformanceTestControl) -> f64 { drain_async_completions(async_io.as_mut(), num_ops); start.elapsed().as_secs_f64() } + +/// Read num_ops clusters from a prepopulated QCOW2 image in random order +/// through the QcowAsync io_uring path. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_async_random_read(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::qcow_async_tempfile(num_ops); + let mut async_io = disk + .new_async_io(num_ops as u32) + .expect("new_async_io failed"); + + let indices = deterministic_permutation(num_ops); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let start = Instant::now(); + for (seq, &cluster_idx) in indices.iter().enumerate() { + async_io + .read_vectored( + (cluster_idx as u64 * QCOW_CLUSTER_SIZE) as libc::off_t, + &[iovec], + seq as u64, + ) + .expect("read_vectored failed"); + } + + drain_async_completions(async_io.as_mut(), num_ops); + start.elapsed().as_secs_f64() +} From 2041ba5a919fa4db286db9235688fa5fcdaa4330 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 11:06:08 +0100 Subject: [PATCH 1247/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 28 ++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 94896ae907..868d3ee15e 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 88] = [ +const TEST_LIST: [PerformanceTest; 90] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1565,6 +1565,30 @@ const TEST_LIST: [PerformanceTest; 88] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_async_multi_cluster_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_multi_cluster_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_async_multi_cluster_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_multi_cluster_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index ae204906ec..4ec0051a40 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -428,3 +428,31 @@ pub fn micro_bench_qcow_async_random_read(control: &PerformanceTestControl) -> f drain_async_completions(async_io.as_mut(), num_ops); start.elapsed().as_secs_f64() } + +/// Issue large multi-cluster reads from a prepopulated QCOW2 image +/// through the QcowAsync io_uring path. +/// +/// Each read spans 8 contiguous clusters (512 KiB). With coalesced +/// mappings, this can hit the io_uring fast path for a single Readv. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_async_multi_cluster_read(control: &PerformanceTestControl) -> f64 { + const CLUSTERS_PER_READ: usize = 8; + + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::qcow_async_tempfile(num_ops); + let mut async_io = disk + .new_async_io(num_ops as u32) + .expect("new_async_io failed"); + + let read_size = CLUSTERS_PER_READ * QCOW_CLUSTER_SIZE as usize; + let mut buf = vec![0u8; read_size]; + let iovec = read_iovec(&mut buf); + + let num_reads = num_ops / CLUSTERS_PER_READ; + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_reads, read_size as u64, &[iovec]); + + drain_async_completions(async_io.as_mut(), num_reads); + start.elapsed().as_secs_f64() +} From c8fee5953fe8e2308474c9a3601c2b57e44482bc Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 11:40:22 +0100 Subject: [PATCH 1248/1893] 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 --- performance-metrics/src/util.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index f9f322f22f..c96d07253b 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -196,6 +196,14 @@ pub fn qcow_overlay_tempfile(num_clusters: usize) -> (TempFile, TempFile, QcowDi (backing, overlay, disk) } +/// QCOW2 overlay with raw backing opened via QcowDiskAsync. +pub fn qcow_async_overlay_tempfile(num_clusters: usize) -> (TempFile, TempFile, QcowDiskAsync) { + let (backing, overlay) = create_overlay_tempfiles(num_clusters); + let disk = QcowDiskAsync::new(overlay.as_file().try_clone().unwrap(), false, true, true) + .expect("failed to open overlay qcow2 via QcowDiskAsync"); + (backing, overlay, disk) +} + /// Create a zlib compressed QCOW2 image with `num_clusters` clusters /// via `qemu-img convert -c`. fn create_compressed_qcow_tempfile(num_clusters: usize) -> TempFile { From 324f16861d431fd53cc702d82b3256330b1bab91 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 12:55:47 +0100 Subject: [PATCH 1249/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 24 ++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 868d3ee15e..56c6a08342 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 90] = [ +const TEST_LIST: [PerformanceTest; 92] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1589,6 +1589,30 @@ const TEST_LIST: [PerformanceTest; 90] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_async_backing_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_backing_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_async_backing_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_backing_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 4ec0051a40..0d485aaa48 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -456,3 +456,27 @@ pub fn micro_bench_qcow_async_multi_cluster_read(control: &PerformanceTestContro drain_async_completions(async_io.as_mut(), num_reads); start.elapsed().as_secs_f64() } + +/// Read num_ops clusters from a QCOW2 overlay backed by a raw file +/// through the QcowAsync io_uring path. +/// +/// All reads fall through to the backing file (sync fallback in +/// QcowAsync since the mapping is not a single allocated cluster). +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_async_backing_read(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_backing, _overlay, disk) = util::qcow_async_overlay_tempfile(num_ops); + let mut async_io = disk + .new_async_io(num_ops as u32) + .expect("new_async_io failed"); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + + drain_async_completions(async_io.as_mut(), num_ops); + start.elapsed().as_secs_f64() +} From 4bf3672fad35a80e7ccadffaad35a5d1044ae17d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 13:00:20 +0100 Subject: [PATCH 1250/1893] 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 --- performance-metrics/src/util.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index c96d07253b..6482bd1151 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -257,6 +257,20 @@ pub fn compressed_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) (tmp, disk) } +/// Compressed QCOW2 opened via QcowDiskAsync. +pub fn compressed_qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDiskAsync) { + let tmp = create_compressed_qcow_tempfile(num_clusters); + let path = tmp.as_path().to_str().unwrap().to_string(); + let disk = QcowDiskAsync::new( + File::open(&path).expect("failed to open compressed qcow2"), + false, + false, + true, + ) + .expect("failed to open compressed qcow2 via QcowDiskAsync"); + (tmp, disk) +} + /// Number of data clusters covered by a single L2 table (64 KiB cluster, /// 8-byte entries -> 8192 entries per L2 table). pub const L2_ENTRIES_PER_TABLE: usize = QCOW_CLUSTER_SIZE as usize / 8; From fca6429e9bd8335ccd85cca68f20d8364b467faa Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 13:01:33 +0100 Subject: [PATCH 1251/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 22 +++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 56c6a08342..1a7956bba7 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 92] = [ +const TEST_LIST: [PerformanceTest; 94] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1613,6 +1613,30 @@ const TEST_LIST: [PerformanceTest; 92] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_async_compressed_read_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_compressed_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_async_compressed_read_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_compressed_read, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 0d485aaa48..6083c28f89 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -480,3 +480,25 @@ pub fn micro_bench_qcow_async_backing_read(control: &PerformanceTestControl) -> drain_async_completions(async_io.as_mut(), num_ops); start.elapsed().as_secs_f64() } + +/// Compressed clusters take the sync fallback in QcowAsync since they +/// require decompression. This measures decompression overhead through +/// the async code path. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_async_compressed_read(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::compressed_qcow_async_tempfile(num_ops); + let mut async_io = disk + .new_async_io(num_ops as u32) + .expect("new_async_io failed"); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + + drain_async_completions(async_io.as_mut(), num_ops); + start.elapsed().as_secs_f64() +} From 1069505151baea26c9e494c46d8e13a172b706ad Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 13:03:17 +0100 Subject: [PATCH 1252/1893] 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 --- performance-metrics/src/util.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 6482bd1151..9536173938 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -158,6 +158,14 @@ pub fn empty_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) { (tmp, disk) } +/// Empty QCOW2 opened via QcowDiskAsync. +pub fn empty_qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDiskAsync) { + let tmp = create_empty_qcow_tempfile(num_clusters); + let disk = QcowDiskAsync::new(tmp.as_file().try_clone().unwrap(), false, false, true) + .expect("failed to open qcow2 via QcowDiskAsync"); + (tmp, disk) +} + /// Create a QCOW2 overlay backed by a raw file with `num_clusters` /// pre-populated clusters. Returns (backing_tempfile, overlay_tempfile). fn create_overlay_tempfiles(num_clusters: usize) -> (TempFile, TempFile) { From 1d5d13eb7bb3dba77026b65c0b16529337460b69 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 13:04:35 +0100 Subject: [PATCH 1253/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 25 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 1a7956bba7..ede10c9538 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 94] = [ +const TEST_LIST: [PerformanceTest; 96] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1637,6 +1637,30 @@ const TEST_LIST: [PerformanceTest; 94] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_async_write_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_write, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_async_write_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_write, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 6083c28f89..9e0c44c210 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -502,3 +502,28 @@ pub fn micro_bench_qcow_async_compressed_read(control: &PerformanceTestControl) drain_async_completions(async_io.as_mut(), num_ops); start.elapsed().as_secs_f64() } + +/// Write num_ops clusters into an empty QCOW2 image through the +/// QcowAsync io_uring path. +/// +/// Writes in QcowAsync are synchronous (COW metadata allocation must +/// complete before the host offset is known), so this measures the +/// write path overhead through the async code path. +/// +/// Returns the total write wall clock time in seconds. +pub fn micro_bench_qcow_async_write(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::empty_qcow_async_tempfile(num_ops); + let mut async_io = disk + .new_async_io(num_ops as u32) + .expect("new_async_io failed"); + + let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = write_iovec(&buf); + + let start = Instant::now(); + submit_writes(async_io.as_mut(), num_ops, QCOW_CLUSTER_SIZE, &[iovec]); + + drain_async_completions(async_io.as_mut(), num_ops); + start.elapsed().as_secs_f64() +} From 3804968eeff5243be295dc4d7f28057761368506 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 13:06:01 +0100 Subject: [PATCH 1254/1893] 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 --- performance-metrics/src/util.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 9536173938..3f68fdd7eb 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -308,6 +308,14 @@ pub fn sparse_qcow_tempfile(num_l2_tables: usize) -> (TempFile, QcowDiskSync) { (tmp, disk) } +/// Sparse QCOW2 opened via QcowDiskAsync. +pub fn sparse_qcow_async_tempfile(num_l2_tables: usize) -> (TempFile, QcowDiskAsync) { + let tmp = create_sparse_qcow_tempfile(num_l2_tables); + let disk = QcowDiskAsync::new(tmp.as_file().try_clone().unwrap(), false, false, true) + .expect("failed to open qcow2 via QcowDiskAsync"); + (tmp, disk) +} + /// Spin and wait until the given eventfd becomes readable. pub fn wait_for_eventfd(notifier: &EventFd) { loop { From 8052c5a66b29d492e0c68d55278f1d50c2fcc8f5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 13:07:14 +0100 Subject: [PATCH 1255/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++++++++++- performance-metrics/src/micro_bench_block.rs | 22 +++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index ede10c9538..2e42cc03cf 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 96] = [ +const TEST_LIST: [PerformanceTest; 98] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1661,6 +1661,30 @@ const TEST_LIST: [PerformanceTest; 96] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_async_l2_cache_miss_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_l2_cache_miss, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_async_l2_cache_miss_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_async_l2_cache_miss, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 9e0c44c210..f545facda6 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -527,3 +527,25 @@ pub fn micro_bench_qcow_async_write(control: &PerformanceTestControl) -> f64 { drain_async_completions(async_io.as_mut(), num_ops); start.elapsed().as_secs_f64() } + +/// Read one cluster from each of num_ops distinct L2 tables in a sparse +/// QCOW2 image through the QcowAsync io_uring path. +/// +/// Returns the total read wall clock time in seconds. +pub fn micro_bench_qcow_async_l2_cache_miss(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::sparse_qcow_async_tempfile(num_ops); + let mut async_io = disk + .new_async_io(num_ops as u32) + .expect("new_async_io failed"); + + let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; + let iovec = read_iovec(&mut buf); + + let stride = L2_ENTRIES_PER_TABLE as u64 * QCOW_CLUSTER_SIZE; + let start = Instant::now(); + submit_reads(async_io.as_mut(), num_ops, stride, &[iovec]); + + drain_async_completions(async_io.as_mut(), num_ops); + start.elapsed().as_secs_f64() +} From 1ab877882ddbe96f5aee108f1881502e79449ef7 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 27 Mar 2026 13:14:37 +0100 Subject: [PATCH 1256/1893] 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 --- performance-metrics/src/main.rs | 26 +++++++++++- performance-metrics/src/micro_bench_block.rs | 42 ++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 2e42cc03cf..5192a4406b 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -378,7 +378,7 @@ mod adjuster { } } -const TEST_LIST: [PerformanceTest; 98] = [ +const TEST_LIST: [PerformanceTest; 100] = [ PerformanceTest { name: "boot_time_ms", func_ptr: performance_boot_time, @@ -1685,6 +1685,30 @@ const TEST_LIST: [PerformanceTest; 98] = [ }, unit_adjuster: adjuster::s_to_us, }, + PerformanceTest { + name: "micro_block_qcow_batch_write_128_us", + func_ptr: micro_bench_block::micro_bench_qcow_batch_write, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(128), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, + PerformanceTest { + name: "micro_block_qcow_batch_write_256_us", + func_ptr: micro_bench_block::micro_bench_qcow_batch_write, + control: PerformanceTestControl { + test_timeout: 10, + test_iterations: 20, + warmup_iterations: 5, + num_ops: Some(256), + ..PerformanceTestControl::default() + }, + unit_adjuster: adjuster::s_to_us, + }, ]; fn run_test_with_timeout( diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index f545facda6..aab20308c1 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -549,3 +549,45 @@ pub fn micro_bench_qcow_async_l2_cache_miss(control: &PerformanceTestControl) -> drain_async_completions(async_io.as_mut(), num_ops); start.elapsed().as_secs_f64() } + +/// Measure QCOW2 batch write submission via io_uring. +/// +/// 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. +/// +/// Returns the total wall clock time in seconds. +pub fn micro_bench_qcow_batch_write(control: &PerformanceTestControl) -> f64 { + let num_ops = control.num_ops.expect("num_ops required") as usize; + let (_tmp, disk) = util::empty_qcow_async_tempfile(num_ops); + let mut async_io = disk + .new_async_io(num_ops as u32) + .expect("new_async_io failed"); + + let buf = vec![0xA5u8; num_ops * QCOW_CLUSTER_SIZE as usize]; + + let batch: Vec = (0..num_ops) + .map(|i| { + let slice = &buf[i * QCOW_CLUSTER_SIZE as usize..(i + 1) * QCOW_CLUSTER_SIZE as usize]; + BatchRequest { + offset: (i as u64 * QCOW_CLUSTER_SIZE) as libc::off_t, + iovecs: vec![libc::iovec { + iov_base: slice.as_ptr() as *mut libc::c_void, + iov_len: QCOW_CLUSTER_SIZE as usize, + }] + .into(), + user_data: i as u64, + request_type: RequestType::Out, + } + }) + .collect(); + + let start = Instant::now(); + async_io + .submit_batch_requests(&batch) + .expect("submit_batch_requests failed"); + + drain_async_completions(async_io.as_mut(), num_ops); + start.elapsed().as_secs_f64() +} From e2c51042d325bc776fb917275278d0436295a988 Mon Sep 17 00:00:00 2001 From: Keith Adler Date: Thu, 16 Apr 2026 03:33:25 -0500 Subject: [PATCH 1257/1893] 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 --- vmm/src/vm.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 6b3f6c42e1..6bb088cb0d 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -129,7 +129,13 @@ pub enum Error { UefiLoad(#[source] arch::riscv64::uefi::Error), #[error("Cannot load the initramfs into memory")] - InitramfsLoad, + InitramfsLoad(#[source] std::io::Error), + + #[error("Cannot determine initramfs load address")] + InitramfsAddress(#[source] arch::Error), + + #[error("Cannot read initramfs into guest memory")] + InitramfsRead(#[source] vm_memory::GuestMemoryError), #[error("Cannot load the kernel command line in memory")] LoadCmdLine(#[source] linux_loader::loader::Error), @@ -334,7 +340,7 @@ pub enum Error { IgvmLoad(#[source] igvm_loader::Error), #[error("Error injecting NMI")] - ErrorNmi, + ErrorNmi(#[source] cpu::Error), #[error("Error resuming the VM")] ResumeVm(#[source] hypervisor::HypervisorVmError), @@ -1362,18 +1368,18 @@ impl Vm { let initramfs = self.initramfs.as_mut().unwrap(); let size: usize = initramfs .seek(SeekFrom::End(0)) - .map_err(|_| Error::InitramfsLoad)? + .map_err(Error::InitramfsLoad)? .try_into() .unwrap(); - initramfs.rewind().map_err(|_| Error::InitramfsLoad)?; + initramfs.rewind().map_err(Error::InitramfsLoad)?; let address = - arch::initramfs_load_addr(guest_mem, size).map_err(|_| Error::InitramfsLoad)?; + arch::initramfs_load_addr(guest_mem, size).map_err(Error::InitramfsAddress)?; let address = GuestAddress(address); guest_mem .read_volatile_from(address, initramfs, size) - .map_err(|_| Error::InitramfsLoad)?; + .map_err(Error::InitramfsRead)?; info!("Initramfs loaded: address = 0x{:x}", address.0); Ok(arch::InitramfsConfig { address, size }) @@ -3032,7 +3038,7 @@ impl Vm { .lock() .unwrap() .nmi() - .map_err(|_| Error::ErrorNmi); + .map_err(Error::ErrorNmi); } } From cdfedfaab24e51f034dd7fd37885d0188c07eaa5 Mon Sep 17 00:00:00 2001 From: Max Makarov Date: Thu, 16 Apr 2026 21:05:27 +0000 Subject: [PATCH 1258/1893] 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 --- vmm/src/device_manager.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 2ea8efe350..97e774f98e 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -657,6 +657,10 @@ pub enum DeviceManagerError { #[error("Invalid identifier: {0}")] InvalidIdentifier(String), + /// vfio-user socket path already in use by another user device. + #[error("vfio-user socket path already in use: {0:?}")] + UserDeviceSocketInUse(std::path::PathBuf), + /// Error activating virtio device #[error("Error activating virtio device")] VirtioActivate(#[source] ActivateError), @@ -4707,6 +4711,17 @@ impl DeviceManager { ) -> DeviceManagerResult { self.validate_identifier(&device_cfg.pci_common.id)?; + // Reject duplicate socket up-front: libvfio-user servers accept a + // single client, so a second Client::new() on the same socket blocks + // indefinitely in the handshake recvmsg() and hangs the VMM thread. + if let Some(existing) = &self.config.lock().unwrap().user_devices + && existing.iter().any(|d| d.socket == device_cfg.socket) + { + return Err(DeviceManagerError::UserDeviceSocketInUse( + device_cfg.socket.clone(), + )); + } + let (bdf, device_name) = self.add_vfio_user_device(device_cfg)?; // Update the PCIU bitmap From 030e63476e6c2dd923a122d4b0760a82fa15ce9d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 08:42:32 +0100 Subject: [PATCH 1259/1893] 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 --- .../tests/common/tests_wrappers.rs | 36 +++++++++---------- test_infra/src/lib.rs | 11 ++++++ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 159a7b0ca8..6c012164da 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -9,6 +9,7 @@ use std::path::{Path, PathBuf}; use std::string::String; use std::sync::mpsc; use std::thread; +use std::time::Duration; use block::ImageType; use net_util::MacAddr; @@ -28,10 +29,9 @@ pub(crate) fn _test_api_create_boot(target_api: &TargetApi, guest: &Guest) { .spawn() .unwrap(); - thread::sleep(std::time::Duration::new(1, 0)); - - // Verify API server is running - assert!(target_api.remote_command("ping", None)); + // Wait for API server to be ready + assert!(wait_until(Duration::from_secs(5), || target_api + .remote_command("ping", None))); // Create the VM first let request_body = guest.api_create_body(); @@ -68,10 +68,9 @@ pub(crate) fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { .spawn() .unwrap(); - thread::sleep(std::time::Duration::new(1, 0)); - - // Verify API server is running - assert!(target_api.remote_command("ping", None)); + // Wait for API server to be ready + assert!(wait_until(Duration::from_secs(5), || target_api + .remote_command("ping", None))); // Create the VM first let request_body = guest.api_create_body(); @@ -98,7 +97,7 @@ pub(crate) fn _test_api_shutdown(target_api: &TargetApi, guest: &Guest) { guest.ssh_command("sudo shutdown -H now").unwrap(); // Wait for the guest to be fully shutdown - thread::sleep(std::time::Duration::new(20, 0)); + assert!(guest.wait_for_ssh_unresponsive(Duration::from_secs(20))); // Then shut it down assert!(target_api.remote_command("shutdown", None)); @@ -129,10 +128,9 @@ pub(crate) fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { .spawn() .unwrap(); - thread::sleep(std::time::Duration::new(1, 0)); - - // Verify API server is running - assert!(target_api.remote_command("ping", None)); + // Wait for API server to be ready + assert!(wait_until(Duration::from_secs(5), || target_api + .remote_command("ping", None))); // Create the VM first let request_body = guest.api_create_body(); @@ -159,7 +157,7 @@ pub(crate) fn _test_api_delete(target_api: &TargetApi, guest: &Guest) { guest.ssh_command("sudo shutdown -H now").unwrap(); // Wait for the guest to be fully shutdown - thread::sleep(std::time::Duration::new(20, 0)); + assert!(guest.wait_for_ssh_unresponsive(Duration::from_secs(20))); // Then delete it assert!(target_api.remote_command("delete", None)); @@ -193,10 +191,9 @@ pub(crate) fn _test_api_pause_resume(target_api: &TargetApi, guest: &Guest) { .spawn() .unwrap(); - thread::sleep(std::time::Duration::new(1, 0)); - - // Verify API server is running - assert!(target_api.remote_command("ping", None)); + // Wait for API server to be ready + assert!(wait_until(Duration::from_secs(5), || target_api + .remote_command("ping", None))); // Create the VM first let request_body = guest.api_create_body(); @@ -209,9 +206,10 @@ pub(crate) fn _test_api_pause_resume(target_api: &TargetApi, guest: &Guest) { // Then boot it assert!(target_api.remote_command("boot", None)); - thread::sleep(std::time::Duration::new(20, 0)); let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + // Check that the VM booted as expected guest.validate_cpu_count(None); guest.validate_memory(None); diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 091fef7c8f..3f7f359804 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1251,6 +1251,17 @@ impl Guest { ) } + /// Waits until the guest's SSH port is no longer reachable, indicating + /// the guest has probably shutdown. + pub fn wait_for_ssh_unresponsive(&self, timeout: Duration) -> bool { + let addr = format!("{}:22", self.network.guest_ip0) + .parse::() + .unwrap(); + wait_until(timeout, || { + std::net::TcpStream::connect_timeout(&addr, Duration::from_secs(2)).is_err() + }) + } + pub fn api_create_body(&self) -> String { let mut body = serde_json::json!({ "cpus": { From 2eafee69bee6eb22ef06875c8e24e5a02a6e9ec7 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 08:46:35 +0100 Subject: [PATCH 1260/1893] tests: Remove explicit sleeps from balloon tests Use the new `wait_until()` to test the balloon size. Signed-off-by: Rob Bradford --- cloud-hypervisor/tests/integration.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index cc24cb8ebb..c009a0ed62 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -4752,8 +4752,9 @@ mod common_parallel { // Wait for balloon memory's initialization and check its size. // The virtio-balloon driver might take a few seconds to report the // balloon effective size back to the VMM. - thread::sleep(std::time::Duration::new(20, 0)); - + assert!(wait_until(Duration::from_secs(20), || { + balloon_size(&api_socket) == 2147483648 + })); let orig_balloon = balloon_size(&api_socket); println!("The original balloon memory size is {orig_balloon} bytes"); assert!(orig_balloon == 2147483648); @@ -4766,7 +4767,9 @@ mod common_parallel { // Give some time for the OOM to happen in the guest and be reported // back to the host. - thread::sleep(std::time::Duration::new(20, 0)); + assert!(wait_until(Duration::from_secs(20), || { + balloon_size(&api_socket) < 2147483648 + })); // 2nd: check balloon_mem's value to verify balloon has been automatically deflated let deflated_balloon = balloon_size(&api_socket); From 52c24136b5aff750eb89276809c0d05b127e2f0d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 08:59:42 +0100 Subject: [PATCH 1261/1893] 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 --- cloud-hypervisor/tests/common/tests_wrappers.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 6c012164da..036b815f40 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -663,7 +663,6 @@ pub(crate) fn test_vhost_user_net( kill_child(&mut child); let output = child.wait_with_output().unwrap(); - thread::sleep(std::time::Duration::new(5, 0)); let _ = daemon_child.kill(); let _ = daemon_child.wait(); @@ -806,7 +805,6 @@ pub(crate) fn test_vhost_user_blk( let output = child.wait_with_output().unwrap(); if let Some(mut daemon_child) = daemon_child { - thread::sleep(std::time::Duration::new(5, 0)); let _ = daemon_child.kill(); let _ = daemon_child.wait(); } @@ -876,7 +874,6 @@ pub(crate) fn test_boot_from_vhost_user_blk( let output = child.wait_with_output().unwrap(); if let Some(mut daemon_child) = daemon_child { - thread::sleep(std::time::Duration::new(5, 0)); let _ = daemon_child.kill(); let _ = daemon_child.wait(); } From a1cfbd6f5c8d15500c5932ab369aa89ee31a0dcc Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 09:07:40 +0100 Subject: [PATCH 1262/1893] 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 --- .../tests/common/tests_wrappers.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 036b815f40..bc68cd6b66 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -569,13 +569,19 @@ pub(crate) fn test_vhost_user_net( if client_mode_daemon { child = ch_command.spawn().unwrap(); - // Make sure the VMM is waiting for the backend to connect - thread::sleep(std::time::Duration::new(10, 0)); + // Wait for the VMM to create the socket before starting the daemon + assert!(wait_until(Duration::from_secs(10), || Path::new( + &vunet_socket_path + ) + .exists())); daemon_child = daemon_command.spawn().unwrap(); } else { daemon_child = daemon_command.spawn().unwrap(); - // Make sure the backend is waiting for the VMM to connect - thread::sleep(std::time::Duration::new(10, 0)); + // Wait for the daemon to create the socket before starting the VMM + assert!(wait_until(Duration::from_secs(10), || Path::new( + &vunet_socket_path + ) + .exists())); child = ch_command.spawn().unwrap(); } @@ -1043,7 +1049,11 @@ pub(crate) fn _test_virtio_fs( prepare_daemon(&guest.tmp_dir, shared_dir.to_str().unwrap()); let r = std::panic::catch_unwind(|| { - thread::sleep(std::time::Duration::new(10, 0)); + // Wait for the daemon socket to be ready + assert!(wait_until(Duration::from_secs(10), || Path::new( + &virtiofsd_socket_path + ) + .exists())); let fs_params = format!( "id=myfs0,socket={},{}{}", virtiofsd_socket_path, From f55a90c170940e8d77b32ef6b95926e91489cc19 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 09:09:33 +0100 Subject: [PATCH 1263/1893] 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 --- .../tests/common/tests_wrappers.rs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index bc68cd6b66..f1f41ecb90 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -657,12 +657,13 @@ pub(crate) fn test_vhost_user_net( let desired_ram = 1024 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(10, 0)); - // Here by simply checking the size (through ssh), we validate // the connection is still working, which means vhost-user-net // keeps working after the resize. - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + assert!(wait_until(Duration::from_secs(10), || guest + .get_total_memory() + .unwrap_or_default() + > 960_000)); } }); @@ -790,9 +791,10 @@ pub(crate) fn test_vhost_user_blk( let desired_ram = 1024 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(10, 0)); - - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + assert!(wait_until(Duration::from_secs(10), || guest + .get_total_memory() + .unwrap_or_default() + > 960_000)); // Check again the content of the block device after the resize // has been performed. @@ -1025,8 +1027,10 @@ pub(crate) fn _test_virtio_fs( let desired_ram = 1024 << 20; resize_command(&api_socket, None, Some(desired_ram), None, None); - thread::sleep(std::time::Duration::new(30, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + assert!(wait_until(Duration::from_secs(30), || guest + .get_total_memory() + .unwrap_or_default() + > 960_000)); // After the resize, check again that file1 exists and its // content is "foo". From 01decd964f224b7fd626eab639652204eecbe318 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 09:16:37 +0100 Subject: [PATCH 1264/1893] 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 --- cloud-hypervisor/tests/common/tests_wrappers.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index f1f41ecb90..4d2b452845 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -1604,7 +1604,6 @@ pub(crate) fn _test_simple_launch(guest: &Guest) { let _ = guest.ssh_command("sudo systemctl stop snapd"); guest.ssh_command("sudo poweroff").unwrap(); - thread::sleep(std::time::Duration::new(20, 0)); let latest_events = [ &MetaEvent { event: "shutdown".to_string(), @@ -1619,7 +1618,9 @@ pub(crate) fn _test_simple_launch(guest: &Guest) { device_id: None, }, ]; - assert!(check_latest_events_exact(&latest_events, &event_path)); + assert!(wait_until(Duration::from_secs(20), || { + check_latest_events_exact(&latest_events, &event_path) + })); }); kill_child(&mut child); From 78224baac6e59af77bbb69f3d09ad7bb56afde21 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 11:30:55 +0100 Subject: [PATCH 1265/1893] tests: Remove explicit sleeps from virtio-fs tests Instead use `wait_until()` for mounting of the filesystem. Signed-off-by: Rob Bradford --- .../tests/common/tests_wrappers.rs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 4d2b452845..05a5ca0d4d 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -906,6 +906,7 @@ pub(crate) fn _test_virtio_fs( let disk_config = UbuntuDiskConfig::new(focal_image); let guest = Guest::new(Box::new(disk_config)); let api_socket = temp_api_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); let mut workload_path = dirs::home_dir().unwrap(); workload_path.push("workloads"); @@ -933,7 +934,8 @@ pub(crate) fn _test_virtio_fs( .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() .default_net() - .args(["--api-socket", &api_socket]); + .args(["--api-socket", &api_socket]) + .args(["--event-monitor", format!("path={event_path}").as_str()]); if pci_segment.is_some() { guest_command.args([ "--platform", @@ -993,13 +995,14 @@ pub(crate) fn _test_virtio_fs( .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}") ); } - - thread::sleep(std::time::Duration::new(10, 0)); } // Mount shared directory through virtio_fs filesystem guest - .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") + .wait_for_ssh_command( + "mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/", + Duration::from_secs(10), + ) .unwrap(); // Check file1 exists and its content is "foo" @@ -1044,11 +1047,19 @@ pub(crate) fn _test_virtio_fs( // Remove from VM guest.ssh_command("sudo umount mount_dir").unwrap(); assert!(remote_command(&api_socket, "remove-device", Some("myfs0"))); + + // Wait for the device to be fully removed before re-adding + let removed_event = MetaEvent { + event: "device-removed".to_string(), + device_id: Some("myfs0".to_string()), + }; + assert!(wait_until(Duration::from_secs(10), || { + check_sequential_events(&[&removed_event], &event_path) + })); } }); let (r, hotplug_daemon_child) = if r.is_ok() && hotplug { - thread::sleep(std::time::Duration::new(10, 0)); let (daemon_child, virtiofsd_socket_path) = prepare_daemon(&guest.tmp_dir, shared_dir.to_str().unwrap()); @@ -1088,10 +1099,13 @@ pub(crate) fn _test_virtio_fs( ); } - thread::sleep(std::time::Duration::new(10, 0)); - // Mount shared directory through virtio_fs filesystem + // Mount shared directory through virtio_fs filesystem, retrying + // until the hotplugged device is recognized by the guest guest - .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") + .wait_for_ssh_command( + "mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/", + Duration::from_secs(10), + ) .unwrap(); // Check file1 exists and its content is "foo" From 9ca6d4ca41620dc9e83395706314ad7074220931 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 11:33:15 +0100 Subject: [PATCH 1266/1893] 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 --- .../tests/common/tests_wrappers.rs | 86 ++++++------------- 1 file changed, 24 insertions(+), 62 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 05a5ca0d4d..8094aa8223 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2714,18 +2714,12 @@ pub(crate) fn _test_disk_hotplug(guest: &Guest, landlock_enabled: bool) { .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") ); - thread::sleep(std::time::Duration::new(10, 0)); - - // Check that /dev/vdc exists and the block size is 16M. - assert_eq!( + // Wait for the hotplugged disk to appear in the guest + assert!(wait_until(Duration::from_secs(10), || { guest .ssh_command("lsblk | grep vdc | grep -c 16M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); + .is_ok_and(|s| s.trim().parse::().unwrap_or_default() == 1) + })); // And check the block device can be read. guest .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M iflag=direct count=16") @@ -2733,17 +2727,10 @@ pub(crate) fn _test_disk_hotplug(guest: &Guest, landlock_enabled: bool) { // Let's remove it the extra disk. assert!(remote_command(&api_socket, "remove-device", Some("test0"))); - thread::sleep(std::time::Duration::new(5, 0)); - // And check /dev/vdc is not there - assert_eq!( - guest - .ssh_command("lsblk | grep -c vdc.*16M || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); + // Wait for the disk to disappear + assert!(wait_until(Duration::from_secs(10), || guest + .ssh_command("lsblk | grep -c vdc.*16M || true") + .is_ok_and(|s| s.trim().parse::().unwrap_or(1) == 0))); // And add it back to validate unplug did work correctly. let (cmd_success, cmd_output) = remote_command_w_output( @@ -2763,18 +2750,12 @@ pub(crate) fn _test_disk_hotplug(guest: &Guest, landlock_enabled: bool) { .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") ); - thread::sleep(std::time::Duration::new(10, 0)); - - // Check that /dev/vdc exists and the block size is 16M. - assert_eq!( + // Wait for the hotplugged disk to appear in the guest + assert!(wait_until(Duration::from_secs(10), || { guest .ssh_command("lsblk | grep vdc | grep -c 16M") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); + .is_ok_and(|s| s.trim().parse::().unwrap_or_default() == 1) + })); // And check the block device can be read. guest .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M iflag=direct count=16") @@ -2796,18 +2777,10 @@ pub(crate) fn _test_disk_hotplug(guest: &Guest, landlock_enabled: bool) { assert!(remote_command(&api_socket, "remove-device", Some("test0"))); - thread::sleep(std::time::Duration::new(20, 0)); - - // Check device has gone away - assert_eq!( - guest - .ssh_command("lsblk | grep -c vdc.*16M || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); + // Wait for the disk to disappear + assert!(wait_until(Duration::from_secs(20), || guest + .ssh_command("lsblk | grep -c vdc.*16M || true") + .is_ok_and(|s| s.trim().parse::().unwrap_or(1) == 0))); guest.reboot_linux(1); @@ -3483,14 +3456,10 @@ pub(crate) fn _test_vdpa_block(guest: &Guest) { .contains("{\"id\":\"myvdpa0\",\"bdf\":\"0001:00:01.0\"}") ); - thread::sleep(std::time::Duration::new(10, 0)); - - // Check IOMMU setup - assert!( - guest - .does_device_vendor_pair_match("0x1057", "0x1af4") - .unwrap_or_default() - ); + // Wait for the hotplugged device to appear + assert!(wait_until(Duration::from_secs(10), || guest + .does_device_vendor_pair_match("0x1057", "0x1af4") + .unwrap_or_default())); assert!( guest .ssh_command("ls /sys/kernel/iommu_groups/*/devices") @@ -3523,18 +3492,11 @@ pub(crate) fn _test_vdpa_block(guest: &Guest) { // Unplug the device let cmd_success = remote_command(&api_socket, "remove-device", Some("myvdpa0")); assert!(cmd_success); - thread::sleep(std::time::Duration::new(10, 0)); - // Check /dev/vdd doesn't exist anymore - assert_eq!( - guest - .ssh_command("lsblk | grep -c vdd || true") - .unwrap() - .trim() - .parse::() - .unwrap_or(1), - 0 - ); + // Wait for the device to disappear + assert!(wait_until(Duration::from_secs(10), || guest + .ssh_command("lsblk | grep -c vdd || true") + .is_ok_and(|s| s.trim().parse::().unwrap_or(1) == 0))); }); kill_child(&mut child); From 0107675eb1116f24747d1278895b8f8ae2e8bf34 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 11:33:15 +0100 Subject: [PATCH 1267/1893] 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 --- .../tests/common/tests_wrappers.rs | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 8094aa8223..f42fce9029 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2928,18 +2928,12 @@ pub(crate) fn _test_net_hotplug( ); } - thread::sleep(std::time::Duration::new(5, 0)); - - // 2 network interfaces + default localhost ==> 3 interfaces - assert_eq!( + // Wait for the hotplugged network interface to appear + assert!(wait_until(Duration::from_secs(10), || { guest .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); + .is_ok_and(|s| s.trim().parse::().unwrap_or_default() == 3) + })); // Test the same using the added network interface's IP assert_eq!( @@ -2956,9 +2950,13 @@ pub(crate) fn _test_net_hotplug( 3 ); - // Remove network + // Remove network and wait for it to disappear assert!(remote_command(&api_socket, "remove-device", Some("test0"),)); - thread::sleep(std::time::Duration::new(5, 0)); + assert!(wait_until(Duration::from_secs(10), || { + guest + .ssh_command("ip -o link | wc -l") + .is_ok_and(|s| s.trim().parse::().unwrap_or_default() == 2) + })); // Add network let (cmd_success, cmd_output) = remote_command_w_output( @@ -2991,18 +2989,12 @@ pub(crate) fn _test_net_hotplug( ); } - thread::sleep(std::time::Duration::new(5, 0)); - - // 2 network interfaces + default localhost ==> 3 interfaces - assert_eq!( + // Wait for the hotplugged network interface to appear + assert!(wait_until(Duration::from_secs(10), || { guest .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); + .is_ok_and(|s| s.trim().parse::().unwrap_or_default() == 3) + })); guest.reboot_linux(0); @@ -3345,10 +3337,12 @@ pub(crate) fn _test_macvtap( let mut child = guest_command.capture_output().spawn().unwrap(); if hotplug { - // Give some time to the VMM process to listen to the API - // socket. This is the only requirement to avoid the following - // call to ch-remote from failing. - thread::sleep(std::time::Duration::new(10, 0)); + // Wait for the VMM process to listen to the API socket + assert!(wait_until(Duration::from_secs(10), || remote_command( + &api_socket, + "ping", + None + ))); // Hotplug the virtio-net device let (cmd_success, cmd_output) = remote_command_w_output(&api_socket, "add-net", Some(&net_params)); From 1269475c2aa8894c7a594045e118b5cdcb6b5b7e Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 11:44:03 +0100 Subject: [PATCH 1268/1893] tests: Remove explicit sleeps from pvpanic test Instead wait for the event to be delivered that it has panicked. Signed-off-by: Rob Bradford --- cloud-hypervisor/tests/common/tests_wrappers.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index f42fce9029..6479f2e2a6 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -3165,17 +3165,14 @@ pub(crate) fn _test_pvpanic(guest: &Guest) { // Trigger guest a panic make_guest_panic(guest); - // Wait a while for guest - thread::sleep(std::time::Duration::new(10, 0)); - + // Wait for the panic event to be recorded let expected_sequential_events = [&MetaEvent { event: "panic".to_string(), device_id: None, }]; - assert!(check_latest_events_exact( - &expected_sequential_events, - &event_path - )); + assert!(wait_until(Duration::from_secs(10), || { + check_latest_events_exact(&expected_sequential_events, &event_path) + })); }); kill_child(&mut child); From 43b5a474f9422f79b5df824e16e763f41d023931 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 15 Apr 2026 11:45:03 +0100 Subject: [PATCH 1269/1893] 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 --- cloud-hypervisor/tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index c009a0ed62..508e5e93dc 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5753,7 +5753,7 @@ mod dbus_api { guest.ssh_command("sudo shutdown -H now").unwrap(); // Wait for the guest to be fully shutdown - thread::sleep(std::time::Duration::new(20, 0)); + assert!(guest.wait_for_ssh_unresponsive(Duration::from_secs(20))); // Then shutdown the VM assert!(dbus_api.remote_command("shutdown", None)); From afd155d578e0205a5beab94fe3860ca1238a876e Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Mon, 16 Mar 2026 15:26:12 +0100 Subject: [PATCH 1270/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com --- pci/src/bus.rs | 32 ++++++++++++++++++-------------- pci/src/lib.rs | 4 +++- vmm/src/device_manager.rs | 2 +- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/pci/src/bus.rs b/pci/src/bus.rs index 1fa7bd866a..57e71551b4 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -20,9 +20,13 @@ use crate::configuration::{ }; use crate::device::{BarReprogrammingParams, DeviceRelocation, Error as PciDeviceError, PciDevice}; +/// Denotes the PCI device ID of a bus' root bridge device. +pub const PCI_ROOT_DEVICE_ID: u8 = 0; +/// Denotes the maximum number of PCI devices allowed on a bus. 32 per PCI spec. +pub const NUM_DEVICE_IDS: u8 = 32; + const VENDOR_ID_INTEL: u16 = 0x8086; const DEVICE_ID_INTEL_VIRT_PCIE_HOST: u16 = 0x0d57; -const NUM_DEVICE_IDS: usize = 32; /// Errors for device manager. #[derive(Error, Debug)] @@ -113,18 +117,18 @@ impl PciDevice for PciRoot { pub struct PciBus { /// Devices attached to this bus. /// Device 0 is host bridge. - devices: HashMap>>, + devices: HashMap>>, device_reloc: Arc, - device_ids: Vec, + device_ids: [bool; NUM_DEVICE_IDS as usize], } impl PciBus { pub fn new(pci_root: PciRoot, device_reloc: Arc) -> Self { - let mut devices: HashMap>> = HashMap::new(); - let mut device_ids: Vec = vec![false; NUM_DEVICE_IDS]; + let mut devices: HashMap>> = HashMap::new(); + let mut device_ids = [false; NUM_DEVICE_IDS as usize]; - devices.insert(0, Arc::new(Mutex::new(pci_root))); - device_ids[0] = true; + devices.insert(PCI_ROOT_DEVICE_ID, Arc::new(Mutex::new(pci_root))); + device_ids[PCI_ROOT_DEVICE_ID as usize] = true; PciBus { devices, @@ -158,7 +162,7 @@ impl PciBus { Ok(()) } - pub fn add_device(&mut self, device_id: u32, device: Arc>) -> Result<()> { + pub fn add_device(&mut self, device_id: u8, device: Arc>) -> Result<()> { self.devices.insert(device_id, device); Ok(()) } @@ -180,7 +184,7 @@ impl PciBus { } pub fn get_device_id(&mut self, id: usize) -> Result<()> { - if id < NUM_DEVICE_IDS { + if id < NUM_DEVICE_IDS as usize { if self.device_ids[id] { Err(PciRootError::AlreadyInUsePciDeviceSlot(id)) } else { @@ -193,7 +197,7 @@ impl PciBus { } pub fn put_device_id(&mut self, id: usize) -> Result<()> { - if id < NUM_DEVICE_IDS { + if id < NUM_DEVICE_IDS as usize { self.device_ids[id] = false; Ok(()) } else { @@ -240,7 +244,7 @@ impl PciConfigIo { .lock() .unwrap() .devices - .get(&(device as u32)) + .get(&(device as u8)) .map_or(0xffff_ffff, |d| { d.lock().unwrap().read_config_register(register) }) @@ -265,7 +269,7 @@ impl PciConfigIo { } let pci_bus = self.pci_bus.as_ref().lock().unwrap(); - if let Some(d) = pci_bus.devices.get(&(device as u32)) { + if let Some(d) = pci_bus.devices.get(&(device as u8)) { let mut device = d.lock().unwrap(); // Update the register value @@ -376,7 +380,7 @@ impl PciConfigMmio { .lock() .unwrap() .devices - .get(&(device as u32)) + .get(&(device as u8)) .map_or(0xffff_ffff, |d| { d.lock().unwrap().read_config_register(register) }) @@ -395,7 +399,7 @@ impl PciConfigMmio { } let pci_bus = self.pci_bus.lock().unwrap(); - if let Some(d) = pci_bus.devices.get(&(device as u32)) { + if let Some(d) = pci_bus.devices.get(&(device as u8)) { let mut device = d.lock().unwrap(); // Update the register value diff --git a/pci/src/lib.rs b/pci/src/lib.rs index 17c3ab7235..c5bba16d29 100644 --- a/pci/src/lib.rs +++ b/pci/src/lib.rs @@ -21,7 +21,9 @@ use std::str::FromStr; use serde::de::Visitor; -pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError}; +pub use self::bus::{ + NUM_DEVICE_IDS, PCI_ROOT_DEVICE_ID, PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError, +}; pub use self::configuration::{ PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityId, PciClassCode, PciConfiguration, PciExpressCapabilityId, PciHeaderType, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 97e774f98e..7f96e1d080 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4125,7 +4125,7 @@ impl DeviceManager { .unwrap(); pci_bus - .add_device(bdf.device() as u32, pci_device) + .add_device(bdf.device(), pci_device) .map_err(DeviceManagerError::AddPciDevice)?; self.bus_devices.push(Arc::clone(&bus_device)); From 34f08002e16f4900e2d21e94cca3e47e25b8c418 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Thu, 26 Mar 2026 14:16:06 +0100 Subject: [PATCH 1271/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com Signed-off-by: Rob Bradford --- pci/src/bus.rs | 156 +++++++++++++++++++++++++++++++++++--- vmm/src/device_manager.rs | 5 +- vmm/src/pci_segment.rs | 13 +++- 3 files changed, 160 insertions(+), 14 deletions(-) diff --git a/pci/src/bus.rs b/pci/src/bus.rs index 57e71551b4..bd0abec303 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -47,10 +47,10 @@ pub enum PciRootError { #[error("Could not find an available device slot on the PCI bus")] NoPciDeviceSlotAvailable, /// Invalid PCI device identifier provided. - #[error("Invalid PCI device identifier provided")] + #[error("Invalid PCI device identifier provided: {0}")] InvalidPciDeviceSlot(usize), /// Valid PCI device identifier but already used. - #[error("Valid PCI device identifier but already used")] + #[error("Valid PCI device identifier but already used: {0}")] AlreadyInUsePciDeviceSlot(usize), } pub type Result = std::result::Result; @@ -172,15 +172,42 @@ impl PciBus { Ok(()) } - pub fn next_device_id(&mut self) -> Result { - for (idx, device_id) in self.device_ids.iter_mut().enumerate() { - if !(*device_id) { - *device_id = true; - return Ok(idx as u32); + /// Allocates a PCI device ID on the bus. + /// + /// - `id`: ID to allocate on the bus. If [`None`], the next free + /// device ID on the bus is allocated, else the ID given is + /// allocated + /// + /// ## Errors + /// * Returns [`PciRootError::AlreadyInUsePciDeviceSlot`] in case + /// the ID requested is already allocated. + /// * Returns [`PciRootError::InvalidPciDeviceSlot`] in case the + /// requested ID exceeds the maximum number of devices allowed per + /// bus (see [`NUM_DEVICE_IDS`]). + /// * If `id` is [`None`]: Returns + /// [`PciRootError::NoPciDeviceSlotAvailable`] if no free device + /// slot is available on the bus. + pub fn allocate_device_id(&mut self, id: Option) -> Result { + if let Some(idx) = id.map(|i| i as usize) { + if idx < NUM_DEVICE_IDS as usize { + if self.device_ids[idx] { + Err(PciRootError::AlreadyInUsePciDeviceSlot(idx)) + } else { + self.device_ids[idx] = true; + Ok(idx as u8) + } + } else { + Err(PciRootError::InvalidPciDeviceSlot(idx)) + } + } else { + for (idx, device_id) in self.device_ids.iter_mut().enumerate() { + if !(*device_id) { + *device_id = true; + return Ok(idx as u8); + } } + Err(PciRootError::NoPciDeviceSlotAvailable) } - - Err(PciRootError::NoPciDeviceSlotAvailable) } pub fn get_device_id(&mut self, id: usize) -> Result<()> { @@ -496,3 +523,114 @@ fn parse_io_config_address(config_address: u32) -> (usize, usize, usize, usize) shift_and_mask(config_address, REGISTER_NUMBER_OFFSET, REGISTER_NUMBER_MASK), ) } + +#[cfg(test)] +mod unit_tests { + use std::error::Error; + use std::result::Result; + + use super::*; + + #[derive(Debug)] + /// Helper struct that mocks the implementation of DeviceRelocation + struct MockDeviceRelocation; + + impl DeviceRelocation for MockDeviceRelocation { + fn move_bar( + &self, + _old_base: u64, + _new_base: u64, + _len: u64, + _pci_dev: &mut dyn PciDevice, + _region_type: PciBarRegionType, + ) -> Result<(), std::io::Error> { + Ok(()) + } + } + + fn setup_bus() -> PciBus { + let pci_root = PciRoot::new(None); + let mock_device_reloc = Arc::new(MockDeviceRelocation {}); + PciBus::new(pci_root, mock_device_reloc) + } + + #[test] + // Test to acquire all IDs that can be acquired + fn allocate_device_id_next_free() { + // The first address is occupied by the root + let mut bus = setup_bus(); + for expected_id in 1..NUM_DEVICE_IDS { + assert_eq!(expected_id, bus.allocate_device_id(None).unwrap()); + } + } + + #[test] + // Test that requesting specific ID work + fn allocate_device_id_request_id() -> Result<(), Box> { + // The first address is occupied by the root + let mut bus = setup_bus(); + let max_id = NUM_DEVICE_IDS - 1; + assert_eq!(0x01_u8, bus.allocate_device_id(Some(0x01))?); + assert_eq!(0x10_u8, bus.allocate_device_id(Some(0x10))?); + assert_eq!(max_id, bus.allocate_device_id(Some(max_id))?); + Ok(()) + } + + #[test] + // Test that gaps resulting from explicit allocations are filled by implicit ones, + // beginning with the first free slot + fn allocate_device_id_fills_gaps() -> Result<(), Box> { + // The first address is occupied by the root + let mut bus = setup_bus(); + assert_eq!(0x01_u8, bus.allocate_device_id(Some(0x01))?); + assert_eq!(0x03_u8, bus.allocate_device_id(Some(0x03))?); + assert_eq!(0x06_u8, bus.allocate_device_id(Some(0x06))?); + assert_eq!(0x02_u8, bus.allocate_device_id(None)?); + assert_eq!(0x04_u8, bus.allocate_device_id(None)?); + assert_eq!(0x05_u8, bus.allocate_device_id(None)?); + assert_eq!(0x07_u8, bus.allocate_device_id(None)?); + Ok(()) + } + + #[test] + // Test that requesting the same ID twice fails + fn allocate_device_id_request_id_twice_fails() -> Result<(), Box> { + let mut bus = setup_bus(); + let max_id = NUM_DEVICE_IDS - 1; + bus.allocate_device_id(Some(max_id))?; + let result = bus.allocate_device_id(Some(max_id)); + assert!(matches!( + result, + Err(PciRootError::AlreadyInUsePciDeviceSlot(x)) if x == usize::from(max_id), + )); + Ok(()) + } + + #[test] + // Test to request an invalid ID + fn allocate_device_id_request_invalid_id_fails() -> Result<(), Box> { + let mut bus = setup_bus(); + let max_id = NUM_DEVICE_IDS + 1; + let result = bus.allocate_device_id(Some(max_id)); + assert!(matches!( + result, + Err(PciRootError::InvalidPciDeviceSlot(x)) if x == usize::from(max_id), + )); + Ok(()) + } + + #[test] + // Test to acquire an ID when all IDs were already acquired + fn allocate_device_id_none_left() { + // The first address is occupied by the root + let mut bus = setup_bus(); + for expected_id in 1..NUM_DEVICE_IDS { + assert_eq!(expected_id, bus.allocate_device_id(None).unwrap()); + } + let result = bus.allocate_device_id(None); + assert!(matches!( + result, + Err(PciRootError::NoPciDeviceSlotAvailable), + )); + } +} diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 7f96e1d080..1499f4d73b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -494,7 +494,7 @@ pub enum DeviceManagerError { /// Failed to find an available PCI device ID. #[error("Failed to find an available PCI device ID")] - NextPciDeviceId(#[source] pci::PciRootError), + AllocatePciDeviceId(#[source] pci::PciRootError), /// Could not reserve the PCI device ID. #[error("Could not reserve the PCI device ID")] @@ -4555,7 +4555,8 @@ impl DeviceManager { (pci_segment_id, pci_device_bdf, resources) } else { - let pci_device_bdf = self.pci_segments[pci_segment_id as usize].next_device_bdf()?; + let pci_device_bdf = + self.pci_segments[pci_segment_id as usize].allocate_device_id(None)?; (pci_segment_id, pci_device_bdf, None) }) diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index 81f11063ee..8ed03c3e26 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -164,15 +164,22 @@ impl PciSegment { ) } - pub(crate) fn next_device_bdf(&self) -> DeviceManagerResult { + /// Allocates a device's ID on this PCI segment. + /// + /// - `device_id`: Device ID to request for allocation + /// + /// ## Errors + /// * [`DeviceManagerError::AllocatePciDeviceId`] if device ID + /// allocation on the bus fails. + pub(crate) fn allocate_device_id(&self, device_id: Option) -> DeviceManagerResult { Ok(PciBdf::new( self.id, 0, self.pci_bus .lock() .unwrap() - .next_device_id() - .map_err(DeviceManagerError::NextPciDeviceId)? as u8, + .allocate_device_id(device_id) + .map_err(DeviceManagerError::AllocatePciDeviceId)?, 0, )) } From 93c17cb29156fb61be57395f360a12a89f95ed4b Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Thu, 26 Mar 2026 14:04:58 +0100 Subject: [PATCH 1272/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com Signed-off-by: Rob Bradford --- vmm/src/pci_segment.rs | 157 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index 8ed03c3e26..35d9e1fef5 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -209,6 +209,65 @@ impl PciSegment { Ok(()) } + + #[cfg(test)] + /// Creates a PciSegment without the need for an [`AddressManager`] + /// for testing purpose. + /// + /// An [`AddressManager`] would otherwise be required to create + /// [`PciBus`] instances. Instead, we use any struct that implements + /// [`DeviceRelocation`] to instantiate a [`PciBus`]. + pub(crate) fn new_without_address_manager( + id: u16, + numa_node: u32, + mem32_allocator: Arc>, + mem64_allocator: Arc>, + pci_irq_slots: &[u8; 32], + device_reloc: &Arc, + ) -> DeviceManagerResult { + let pci_root = PciRoot::new(None); + let pci_bus = Arc::new(Mutex::new(PciBus::new(pci_root, device_reloc.clone()))); + + let pci_config_mmio = Arc::new(Mutex::new(PciConfigMmio::new(Arc::clone(&pci_bus)))); + let mmio_config_address = + layout::PCI_MMCONFIG_START.0 + layout::PCI_MMIO_CONFIG_SIZE_PER_SEGMENT * id as u64; + + let start_of_mem32_area = mem32_allocator.lock().unwrap().base().0; + let end_of_mem32_area = mem32_allocator.lock().unwrap().end().0; + + let start_of_mem64_area = mem64_allocator.lock().unwrap().base().0; + let end_of_mem64_area = mem64_allocator.lock().unwrap().end().0; + + let segment = PciSegment { + id, + pci_bus, + pci_config_mmio, + mmio_config_address, + proximity_domain: numa_node, + pci_devices_up: 0, + pci_devices_down: 0, + #[cfg(target_arch = "x86_64")] + pci_config_io: None, + mem32_allocator, + mem64_allocator, + start_of_mem32_area, + end_of_mem32_area, + start_of_mem64_area, + end_of_mem64_area, + pci_irq_slots: *pci_irq_slots, + }; + + info!( + "Adding PCI segment: id={}, PCI MMIO config address: 0x{:x}, mem32 area [0x{:x}-0x{:x}], mem64 area [0x{:x}-0x{:x}]", + segment.id, + segment.mmio_config_address, + segment.start_of_mem32_area, + segment.end_of_mem32_area, + segment.start_of_mem64_area, + segment.end_of_mem64_area + ); + Ok(segment) + } } struct PciDevSlot { @@ -481,3 +540,101 @@ impl Aml for PciSegment { .to_aml_bytes(sink); } } + +#[cfg(test)] +mod unit_tests { + use std::result::Result; + + use vm_memory::GuestAddress; + + use super::*; + + #[derive(Debug)] + struct MockDeviceRelocation; + impl DeviceRelocation for MockDeviceRelocation { + fn move_bar( + &self, + _old_base: u64, + _new_base: u64, + _len: u64, + _pci_dev: &mut dyn pci::PciDevice, + _region_type: pci::PciBarRegionType, + ) -> Result<(), std::io::Error> { + Ok(()) + } + } + + fn setup() -> PciSegment { + let guest_addr = 0_u64; + let guest_size = 0x1000_usize; + let allocator_1 = Arc::new(Mutex::new( + AddressAllocator::new(GuestAddress(guest_addr), guest_size as u64).unwrap(), + )); + let allocator_2 = Arc::new(Mutex::new( + AddressAllocator::new(GuestAddress(guest_addr), guest_size as u64).unwrap(), + )); + let mock_device_reloc: Arc = Arc::new(MockDeviceRelocation {}); + let arr = [0_u8; 32]; + + PciSegment::new_without_address_manager( + 0, + 0, + allocator_1, + allocator_2, + &arr, + &mock_device_reloc, + ) + .unwrap() + } + + #[test] + // Test the default device ID for a segment with an empty bus (except for the root device). + fn allocate_device_id_default() { + // The first address is occupied by the root + let segment = setup(); + let bdf = segment.allocate_device_id(None).unwrap(); + assert_eq!(bdf.segment(), segment.id); + assert_eq!(bdf.bus(), 0); + assert_eq!(bdf.device(), 1); + assert_eq!(bdf.function(), 0); + } + + #[test] + // Test to acquire a specific device ID + fn allocate_device_id_fixed_device_id() { + // The first address is occupied by the root + let expect_device_id = 0x10_u8; + let segment = setup(); + let bdf = segment.allocate_device_id(Some(expect_device_id)).unwrap(); + assert_eq!(bdf.segment(), segment.id); + assert_eq!(bdf.bus(), 0); + assert_eq!(bdf.device(), expect_device_id); + assert_eq!(bdf.function(), 0); + } + + #[test] + // Test to acquire a device ID that is invalid, one that is already taken + // and one being greater than the number of allowed devices per bus. + fn allocate_device_id_invalid_device_id() { + // The first address is occupied by the root + let already_taken_device_id = 0x0_u8; + let overflow_device_id = 0xff_u8; + let segment = setup(); + let bdf_res = segment.allocate_device_id(Some(already_taken_device_id)); + assert!(matches!( + bdf_res, + Err(DeviceManagerError::GetPciDeviceId(e)) if matches!( + e, + pci::PciRootError::AlreadyInUsePciDeviceSlot(0x0) + ) + )); + let bdf_res = segment.allocate_device_id(Some(overflow_device_id)); + assert!(matches!( + bdf_res, + Err(DeviceManagerError::AllocatePciDeviceId(e)) if matches!( + e, + pci::PciRootError::InvalidPciDeviceSlot(0xff) + ) + )); + } +} From 3a5fad22b9221a86c6e1a763b4b14242c5c6be15 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 4 Apr 2026 13:30:51 -0700 Subject: [PATCH 1273/1893] vmm: Fix segment log message formatting Signed-off-by: Rob Bradford --- vmm/src/pci_segment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index 35d9e1fef5..37cc0dcc6c 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -105,7 +105,7 @@ impl PciSegment { }; info!( - "Adding PCI segment: id={}, PCI MMIO config address: 0x{:x}, mem32 area [0x{:x}-0x{:x}, mem64 area [0x{:x}-0x{:x}", + "Adding PCI segment: id={}, PCI MMIO config address: 0x{:x}, mem32 area [0x{:x}-0x{:x}], mem64 area [0x{:x}-0x{:x}]", segment.id, segment.mmio_config_address, segment.start_of_mem32_area, From 51a729a87454358c0f2d2c89b2baeca3b14e9aad Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 4 Apr 2026 11:05:01 -0700 Subject: [PATCH 1274/1893] 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 --- pci/src/bus.rs | 88 ++++++++++++++++++++++++++------------- vmm/src/device_manager.rs | 6 +-- vmm/src/pci_segment.rs | 19 +++++++-- 3 files changed, 76 insertions(+), 37 deletions(-) diff --git a/pci/src/bus.rs b/pci/src/bus.rs index bd0abec303..4e52ebc9be 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -114,21 +114,28 @@ impl PciDevice for PciRoot { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum DeviceIdState { + Free, + Reserved, + Allocated, +} + pub struct PciBus { /// Devices attached to this bus. /// Device 0 is host bridge. devices: HashMap>>, device_reloc: Arc, - device_ids: [bool; NUM_DEVICE_IDS as usize], + device_ids: [DeviceIdState; NUM_DEVICE_IDS as usize], } impl PciBus { pub fn new(pci_root: PciRoot, device_reloc: Arc) -> Self { let mut devices: HashMap>> = HashMap::new(); - let mut device_ids = [false; NUM_DEVICE_IDS as usize]; + let mut device_ids = [DeviceIdState::Free; NUM_DEVICE_IDS as usize]; devices.insert(PCI_ROOT_DEVICE_ID, Arc::new(Mutex::new(pci_root))); - device_ids[PCI_ROOT_DEVICE_ID as usize] = true; + device_ids[PCI_ROOT_DEVICE_ID as usize] = DeviceIdState::Allocated; PciBus { devices, @@ -172,6 +179,31 @@ impl PciBus { Ok(()) } + /// Reserves a PCI device ID on the bus, marking it as in-use so + /// that automatic allocation will not use it. + /// + /// - `id`: Preferred ID to reserve on the bus. + /// + /// ## Errors + /// + /// * Returns [`PciRootError::AlreadyInUsePciDeviceSlot`] if the + /// slot is already reserved or allocated. + /// * Returns [`PciRootError::InvalidPciDeviceSlot`] if the slot + /// exceeds [`NUM_DEVICE_IDS`]. + pub fn reserve_device_id(&mut self, id: u8) -> Result { + let idx = id as usize; + if idx < NUM_DEVICE_IDS as usize { + if self.device_ids[idx] == DeviceIdState::Free { + self.device_ids[idx] = DeviceIdState::Reserved; + Ok(id) + } else { + Err(PciRootError::AlreadyInUsePciDeviceSlot(idx)) + } + } else { + Err(PciRootError::InvalidPciDeviceSlot(idx)) + } + } + /// Allocates a PCI device ID on the bus. /// /// - `id`: ID to allocate on the bus. If [`None`], the next free @@ -179,6 +211,7 @@ impl PciBus { /// allocated /// /// ## Errors + /// /// * Returns [`PciRootError::AlreadyInUsePciDeviceSlot`] in case /// the ID requested is already allocated. /// * Returns [`PciRootError::InvalidPciDeviceSlot`] in case the @@ -190,10 +223,10 @@ impl PciBus { pub fn allocate_device_id(&mut self, id: Option) -> Result { if let Some(idx) = id.map(|i| i as usize) { if idx < NUM_DEVICE_IDS as usize { - if self.device_ids[idx] { + if self.device_ids[idx] == DeviceIdState::Allocated { Err(PciRootError::AlreadyInUsePciDeviceSlot(idx)) } else { - self.device_ids[idx] = true; + self.device_ids[idx] = DeviceIdState::Allocated; Ok(idx as u8) } } else { @@ -201,8 +234,8 @@ impl PciBus { } } else { for (idx, device_id) in self.device_ids.iter_mut().enumerate() { - if !(*device_id) { - *device_id = true; + if *device_id == DeviceIdState::Free { + *device_id = DeviceIdState::Allocated; return Ok(idx as u8); } } @@ -210,22 +243,9 @@ impl PciBus { } } - pub fn get_device_id(&mut self, id: usize) -> Result<()> { - if id < NUM_DEVICE_IDS as usize { - if self.device_ids[id] { - Err(PciRootError::AlreadyInUsePciDeviceSlot(id)) - } else { - self.device_ids[id] = true; - Ok(()) - } - } else { - Err(PciRootError::InvalidPciDeviceSlot(id)) - } - } - pub fn put_device_id(&mut self, id: usize) -> Result<()> { if id < NUM_DEVICE_IDS as usize { - self.device_ids[id] = false; + self.device_ids[id] = DeviceIdState::Free; Ok(()) } else { Err(PciRootError::InvalidPciDeviceSlot(id)) @@ -577,14 +597,13 @@ mod unit_tests { } #[test] - // Test that gaps resulting from explicit allocations are filled by implicit ones, - // beginning with the first free slot + // Test that reserved IDs are skipped by automatic allocation fn allocate_device_id_fills_gaps() -> Result<(), Box> { // The first address is occupied by the root let mut bus = setup_bus(); - assert_eq!(0x01_u8, bus.allocate_device_id(Some(0x01))?); - assert_eq!(0x03_u8, bus.allocate_device_id(Some(0x03))?); - assert_eq!(0x06_u8, bus.allocate_device_id(Some(0x06))?); + bus.reserve_device_id(0x01)?; + bus.reserve_device_id(0x03)?; + bus.reserve_device_id(0x06)?; assert_eq!(0x02_u8, bus.allocate_device_id(None)?); assert_eq!(0x04_u8, bus.allocate_device_id(None)?); assert_eq!(0x05_u8, bus.allocate_device_id(None)?); @@ -593,12 +612,12 @@ mod unit_tests { } #[test] - // Test that requesting the same ID twice fails - fn allocate_device_id_request_id_twice_fails() -> Result<(), Box> { + // Test that reserving the same ID twice fails + fn reserve_device_id_twice_fails() -> Result<(), Box> { let mut bus = setup_bus(); let max_id = NUM_DEVICE_IDS - 1; - bus.allocate_device_id(Some(max_id))?; - let result = bus.allocate_device_id(Some(max_id)); + bus.reserve_device_id(max_id)?; + let result = bus.reserve_device_id(max_id); assert!(matches!( result, Err(PciRootError::AlreadyInUsePciDeviceSlot(x)) if x == usize::from(max_id), @@ -606,6 +625,15 @@ mod unit_tests { Ok(()) } + #[test] + // Test that allocating a previously reserved ID succeeds (idempotent) + fn allocate_device_id_after_reserve() -> Result<(), Box> { + let mut bus = setup_bus(); + bus.reserve_device_id(0x10)?; + assert_eq!(0x10_u8, bus.allocate_device_id(Some(0x10))?); + Ok(()) + } + #[test] // Test to request an invalid ID fn allocate_device_id_request_invalid_id_fails() -> Result<(), Box> { diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 1499f4d73b..cb88fb8ccd 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -498,7 +498,7 @@ pub enum DeviceManagerError { /// Could not reserve the PCI device ID. #[error("Could not reserve the PCI device ID")] - GetPciDeviceId(#[source] pci::PciRootError), + ReservePciDeviceId(#[source] pci::PciRootError), /// Could not give the PCI device ID back. #[error("Could not give the PCI device ID back")] @@ -4550,8 +4550,8 @@ impl DeviceManager { .pci_bus .lock() .unwrap() - .get_device_id(pci_device_bdf.device() as usize) - .map_err(DeviceManagerError::GetPciDeviceId)?; + .allocate_device_id(Some(pci_device_bdf.device())) + .map_err(DeviceManagerError::AllocatePciDeviceId)?; (pci_segment_id, pci_device_bdf, resources) } else { diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index 37cc0dcc6c..6a4f10aa70 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -164,6 +164,17 @@ impl PciSegment { ) } + /// Reserves a device ID on this PCI segment, marking it as in-use + /// so that automatic allocation will not use it. + pub(crate) fn reserve_device_id(&self, device_id: u8) -> DeviceManagerResult<()> { + self.pci_bus + .lock() + .unwrap() + .reserve_device_id(device_id) + .map_err(DeviceManagerError::ReservePciDeviceId)?; + Ok(()) + } + /// Allocates a device's ID on this PCI segment. /// /// - `device_id`: Device ID to request for allocation @@ -613,17 +624,17 @@ mod unit_tests { } #[test] - // Test to acquire a device ID that is invalid, one that is already taken - // and one being greater than the number of allowed devices per bus. + // Test that reserving an already taken device ID fails and that + // allocating an out-of-range device ID fails. fn allocate_device_id_invalid_device_id() { // The first address is occupied by the root let already_taken_device_id = 0x0_u8; let overflow_device_id = 0xff_u8; let segment = setup(); - let bdf_res = segment.allocate_device_id(Some(already_taken_device_id)); + let bdf_res = segment.reserve_device_id(already_taken_device_id); assert!(matches!( bdf_res, - Err(DeviceManagerError::GetPciDeviceId(e)) if matches!( + Err(DeviceManagerError::ReservePciDeviceId(e)) if matches!( e, pci::PciRootError::AlreadyInUsePciDeviceSlot(0x0) ) From c3ec804a44e3475bd5a3ca66f5e8a8564754d19f Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 4 Apr 2026 09:07:11 -0700 Subject: [PATCH 1275/1893] 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 --- vmm/src/config.rs | 8 ++++++-- vmm/src/vm_config.rs | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 8e0c4232af..a1453a011d 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1195,8 +1195,8 @@ impl RateLimiterGroupConfig { } impl PciDeviceCommonConfig { - const OPTIONS: &[&str] = &["id", "pci_segment"]; - const OPTIONS_IOMMU: &[&str] = &["id", "iommu", "pci_segment"]; + const OPTIONS: &[&str] = &["id", "pci_segment", "pci_device_id"]; + const OPTIONS_IOMMU: &[&str] = &["id", "iommu", "pci_segment", "pci_device_id"]; pub fn parse(input: &str) -> Result { let mut parser = OptionParser::new(); @@ -1217,11 +1217,15 @@ impl PciDeviceCommonConfig { .convert("pci_segment") .map_err(Error::ParsePciDeviceCommonConfig)? .unwrap_or_default(); + let pci_device_id = parser + .convert::("pci_device_id") + .map_err(Error::ParsePciDeviceCommonConfig)?; Ok(Self { id, iommu, pci_segment, + pci_device_id, }) } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 156650cb2e..9162045404 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -281,6 +281,8 @@ pub struct PciDeviceCommonConfig { pub iommu: bool, #[serde(default)] pub pci_segment: u16, + #[serde(default)] + pub pci_device_id: Option, } #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] From e5d73159f6f10e800918c6ee18587cdc097f7c4a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 4 Apr 2026 11:57:13 -0700 Subject: [PATCH 1276/1893] 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 --- vmm/src/api/openapi/cloud-hypervisor.yaml | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 5be55560e6..422660d07a 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -984,6 +984,9 @@ components: pci_segment: type: integer format: int16 + pci_device_id: + type: integer + format: uint8 id: type: string serial: @@ -1046,6 +1049,9 @@ components: pci_segment: type: integer format: int16 + pci_device_id: + type: integer + format: uint8 rate_limiter_config: $ref: "#/components/schemas/RateLimiterConfig" offload_tso: @@ -1107,6 +1113,9 @@ components: pci_segment: type: integer format: int16 + pci_device_id: + type: integer + format: uint8 id: type: string @@ -1127,6 +1136,9 @@ components: pci_segment: type: integer format: int16 + pci_device_id: + type: integer + format: uint8 virtio_id: type: uint32 @@ -1149,6 +1161,9 @@ components: pci_segment: type: integer format: int16 + pci_device_id: + type: integer + format: uint8 id: type: string @@ -1196,6 +1211,9 @@ components: pci_segment: type: integer format: int16 + pci_device_id: + type: integer + format: uint8 id: type: string x_nv_gpudirect_clique: @@ -1226,6 +1244,9 @@ components: pci_segment: type: integer format: int16 + pci_device_id: + type: integer + format: uint8 id: type: string @@ -1249,6 +1270,9 @@ components: pci_segment: type: integer format: int16 + pci_device_id: + type: integer + format: uint8 id: type: string @@ -1429,6 +1453,12 @@ components: properties: socket: type: string + pci_segment: + type: integer + format: int16 + pci_device_id: + type: integer + format: uint8 LandlockConfig: required: From 4e247cf91dfc69c9a311d74a36e2ebe1543bb371 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 4 Apr 2026 12:02:55 -0700 Subject: [PATCH 1277/1893] 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 --- vmm/src/config.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index a1453a011d..5e1414aa1c 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1254,7 +1254,8 @@ impl DiskConfig { vhost_user=on|off,socket=,\ bw_size=,bw_one_time_burst=,bw_refill_time=,\ ops_size=,ops_one_time_burst=,ops_refill_time=,\ - id=,pci_segment=,rate_limit_group=,\ + id=,pci_segment=,pci_device_id=,\ + rate_limit_group=,\ queue_affinity=,\ serial=,backing_files=on|off,sparse=on|off,\ image_type=,lock_granularity=byte-range|full"; @@ -1496,7 +1497,8 @@ impl NetConfig { num_queues=,queue_size=,id=,\ vhost_user=,socket=,vhost_mode=client|server,\ bw_size=,bw_one_time_burst=,bw_refill_time=,\ - ops_size=,ops_one_time_burst=,ops_refill_time=,pci_segment=,\ + ops_size=,ops_one_time_burst=,ops_refill_time=,\ + pci_segment=,pci_device_id=,\ offload_tso=on|off,offload_ufo=on|off,offload_csum=on|off\""; pub fn parse(net: &str) -> Result { @@ -1767,7 +1769,7 @@ impl GenericVhostUserConfig { \"virtio_id=,\ socket=,\ queue_sizes=,\ - id=,pci_segment=\""; + id=,pci_segment=,pci_device_id=\""; pub fn parse(vhost_user: &str) -> Result { let mut parser = OptionParser::new(); @@ -1891,7 +1893,8 @@ impl GenericVhostUserConfig { impl FsConfig { pub const SYNTAX: &'static str = "virtio-fs parameters \ \"tag=,socket=,num_queues=,\ - queue_size=,id=,pci_segment=\""; + queue_size=,id=,\ + pci_segment=,pci_device_id=\""; pub fn parse(fs: &str) -> Result { let mut parser = OptionParser::new(); @@ -2044,7 +2047,8 @@ impl FwCfgItem { impl PmemConfig { pub const SYNTAX: &'static str = "Persistent memory parameters \ \"file=,size=,iommu=on|off,\ - discard_writes=on|off,id=,pci_segment=\""; + discard_writes=on|off,id=,\ + pci_segment=,pci_device_id=\""; pub fn parse(pmem: &str) -> Result { let mut parser = OptionParser::new(); @@ -2188,7 +2192,9 @@ impl DebugConsoleConfig { } impl DeviceConfig { - pub const SYNTAX: &'static str = "Direct device assignment parameters \"path=,iommu=on|off,id=,pci_segment=\""; + pub const SYNTAX: &'static str = "Direct device assignment parameters \ + \"path=,iommu=on|off,id=,\ + pci_segment=,pci_device_id=\""; pub fn parse(device: &str) -> Result { let mut parser = OptionParser::new(); @@ -2228,8 +2234,8 @@ impl DeviceConfig { } impl UserDeviceConfig { - pub const SYNTAX: &'static str = - "Userspace device socket=,id=,pci_segment=\""; + pub const SYNTAX: &'static str = "Userspace device socket=,id=,\ + pci_segment=,pci_device_id=\""; pub fn parse(user_device: &str) -> Result { let mut parser = OptionParser::new(); @@ -2257,7 +2263,7 @@ impl UserDeviceConfig { impl VdpaConfig { pub const SYNTAX: &'static str = "vDPA device \ \"path=,num_queues=,iommu=on|off,\ - id=,pci_segment=\""; + id=,pci_segment=,pci_device_id=\""; pub fn parse(vdpa: &str) -> Result { let mut parser = OptionParser::new(); @@ -2291,7 +2297,8 @@ impl VdpaConfig { impl VsockConfig { pub const SYNTAX: &'static str = "Virtio VSOCK parameters \ - \"cid=,socket=,iommu=on|off,id=,pci_segment=\""; + \"cid=,socket=,iommu=on|off,id=,\ + pci_segment=,pci_device_id=\""; pub fn parse(vsock: &str) -> Result { let mut parser = OptionParser::new(); From 7315a38a024e678963deb27b46e1c97956a62291 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Thu, 26 Mar 2026 09:17:44 +0100 Subject: [PATCH 1278/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com Signed-off-by: Rob Bradford --- vmm/src/config.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 5e1414aa1c..72f0f6d47f 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -17,6 +17,7 @@ use log::{debug, warn}; use option_parser::{ ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, Tuple, }; +use pci::NUM_DEVICE_IDS; use serde::{Deserialize, Serialize}; use thiserror::Error; use virtio_bindings::virtio_blk::VIRTIO_BLK_ID_BYTES; @@ -402,6 +403,13 @@ pub enum ValidationError { /// Invalid NUMA Configuration #[error("NUMA Configuration is invalid")] InvalidNumaConfig(String), + /// The supplied PCI ID was greater then the max. supported number + /// of devices per Bus + #[error("Given PCI device ID ({0}) is out of the supported range of 0..{NUM_DEVICE_IDS}")] + InvalidPciDeviceId(u8), + /// The supplied PCI ID is reserved + #[error("Given PCI device ID ({0}) is reserved")] + ReservedPciDeviceId(u8), } type ValidationResult = std::result::Result; @@ -414,6 +422,21 @@ pub fn add_to_config(items: &mut Option>, item: T) { } } +/// Check that the PCI device supplied is neither out of range nor does +/// it use any reserved device ID. +fn validate_pci_device_id(device_id: u8) -> ValidationResult<()> { + if device_id >= pci::NUM_DEVICE_IDS { + // Check the given ID is not out of range + return Err(ValidationError::InvalidPciDeviceId(device_id)); + } else if device_id == pci::PCI_ROOT_DEVICE_ID { + // Check the ID isn't any reserved one. Currently, only the device ID + // for the root device is reserved. + return Err(ValidationError::ReservedPciDeviceId(device_id)); + } + + Ok(()) +} + pub type Result = result::Result; pub struct VmParams<'a> { @@ -1243,6 +1266,10 @@ impl PciDeviceCommonConfig { } } + if let Some(device_id) = self.pci_device_id { + validate_pci_device_id(device_id)?; + } + Ok(()) } } @@ -5643,7 +5670,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" }]); still_valid_config.validate().unwrap(); - let mut still_valid_config = valid_config; + let mut still_valid_config = valid_config.clone(); // SAFETY: Safe as the file was just opened let fd1 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) }; // SAFETY: Safe as the file was just opened @@ -5653,6 +5680,45 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" still_valid_config.add_preserved_fds(vec![fd1, fd2]); } let _still_valid_config = still_valid_config.clone(); + + // Valid BDF test + let mut still_valid_config = valid_config.clone(); + still_valid_config.disks = Some(vec![DiskConfig { + pci_common: PciDeviceCommonConfig { + pci_device_id: Some(8), + ..Default::default() + }, + ..disk_fixture() + }]); + still_valid_config.validate().unwrap(); + // Invalid BDF - Same ID as Root device + let mut invalid_config = valid_config.clone(); + invalid_config.disks = Some(vec![DiskConfig { + pci_common: PciDeviceCommonConfig { + pci_device_id: Some(pci::PCI_ROOT_DEVICE_ID), + ..Default::default() + }, + ..disk_fixture() + }]); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::ReservedPciDeviceId( + pci::PCI_ROOT_DEVICE_ID + )) + ); + // Invalid BDF - Out of range + let mut invalid_config = valid_config.clone(); + invalid_config.disks = Some(vec![DiskConfig { + pci_common: PciDeviceCommonConfig { + pci_device_id: Some(pci::NUM_DEVICE_IDS + 1), + ..Default::default() + }, + ..disk_fixture() + }]); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::InvalidPciDeviceId(pci::NUM_DEVICE_IDS + 1)) + ); } #[test] fn test_landlock_parsing() -> Result<()> { From aace90f270503a6429254751c41a6287d0f88d80 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Fri, 13 Mar 2026 09:00:41 +0100 Subject: [PATCH 1279/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com Signed-off-by: Rob Bradford --- vmm/src/device_manager.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index cb88fb8ccd..1f1dd2f056 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1711,6 +1711,7 @@ impl DeviceManager { &id, handle.pci_common.pci_segment, handle.dma_handler, + handle.pci_common.pci_device_id, )?; // Track device BDF for Generic Initiator support @@ -1742,7 +1743,8 @@ impl DeviceManager { } if let Some(iommu_device) = iommu_device { - let dev_id = self.add_virtio_pci_device(iommu_device, &None, &iommu_id, 0, None)?; + let dev_id = + self.add_virtio_pci_device(iommu_device, &None, &iommu_id, 0, None, None)?; self.iommu_attached_devices = Some((dev_id, iommu_attached_devices)); } } @@ -3656,7 +3658,7 @@ impl DeviceManager { let pci_segment_id = 0x0_u16; let (pci_segment_id, pci_device_bdf, resources) = - self.pci_resources(&id, pci_segment_id)?; + self.pci_resources(&id, pci_segment_id, None)?; info!("Creating pvmemcontrol device: id = {id}"); let (pvmemcontrol_pci_device, pvmemcontrol_bus_device) = @@ -3922,8 +3924,11 @@ impl DeviceManager { id }; - let (pci_segment_id, pci_device_bdf, resources) = - self.pci_resources(&vfio_name, device_cfg.pci_common.pci_segment)?; + let (pci_segment_id, pci_device_bdf, resources) = self.pci_resources( + &vfio_name, + device_cfg.pci_common.pci_segment, + device_cfg.pci_common.pci_device_id, + )?; let mut needs_dma_mapping = false; @@ -4184,8 +4189,11 @@ impl DeviceManager { id }; - let (pci_segment_id, pci_device_bdf, resources) = - self.pci_resources(&vfio_user_name, device_cfg.pci_common.pci_segment)?; + let (pci_segment_id, pci_device_bdf, resources) = self.pci_resources( + &vfio_user_name, + device_cfg.pci_common.pci_segment, + device_cfg.pci_common.pci_device_id, + )?; let legacy_interrupt_group = if let Some(legacy_interrupt_manager) = &self.legacy_interrupt_manager { @@ -4301,6 +4309,7 @@ impl DeviceManager { virtio_device_id: &str, pci_segment_id: u16, dma_handler: Option>, + pci_device_id: Option, ) -> DeviceManagerResult { let id = format!("{VIRTIO_PCI_DEVICE_NAME_PREFIX}-{virtio_device_id}"); @@ -4309,7 +4318,7 @@ impl DeviceManager { node.children = vec![virtio_device_id.to_string()]; let (pci_segment_id, pci_device_bdf, resources) = - self.pci_resources(&id, pci_segment_id)?; + self.pci_resources(&id, pci_segment_id, pci_device_id)?; // Update the existing virtio node by setting the parent. if let Some(node) = self.device_tree.lock().unwrap().get_mut(virtio_device_id) { @@ -4446,7 +4455,7 @@ impl DeviceManager { info!("Creating pvpanic device {id}"); let (pci_segment_id, pci_device_bdf, resources) = - self.pci_resources(&id, pci_segment_id)?; + self.pci_resources(&id, pci_segment_id, None)?; let snapshot = snapshot_from_id(self.snapshot.as_ref(), id.as_str()); @@ -4484,7 +4493,7 @@ impl DeviceManager { info!("Creating ivshmem device {id}"); let (pci_segment_id, pci_device_bdf, resources) = - self.pci_resources(&id, pci_segment_id)?; + self.pci_resources(&id, pci_segment_id, None)?; let snapshot = snapshot_from_id(self.snapshot.as_ref(), id.as_str()); let ivshmem_ops = Arc::new(Mutex::new(IvshmemHandler { @@ -4529,6 +4538,7 @@ impl DeviceManager { &self, id: &str, pci_segment_id: u16, + pci_device_id: Option, ) -> DeviceManagerResult<(u16, PciBdf, Option>)> { // Look for the id in the device tree. If it can be found, that means // the device is being restored, otherwise it's created from scratch. @@ -4556,7 +4566,7 @@ impl DeviceManager { (pci_segment_id, pci_device_bdf, resources) } else { let pci_device_bdf = - self.pci_segments[pci_segment_id as usize].allocate_device_id(None)?; + self.pci_segments[pci_segment_id as usize].allocate_device_id(pci_device_id)?; (pci_segment_id, pci_device_bdf, None) }) @@ -5075,6 +5085,7 @@ impl DeviceManager { &id, handle.pci_common.pci_segment, handle.dma_handler, + handle.pci_common.pci_device_id, )?; // Update the PCIU bitmap From 5aa3692c6d3bad8abc87f9b0933f76393ec491ca Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 4 Apr 2026 11:09:01 -0700 Subject: [PATCH 1280/1893] 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 --- vmm/src/device_manager.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 1f1dd2f056..6c52026e1a 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1697,6 +1697,10 @@ impl DeviceManager { let mut iommu_attached_devices = Vec::new(); { + // Reserve all explicit PCI device IDs before any device creation + // so that they won't be picked for dynamic allocation. + self.reserve_explicit_device_ids()?; + for handle in self.virtio_devices.clone() { let mapping: Option> = if handle.pci_common.iommu { self.iommu_mapping.clone() @@ -4534,6 +4538,37 @@ impl DeviceManager { Ok(Some(ivshmem_device)) } + fn reserve_explicit_device_ids(&self) -> DeviceManagerResult<()> { + for handle in &self.virtio_devices { + if let Some(device_id) = handle.pci_common.pci_device_id { + self.pci_segments[handle.pci_common.pci_segment as usize] + .reserve_device_id(device_id)?; + } + } + + let config = self.config.lock().unwrap(); + + if let Some(devices) = &config.devices { + for device_cfg in devices { + if let Some(device_id) = device_cfg.pci_common.pci_device_id { + self.pci_segments[device_cfg.pci_common.pci_segment as usize] + .reserve_device_id(device_id)?; + } + } + } + + if let Some(user_devices) = &config.user_devices { + for device_cfg in user_devices { + if let Some(device_id) = device_cfg.pci_common.pci_device_id { + self.pci_segments[device_cfg.pci_common.pci_segment as usize] + .reserve_device_id(device_id)?; + } + } + } + + Ok(()) + } + fn pci_resources( &self, id: &str, From b4723999f8c53a3605deb0a8cff94c9ee14f6884 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 4 Apr 2026 12:49:53 -0700 Subject: [PATCH 1281/1893] 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 --- docs/device_model.md | 5 +++-- docs/vdpa.md | 24 ++++++++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/device_model.md b/docs/device_model.md index ed4577a2cd..e915c47eec 100644 --- a/docs/device_model.md +++ b/docs/device_model.md @@ -90,8 +90,9 @@ feature is enabled by default. For all virtio devices listed below, only `virtio-pci` transport layer is supported. Cloud Hypervisor supports multiple PCI segments, and users can -append `,pci_segment=` to the device flag in the Cloud -Hypervisor command line to assign devices to a specific PCI segment. +append `,pci_segment=` or `,pci_device_id=` to +the device flag in the Cloud Hypervisor command line to assign devices to a specific +PCI segment or into a specific device slot. ### virtio-block diff --git a/docs/vdpa.md b/docs/vdpa.md index c1aa34c571..1e171670ad 100644 --- a/docs/vdpa.md +++ b/docs/vdpa.md @@ -32,11 +32,12 @@ struct VdpaConfig { iommu: bool, id: Option, pci_segment: u16, + pci_device_id: Option } ``` ``` ---vdpa vDPA device "path=,num_queues=,iommu=on|off,id=,pci_segment=" +--vdpa vDPA device "path=,num_queues=,iommu=on|off,id=,pci_segment=,pci_device_id=" ``` ### `path` @@ -96,6 +97,21 @@ _Example_ --vdpa path=/dev/vhost-vdpa-0,pci_segment=1 ``` +### `pci_device_id` + +PCI device ID to assign to the vDPA device on its PCI bus. + +This parameter is optional. If not specified, a device ID is automatically +allocated. + +Value is an unsigned integer in the range 1-31. + +_Example_ + +``` +--vdpa path=/dev/vhost-vdpa-0,pci_device_id=5 +``` + ## Example with vDPA block simulator The vDPA framework provides a simulator with both `virtio-block` and @@ -146,10 +162,10 @@ The `virtio-block` device backed by the vDPA simulator can be found as ``` cloud@cloud:~$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT -nullb0 252:0 0 250G 0 disk -vda 254:0 0 2.2G 0 disk +nullb0 252:0 0 250G 0 disk +vda 254:0 0 2.2G 0 disk ├─vda1 254:1 0 2.1G 0 part / -├─vda14 254:14 0 4M 0 part +├─vda14 254:14 0 4M 0 part └─vda15 254:15 0 106M 0 part /boot/efi vdb 254:16 0 128M 0 disk ``` From 8259f929096904f943e1fb648179f04c99e8ebc0 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Wed, 25 Mar 2026 19:34:56 +0100 Subject: [PATCH 1282/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com --- .../tests/common/tests_wrappers.rs | 18 +++++++-------- cloud-hypervisor/tests/common/utils.rs | 8 +++---- cloud-hypervisor/tests/integration.rs | 22 +++++++++---------- test_infra/src/lib.rs | 4 ++-- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 6479f2e2a6..71087a9009 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -981,7 +981,7 @@ pub(crate) fn _test_virtio_fs( if hotplug { // Add fs to the VM - let (cmd_success, cmd_output) = + let (cmd_success, cmd_output, _) = remote_command_w_output(&api_socket, add_arg, Some(&fs_params)); assert!(cmd_success); @@ -1085,7 +1085,7 @@ pub(crate) fn _test_virtio_fs( ); // Add back and check it works - let (cmd_success, cmd_output) = + let (cmd_success, cmd_output, _) = remote_command_w_output(&api_socket, add_arg, Some(&fs_params)); assert!(cmd_success); if let Some(pci_segment) = pci_segment { @@ -1230,7 +1230,7 @@ pub(crate) fn _test_virtio_vsock(guest: &Guest, hotplug: bool) { guest.wait_vm_boot().unwrap(); if hotplug { - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-vsock", Some(format!("cid=3,socket={socket},id=test0").as_str()), @@ -2697,7 +2697,7 @@ pub(crate) fn _test_disk_hotplug(guest: &Guest, landlock_enabled: bool) { ); // Now let's add the extra disk. - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-disk", Some( @@ -2733,7 +2733,7 @@ pub(crate) fn _test_disk_hotplug(guest: &Guest, landlock_enabled: bool) { .is_ok_and(|s| s.trim().parse::().unwrap_or(1) == 0))); // And add it back to validate unplug did work correctly. - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-disk", Some( @@ -2898,7 +2898,7 @@ pub(crate) fn _test_net_hotplug( let r = std::panic::catch_unwind(|| { // Add network - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-net", Some( @@ -2959,7 +2959,7 @@ pub(crate) fn _test_net_hotplug( })); // Add network - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-net", Some( @@ -3341,7 +3341,7 @@ pub(crate) fn _test_macvtap( None ))); // Hotplug the virtio-net device - let (cmd_success, cmd_output) = + let (cmd_success, cmd_output, _) = remote_command_w_output(&api_socket, "add-net", Some(&net_params)); assert!(cmd_success); #[cfg(target_arch = "x86_64")] @@ -3436,7 +3436,7 @@ pub(crate) fn _test_vdpa_block(guest: &Guest) { // Hotplug an extra vDPA block device behind the vIOMMU // Add a new vDPA device to the VM - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-vdpa", Some("id=myvdpa0,path=/dev/vhost-vdpa-1,num_queues=1,pci_segment=1,iommu=on"), diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs index f7cc1ea181..d39cbcc580 100644 --- a/cloud-hypervisor/tests/common/utils.rs +++ b/cloud-hypervisor/tests/common/utils.rs @@ -688,7 +688,7 @@ pub struct Counters { pub(crate) fn get_counters(api_socket: &str) -> Counters { // Get counters - let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "counters", None); + let (cmd_success, cmd_output, _) = remote_command_w_output(api_socket, "counters", None); assert!(cmd_success); let counters: HashMap<&str, HashMap<&str, u64>> = @@ -738,7 +738,7 @@ pub(super) fn pty_read(mut pty: std::fs::File) -> Receiver { } pub(crate) fn get_pty_path(api_socket: &str, pty_type: &str) -> PathBuf { - let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None); + let (cmd_success, cmd_output, _) = remote_command_w_output(api_socket, "info", None); assert!(cmd_success); let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default(); assert_eq!("Pty", info["config"][pty_type]["mode"]); @@ -786,7 +786,7 @@ pub(crate) fn cleanup_vfio_network_interfaces() { } pub(crate) fn balloon_size(api_socket: &str) -> u64 { - let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None); + let (cmd_success, cmd_output, _) = remote_command_w_output(api_socket, "info", None); assert!(cmd_success); let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default(); @@ -802,7 +802,7 @@ pub(crate) fn balloon_size(api_socket: &str) -> u64 { } pub(crate) fn vm_state(api_socket: &str) -> String { - let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None); + let (cmd_success, cmd_output, _) = remote_command_w_output(api_socket, "info", None); assert!(cmd_success); let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default(); diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 508e5e93dc..17c93ba1e7 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -375,7 +375,7 @@ mod common_parallel { guest.wait_vm_boot().unwrap(); let r = std::panic::catch_unwind(|| { - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-disk", Some( @@ -2979,7 +2979,7 @@ mod common_parallel { guest.wait_vm_boot().unwrap(); // Add the disk to the VM - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-disk", Some("path=/tmp/resize.img,id=test0"), @@ -3094,7 +3094,7 @@ mod common_parallel { guest.wait_vm_boot().unwrap(); // Add the QCOW2 disk to the VM - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-disk", Some(&format!( @@ -4907,7 +4907,7 @@ mod common_parallel { let pmem_temp_file = TempFile::new().unwrap(); pmem_temp_file.as_file().set_len(128 << 20).unwrap(); - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-pmem", Some(&format!( @@ -5405,7 +5405,7 @@ mod common_parallel { guest.wait_vm_boot().unwrap(); // Hotplug the SPDK-NVMe device to the VM - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-user-device", Some(&format!( @@ -7982,7 +7982,7 @@ mod windows { assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num); // Hotplug network device - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-net", Some(windows_guest.guest().default_net_string().as_str()), @@ -8066,7 +8066,7 @@ mod windows { assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); // Hotplug disk device - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-disk", Some(format!("path={disk},readonly=off").as_str()), @@ -8104,7 +8104,7 @@ mod windows { assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); // Remount and check the file exists with the expected contents - let (cmd_success, _cmd_output) = remote_command_w_output( + let (cmd_success, _cmd_output, _) = remote_command_w_output( &api_socket, "add-disk", Some(format!("path={disk},readonly=off").as_str()), @@ -8195,7 +8195,7 @@ mod windows { let expected_ctrl_threads = disk_ctrl_threads_count(child.id()) + 1; // Hotplug disk device - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-disk", Some(format!("path={disk},readonly=off").as_str()), @@ -8248,7 +8248,7 @@ mod windows { // Remount for it in &disk_test_data { let disk = it[1].as_str(); - let (cmd_success, _cmd_output) = remote_command_w_output( + let (cmd_success, _cmd_output, _) = remote_command_w_output( &api_socket, "add-disk", Some(format!("path={disk},readonly=off").as_str()), @@ -8514,7 +8514,7 @@ mod vfio { guest.wait_vm_boot().unwrap(); // Hotplug the card to the VM - let (cmd_success, cmd_output) = remote_command_w_output( + let (cmd_success, cmd_output, _) = remote_command_w_output( &api_socket, "add-device", Some(format!("id=vfio0,path={NVIDIA_VFIO_DEVICE}").as_str()), diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 3f7f359804..b39e5a0842 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1981,7 +1981,7 @@ pub fn remote_command_w_output( api_socket: &str, command: &str, arg: Option<&str>, -) -> (bool, Vec) { +) -> (bool, Vec /* stdout */, Vec /* stderr */) { let mut cmd = Command::new(clh_command("ch-remote")); cmd.args([&format!("--api-socket={api_socket}"), command]); @@ -1991,7 +1991,7 @@ pub fn remote_command_w_output( let output = cmd.output().expect("Failed to launch ch-remote"); - (output.status.success(), output.stdout) + (output.status.success(), output.stdout, output.stderr) } pub fn parse_iperf3_output(output: &[u8], sender: bool, bandwidth: bool) -> Result { From f82eebc0b0c7113ec179b85f709bef5bfbc1f833 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Thu, 26 Mar 2026 13:12:15 +0100 Subject: [PATCH 1283/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com Signed-off-by: Rob Bradford --- cloud-hypervisor/tests/common/utils.rs | 26 +++++ cloud-hypervisor/tests/integration.rs | 138 +++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs index d39cbcc580..1821575d49 100644 --- a/cloud-hypervisor/tests/common/utils.rs +++ b/cloud-hypervisor/tests/common/utils.rs @@ -1033,3 +1033,29 @@ pub(crate) fn make_guest_panic(guest: &Guest) { // Trigger guest a panic guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); } + +/// Extracts a BDF from a CHV returned response +pub(crate) fn bdf_from_hotplug_response( + s: &str, +) -> ( + u16, /* Segment ID */ + u8, /* Bus ID */ + u8, /* Device ID */ + u8, /* Function ID */ +) { + let json: serde_json::Value = serde_json::from_str(s).expect("should be valid JSON"); + let bdf_str = json["bdf"] + .as_str() + .expect("should contain string key `bdf`"); + + // BDF format: "SSSS:BB:DD.F" + let parts: Vec<&str> = bdf_str.split(&[':', '.'][..]).collect(); + assert_eq!(parts.len(), 4, "unexpected BDF format: {bdf_str}"); + + let segment_id = u16::from_str_radix(parts[0], 16).unwrap(); + let bus_id = u8::from_str_radix(parts[1], 16).unwrap(); + let device_id = u8::from_str_radix(parts[2], 16).unwrap(); + let function_id = u8::from_str_radix(parts[3], 16).unwrap(); + + (segment_id, bus_id, device_id, function_id) +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 17c93ba1e7..54394903c8 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5699,6 +5699,144 @@ mod common_parallel { handle_child_output(r, &output); } + + #[test] + fn test_pci_device_id() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = edk2_path(); + + let api_socket = temp_api_path(&guest.tmp_dir); + + // Boot without network + let mut cmd = GuestCommand::new(&guest); + + cmd.args(["--api-socket", &api_socket]) + .default_cpus() + .default_memory() + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_net() + .default_disks() + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + // Add a network device with non-static device id request + let r = std::panic::catch_unwind(|| { + let (cmd_success, cmd_stdout, _) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test0,tap=,mac={},ip={},mask=255.255.255.128", + guest.network.guest_mac1, guest.network.host_ip1, + ) + .as_str(), + ), + ); + assert!(cmd_success); + // We now know the first free device ID on the bus + let output = String::from_utf8(cmd_stdout).expect("should work"); + let (_, _, first_free_device_id, _) = bdf_from_hotplug_response(output.as_str()); + assert_ne!(first_free_device_id, 0); + + // We expect a match from grep + let _ = String::from( + guest + .ssh_command(&format!( + "lspci -n | grep \"00:{first_free_device_id:02x}.0\"" + )) + .unwrap() + .trim(), + ); + // Calculate the succeeding device ID + let device_id_to_allocate = first_free_device_id + 1; + // We expect the succeeding device ID to be free + assert!(matches!( + guest.ssh_command(&format!( + "lspci -n | grep \"00:{device_id_to_allocate:02x}.0\"" + )), + Err(SshCommandError::NonZeroExitStatus(1)) + )); + + // Add a device to the next device slot explicitly + let (cmd_success, cmd_stdout, _) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test1337,tap=,mac={},ip={},mask=255.255.255.128,pci_device_id={}", + guest.network.guest_mac1, guest.network.host_ip1, device_id_to_allocate, + ) + .as_str(), + ), + ); + assert!(cmd_success); + // Retrieve what BDF we actually reserved and assert it's equal to that we wanted to reserve + let output = String::from_utf8(cmd_stdout).expect("should work"); + let (_, _, allocated_device_id, _) = bdf_from_hotplug_response(output.as_str()); + assert_eq!(device_id_to_allocate, allocated_device_id); + // Check that the device ID is really in use + let _ = String::from( + guest + .ssh_command(&format!( + "lspci -n | grep \"00:{allocated_device_id:02x}.0\"" + )) + .unwrap() + .trim(), + ); + // Remove the first device to create a hole + let cmd_success = remote_command(&api_socket, "remove-device", Some("test0")); + assert!(cmd_success); + thread::sleep(std::time::Duration::new(5, 0)); + // We left a hole in the used PCI IDs. The guest sees no device on the respective ID + assert!(matches!( + guest.ssh_command(&format!( + "lspci -n | grep \"00:{first_free_device_id:02x}.0\"" + )), + Err(SshCommandError::NonZeroExitStatus(1)) + )); + // Reuse the device ID hole by dynamically coalescing with the first free ID + let (cmd_success, cmd_stdout, _) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test0,tap=,mac={},ip={},mask=255.255.255.128", + guest.network.guest_mac1, guest.network.host_ip1, + ) + .as_str(), + ), + ); + assert!(cmd_success); + // Check that CHV reports that we added the same device to the same ID + let output = String::from_utf8(cmd_stdout).expect("should work"); + let (_, _, allocated_device_id, _) = bdf_from_hotplug_response(output.as_str()); + assert_eq!(first_free_device_id, allocated_device_id); + + // Check that guest sees the same device again at the same BDF + let _ = String::from( + guest + .ssh_command(&format!( + "lspci -n | grep \"00:{allocated_device_id:02x}.0\"" + )) + .unwrap() + .trim(), + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } } mod dbus_api { From f81faad0a15190c85b6668ac4416b1e4a78854fd Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Thu, 26 Mar 2026 11:48:03 +0100 Subject: [PATCH 1284/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com Signed-off-by: Rob Bradford --- cloud-hypervisor/tests/integration.rs | 78 +++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 54394903c8..51d4b06e86 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5837,6 +5837,84 @@ mod common_parallel { handle_child_output(r, &output); } + + #[test] + // Test that adding a duplicate PCI device ID fails + fn test_duplicate_pci_device_id() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = edk2_path(); + + let api_socket = temp_api_path(&guest.tmp_dir); + + // Boot without network + let mut cmd = GuestCommand::new(&guest); + + cmd.args(["--api-socket", &api_socket]) + .default_cpus() + .default_memory() + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_net() + .default_disks() + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + // Add a network device with non-static device ID request + let r = std::panic::catch_unwind(|| { + let (cmd_success, cmd_stdout, _) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test0,tap=,mac={},ip={},mask=255.255.255.128", + guest.network.guest_mac1, guest.network.host_ip1, + ) + .as_str(), + ), + ); + assert!(cmd_success); + + // We now know the first free device ID on the bus + let output = String::from_utf8(cmd_stdout).expect("should work"); + let (_, _, first_free_device_id, _) = bdf_from_hotplug_response(output.as_str()); + assert_ne!(first_free_device_id, 0); + + let (cmd_success, _, cmd_stderr) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test1337,tap=,mac={},ip={},mask=255.255.255.128,pci_device_id={first_free_device_id}", + guest.network.guest_mac1, guest.network.host_ip1, + ) + .as_str(), + ), + ); + // Check for fail; Allocating the same device ID for two devices is disallowed + assert!(!cmd_success); + // Check that the error message contains the expected error + let std_err_str = String::from_utf8(cmd_stderr).unwrap(); + assert!( + std_err_str.contains(&format!( + "Valid PCI device identifier but already used: {first_free_device_id}" + )), + "Command return was: {std_err_str}" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } } mod dbus_api { From 18873d88d7b61e02c4b4fce5fafa9172a6e2dee0 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Wed, 25 Mar 2026 18:34:53 +0100 Subject: [PATCH 1285/1893] 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 On-behalf-of: SAP pascal.scholz@sap.com Signed-off-by: Rob Bradford --- cloud-hypervisor/tests/integration.rs | 80 +++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 51d4b06e86..8657edc3bd 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5915,6 +5915,86 @@ mod common_parallel { handle_child_output(r, &output); } + + #[test] + // Test that requesting an invalid device ID fails. + fn test_invalid_pci_device_id() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = edk2_path(); + + let api_socket = temp_api_path(&guest.tmp_dir); + + // Boot without network + let mut cmd = GuestCommand::new(&guest); + + cmd.args(["--api-socket", &api_socket]) + .default_cpus() + .default_memory() + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_net() + .default_disks() + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + let r = std::panic::catch_unwind(|| { + // Invalid API call because the PCI device ID is out of range + let (cmd_success, _, cmd_stderr) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test0,tap=,mac={},ip={},mask=255.255.255.128,pci_device_id=188", + guest.network.guest_mac1, guest.network.host_ip1, + ) + .as_str(), + ), + ); + // Check for fail + assert!(!cmd_success); + // Check that the error message contains the expected error + let std_err_str = String::from_utf8(cmd_stderr).unwrap(); + assert!( + std_err_str + .contains("Given PCI device ID (188) is out of the supported range of 0..32"), + "Command return was: {std_err_str}", + ); + + // Use the reserved device ID 0 (root device) + let (cmd_success, _, cmd_stderr) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test0,tap=,mac={},ip={},mask=255.255.255.128,pci_device_id=0", + guest.network.guest_mac1, guest.network.host_ip1, + ) + .as_str(), + ), + ); + // Check for fail + assert!(!cmd_success); + // Check that the error message contains the expected error + let std_err_str = String::from_utf8(cmd_stderr).unwrap(); + assert!( + std_err_str.contains("Given PCI device ID (0) is reserved"), + "Command return was: {std_err_str}" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } } mod dbus_api { From d449983495db036307e6f0acc60bfebdc1b046c9 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 16 Apr 2026 21:01:25 +0100 Subject: [PATCH 1286/1893] 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 --- pci/src/bus.rs | 15 +++++++++++---- vmm/src/device_manager.rs | 10 +++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/pci/src/bus.rs b/pci/src/bus.rs index 4e52ebc9be..89efd2ed68 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -243,12 +243,19 @@ impl PciBus { } } - pub fn put_device_id(&mut self, id: usize) -> Result<()> { - if id < NUM_DEVICE_IDS as usize { - self.device_ids[id] = DeviceIdState::Free; + /// Frees a PCI device ID on the bus. + /// + /// - `id`: ID to free on the bus. + /// + /// ## Errors + /// * Returns [`PciRootError::InvalidPciDeviceSlot`] if the slot + /// exceeds [`NUM_DEVICE_IDS`]. + pub fn free_device_id(&mut self, id: u8) -> Result<()> { + if id < NUM_DEVICE_IDS { + self.device_ids[id as usize] = DeviceIdState::Free; Ok(()) } else { - Err(PciRootError::InvalidPciDeviceSlot(id)) + Err(PciRootError::InvalidPciDeviceSlot(id as usize)) } } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 6c52026e1a..9f65784ecb 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -500,9 +500,9 @@ pub enum DeviceManagerError { #[error("Could not reserve the PCI device ID")] ReservePciDeviceId(#[source] pci::PciRootError), - /// Could not give the PCI device ID back. - #[error("Could not give the PCI device ID back")] - PutPciDeviceId(#[source] pci::PciRootError), + /// Could not free the PCI device ID. + #[error("Could not free PCI device ID")] + FreePciDeviceId(#[source] pci::PciRootError), /// No disk path was specified when one was expected #[error("No disk path was specified when one was expected")] @@ -4892,8 +4892,8 @@ impl DeviceManager { .pci_bus .lock() .unwrap() - .put_device_id(device_id as usize) - .map_err(DeviceManagerError::PutPciDeviceId)?; + .free_device_id(device_id) + .map_err(DeviceManagerError::FreePciDeviceId)?; let (pci_device_handle, id) = { // Remove the device from the device tree along with its children. From 67cf328a9e241fc6d12dd96950f1cb3e2deb6aca Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 16 Apr 2026 23:11:21 +0100 Subject: [PATCH 1287/1893] tests: Speed up test_pci_device_id() This test was taking > 300s due to SSH backoffs. Signed-off-by: Rob Bradford --- cloud-hypervisor/tests/integration.rs | 88 +++++++++++++++------------ test_infra/src/lib.rs | 2 +- 2 files changed, 50 insertions(+), 40 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 8657edc3bd..195ca9665b 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5747,22 +5747,26 @@ mod common_parallel { let (_, _, first_free_device_id, _) = bdf_from_hotplug_response(output.as_str()); assert_ne!(first_free_device_id, 0); - // We expect a match from grep - let _ = String::from( - guest - .ssh_command(&format!( - "lspci -n | grep \"00:{first_free_device_id:02x}.0\"" - )) - .unwrap() - .trim(), - ); + // Wait for the hotplugged device to appear in the guest + assert!(wait_until(Duration::from_secs(10), || { + ssh_command_ip_with_auth( + &format!("lspci -n | grep \"00:{first_free_device_id:02x}.0\""), + &default_guest_auth(), + &guest.network.guest_ip0, + Some(Duration::from_secs(1)), + ) + .is_ok() + })); // Calculate the succeeding device ID let device_id_to_allocate = first_free_device_id + 1; - // We expect the succeeding device ID to be free + // We expect the succeeding device ID to be free (single attempt, no retries) assert!(matches!( - guest.ssh_command(&format!( - "lspci -n | grep \"00:{device_id_to_allocate:02x}.0\"" - )), + ssh_command_ip_with_auth( + &format!("lspci -n | grep \"00:{device_id_to_allocate:02x}.0\""), + &default_guest_auth(), + &guest.network.guest_ip0, + Some(Duration::from_secs(1)), + ), Err(SshCommandError::NonZeroExitStatus(1)) )); @@ -5783,26 +5787,31 @@ mod common_parallel { let output = String::from_utf8(cmd_stdout).expect("should work"); let (_, _, allocated_device_id, _) = bdf_from_hotplug_response(output.as_str()); assert_eq!(device_id_to_allocate, allocated_device_id); - // Check that the device ID is really in use - let _ = String::from( - guest - .ssh_command(&format!( - "lspci -n | grep \"00:{allocated_device_id:02x}.0\"" - )) - .unwrap() - .trim(), - ); + // Wait for the hotplugged device to appear in the guest + assert!(wait_until(Duration::from_secs(10), || { + ssh_command_ip_with_auth( + &format!("lspci -n | grep \"00:{allocated_device_id:02x}.0\""), + &default_guest_auth(), + &guest.network.guest_ip0, + Some(Duration::from_secs(1)), + ) + .is_ok() + })); // Remove the first device to create a hole let cmd_success = remote_command(&api_socket, "remove-device", Some("test0")); assert!(cmd_success); - thread::sleep(std::time::Duration::new(5, 0)); - // We left a hole in the used PCI IDs. The guest sees no device on the respective ID - assert!(matches!( - guest.ssh_command(&format!( - "lspci -n | grep \"00:{first_free_device_id:02x}.0\"" - )), - Err(SshCommandError::NonZeroExitStatus(1)) - )); + // Wait for the device to disappear from the guest + assert!(wait_until(Duration::from_secs(10), || { + matches!( + ssh_command_ip_with_auth( + &format!("lspci -n | grep \"00:{first_free_device_id:02x}.0\""), + &default_guest_auth(), + &guest.network.guest_ip0, + Some(Duration::from_secs(1)), + ), + Err(SshCommandError::NonZeroExitStatus(1)) + ) + })); // Reuse the device ID hole by dynamically coalescing with the first free ID let (cmd_success, cmd_stdout, _) = remote_command_w_output( &api_socket, @@ -5821,15 +5830,16 @@ mod common_parallel { let (_, _, allocated_device_id, _) = bdf_from_hotplug_response(output.as_str()); assert_eq!(first_free_device_id, allocated_device_id); - // Check that guest sees the same device again at the same BDF - let _ = String::from( - guest - .ssh_command(&format!( - "lspci -n | grep \"00:{allocated_device_id:02x}.0\"" - )) - .unwrap() - .trim(), - ); + // Wait for the re-added device to appear in the guest + assert!(wait_until(Duration::from_secs(10), || { + ssh_command_ip_with_auth( + &format!("lspci -n | grep \"00:{allocated_device_id:02x}.0\""), + &default_guest_auth(), + &guest.network.guest_ip0, + Some(Duration::from_secs(1)), + ) + .is_ok() + })); }); kill_child(&mut child); diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index b39e5a0842..42acca1905 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -702,7 +702,7 @@ pub enum WaitForSshError { }, } -fn default_guest_auth() -> PasswordAuth { +pub fn default_guest_auth() -> PasswordAuth { PasswordAuth { username: String::from("cloud"), password: String::from("cloud123"), From e5dbf5242e25eda3310787fd698ba64bd7534b96 Mon Sep 17 00:00:00 2001 From: Vincent Thomas Date: Thu, 9 Apr 2026 14:46:08 +0000 Subject: [PATCH 1288/1893] 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 --- virtio-devices/src/device.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index d1b9257995..3e6b30e796 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -340,7 +340,13 @@ impl Pausable for VirtioCommon { "Pausing virtio-{}", VirtioDeviceType::from(self.device_type) ); - self.paused.store(true, Ordering::SeqCst); + + // If already paused, return early to avoid deadlock waiting on barrier + // for worker threads that are already parked. + if self.paused.swap(true, Ordering::SeqCst) { + return Ok(()); + } + if let Some(pause_evt) = &self.pause_evt { pause_evt .write(1) From fd8ded9d787a67843fd5c3d46c03f4e1c6afa042 Mon Sep 17 00:00:00 2001 From: Vincent Thomas Date: Thu, 9 Apr 2026 14:45:12 +0000 Subject: [PATCH 1289/1893] 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 --- block/src/raw_async.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 90332aa4b8..7fa3208f42 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause use std::fs::File; -use std::io::Error; +use std::io::{self, Error}; +use std::os::unix::fs::FileTypeExt; use std::os::unix::io::{AsRawFd, RawFd}; use io_uring::{IoUring, opcode, types}; @@ -68,9 +69,30 @@ impl disk_file::SparseCapable for RawFileDisk { impl disk_file::Resizable for RawFileDisk { fn resize(&mut self, size: u64) -> BlockResult<()> { - self.file - .set_len(size) - .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e))) + 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 -c, 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))) + } } } From 11a86fee3e3398f2c75ee638d076c4f0e76db2da Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 16 Apr 2026 13:54:57 +0200 Subject: [PATCH 1290/1893] 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 --- cloud-hypervisor/tests/integration.rs | 90 +++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 195ca9665b..0e59acbe70 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1309,6 +1309,96 @@ mod common_parallel { .expect("Failed to read back data after discard stress"); }); } + + #[test] + fn test_virtio_block_qcow2_uefi_direct_io() { + // Regression test for #8007. + // Place the QCOW2 OS image on a 4096 byte sector filesystem so + // O_DIRECT forces 4096 byte alignment on all I/O buffers. + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = edk2_path(); + + let mut workloads_path = dirs::home_dir().unwrap(); + workloads_path.push("workloads"); + let img_dir = TempDir::new_in(workloads_path.as_path()).unwrap(); + let fs_img_path = img_dir.as_path().join("fs_4ksec.img"); + + assert!( + exec_host_command_output(&format!("truncate -s 4G {}", fs_img_path.to_str().unwrap())) + .status + .success(), + "truncate failed" + ); + + let loop_dev_path = create_loop_device(fs_img_path.to_str().unwrap(), 4096, 5); + + assert!( + exec_host_command_output(&format!("mkfs.ext4 -q {loop_dev_path}")) + .status + .success(), + "mkfs.ext4 failed" + ); + + let mnt_dir = img_dir.as_path().join("mnt"); + fs::create_dir_all(&mnt_dir).unwrap(); + assert!( + exec_host_command_output(&format!( + "mount {} {}", + &loop_dev_path, + mnt_dir.to_str().unwrap() + )) + .status + .success(), + "mount failed" + ); + + let src_qcow2 = guest.disk_config.disk(DiskType::OperatingSystem).unwrap(); + let dest_qcow2 = mnt_dir.join("os.qcow2"); + assert!( + exec_host_command_output(&format!( + "cp {} {}", + &src_qcow2, + dest_qcow2.to_str().unwrap() + )) + .status + .success(), + "cp failed" + ); + + let mut child = GuestCommand::new(&guest) + .default_cpus() + .default_memory() + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args([ + "--disk", + &format!( + "path={},direct=on,image_type=qcow2", + dest_qcow2.to_str().unwrap() + ), + &format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + let _ = exec_host_command_output(&format!("umount {}", mnt_dir.to_str().unwrap())); + let _ = exec_host_command_output(&format!("losetup -d {loop_dev_path}")); + + handle_child_output(r, &output); + } + #[test] fn test_virtio_block_qcow2_dirty_bit_unclean_shutdown() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string()); From 854b6862937d4d310335df065dabd88d4bfb39c7 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 16 Apr 2026 22:40:42 +0200 Subject: [PATCH 1291/1893] 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 --- block/src/qcow_async.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index e65a4ac757..16272f4965 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -630,7 +630,7 @@ mod unit_tests { use super::*; use crate::disk_file::AsyncDiskFile; use crate::qcow::{QcowFile, RawFile}; - use crate::{BatchRequest, RequestType}; + use crate::{BatchRequest, RequestType, SECTOR_SIZE}; fn create_disk_with_data( file_size: u64, @@ -995,4 +995,18 @@ mod unit_tests { ); } } + + #[test] + fn test_qcow_async_alignment_without_direct_io() { + let file_size = 100 * 1024 * 1024; + let temp_file = TempFile::new().unwrap(); + { + let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + QcowFile::new(raw_file, 3, file_size, true).unwrap(); + } + let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) + .unwrap(); + let async_io = disk.new_async_io(1).unwrap(); + assert_eq!(async_io.alignment(), SECTOR_SIZE); + } } From dd79b1899d657c0c18b1a9ac5bf34039677ee965 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 16 Apr 2026 22:41:53 +0200 Subject: [PATCH 1292/1893] 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 --- block/src/qcow_async.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 16272f4965..390b863e9d 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -1009,4 +1009,27 @@ mod unit_tests { let async_io = disk.new_async_io(1).unwrap(); assert_eq!(async_io.alignment(), SECTOR_SIZE); } + + /// Returns None if O_DIRECT is not supported (e.g. tmpfs). + fn try_create_direct_io_disk(temp_file: &TempFile, file_size: u64) -> Option { + { + let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + QcowFile::new(raw_file, 3, file_size, true).unwrap(); + } + QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), true, false, true).ok() + } + + #[test] + fn test_qcow_async_alignment_with_direct_io() { + let temp_file = TempFile::new().unwrap(); + let disk = match try_create_direct_io_disk(&temp_file, 100 * 1024 * 1024) { + Some(d) => d, + None => { + eprintln!("skipping: O_DIRECT not supported on this filesystem"); + return; + } + }; + let async_io = disk.new_async_io(1).unwrap(); + assert!(async_io.alignment() >= SECTOR_SIZE); + } } From e67195ce481c182887306852992e869b6fe1cfe5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 16 Apr 2026 22:44:57 +0200 Subject: [PATCH 1293/1893] 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 --- block/src/qcow_async.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 390b863e9d..cbcd620422 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -1032,4 +1032,25 @@ mod unit_tests { let async_io = disk.new_async_io(1).unwrap(); assert!(async_io.alignment() >= SECTOR_SIZE); } + + #[test] + fn test_qcow_async_sub_sector_read_with_direct_io() { + let temp_file = TempFile::new().unwrap(); + let disk = match try_create_direct_io_disk(&temp_file, 100 * 1024 * 1024) { + Some(d) => d, + None => { + eprintln!("skipping: O_DIRECT not supported on this filesystem"); + return; + } + }; + + let pattern = vec![0xAB; 65536]; + async_write(&disk, 0, &pattern); + + let buf = async_read(&disk, 0, 512); + assert!( + buf.iter().all(|&b| b == 0xAB), + "sub-sector O_DIRECT read should return written data" + ); + } } From 98c533a501d5eff51bf16bb1a78b043f86450626 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 16 Apr 2026 22:46:38 +0200 Subject: [PATCH 1294/1893] 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 --- block/src/qcow_async.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index cbcd620422..2794b4235f 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -1053,4 +1053,22 @@ mod unit_tests { "sub-sector O_DIRECT read should return written data" ); } + + #[test] + fn test_qcow_async_direct_io_write_read_roundtrip() { + let temp_file = TempFile::new().unwrap(); + let disk = match try_create_direct_io_disk(&temp_file, 100 * 1024 * 1024) { + Some(d) => d, + None => { + eprintln!("skipping: O_DIRECT not supported on this filesystem"); + return; + } + }; + + let pattern: Vec = (0..128 * 1024).map(|i| (i % 251) as u8).collect(); + async_write(&disk, 0, &pattern); + + let buf = async_read(&disk, 0, pattern.len()); + assert_eq!(buf, pattern, "O_DIRECT roundtrip should match"); + } } From 4772235952b87190a274713df8e6d0edd8093357 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 16 Apr 2026 18:01:34 +0200 Subject: [PATCH 1295/1893] 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 --- block/src/qcow_async.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 2794b4235f..308c2e27d0 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -6,7 +6,7 @@ //! QCOW2 async disk backend. -use std::cmp::min; +use std::cmp::{max, min}; use std::collections::VecDeque; use std::fs::File; use std::io::Error; @@ -30,7 +30,7 @@ use crate::qcow_common::{ AlignedBuf, aligned_pread, aligned_pwrite, gather_from_iovecs_into, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; -use crate::{BatchRequest, RequestType, disk_file}; +use crate::{BatchRequest, RequestType, SECTOR_SIZE, disk_file}; /// Device level handle for a QCOW2 image. /// @@ -175,6 +175,8 @@ pub struct QcowAsync { sparse: bool, /// O_DIRECT alignment requirement (0 = no alignment needed). alignment: usize, + /// I/O alignment for the AsyncIo trait (at least SECTOR_SIZE). + io_alignment: u64, io_uring: IoUring, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, @@ -189,6 +191,7 @@ impl QcowAsync { ring_depth: u32, ) -> io::Result { let alignment = data_file.file().alignment(); + let io_alignment = max(alignment as u64, SECTOR_SIZE); let io_uring = IoUring::new(ring_depth)?; let eventfd = EventFd::new(libc::EFD_NONBLOCK)?; io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?; @@ -199,6 +202,7 @@ impl QcowAsync { backing_file, sparse, alignment, + io_alignment, io_uring, eventfd, completion_list: VecDeque::new(), @@ -367,6 +371,10 @@ impl AsyncIo for QcowAsync { true } + fn alignment(&self) -> u64 { + self.io_alignment + } + fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> { let (submitter, mut sq, _) = self.io_uring.split(); let mut needs_submit = false; @@ -464,7 +472,15 @@ impl QcowAsync { .map_clusters_for_read(address, total_len, has_backing) .map_err(AsyncIoError::ReadVectored)?; - if mappings.len() == 1 + // The fast path returns a host offset so the caller can submit a + // single io_uring readv with the original iovecs. This only works + // without O_DIRECT because it requires I/O + // size and file offset to be multiples of the device sector size. + // Guest requests can be smaller (e.g. 512 byte UEFI reads on a + // 4096 byte sector device), so O_DIRECT reads fall through to the + // alignment aware synchronous path instead. + if alignment == 0 + && mappings.len() == 1 && let ClusterReadMapping::Allocated { offset: host_offset, length, From e38f00644d247894442402e098435fcccf5ce428 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 17 Apr 2026 09:23:12 +0200 Subject: [PATCH 1296/1893] 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 --- cloud-hypervisor/tests/integration.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 0e59acbe70..0dc7a8d64d 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -3529,12 +3529,7 @@ mod common_parallel { "truncate failed" ); - let loop_dev = exec_host_command_output(&format!( - "losetup --find --show --sector-size 4096 {}", - fs_img_path.to_str().unwrap() - )); - assert!(loop_dev.status.success(), "losetup failed"); - let loop_dev_path = String::from_utf8_lossy(&loop_dev.stdout).trim().to_string(); + let loop_dev_path = create_loop_device(fs_img_path.to_str().unwrap(), 4096, 5); assert!( exec_host_command_output(&format!("mkfs.ext4 -q {loop_dev_path}")) From ea64d109c72da739590135042ab9b609c9b3814a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 17 Apr 2026 08:51:28 +0100 Subject: [PATCH 1297/1893] 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 --- virtio-devices/src/device.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 3e6b30e796..ca2e5834e5 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -16,6 +16,7 @@ use std::thread; use anyhow::anyhow; use libc::EFD_NONBLOCK; use log::{error, info, warn}; +use virtio_bindings::virtio_config::VIRTIO_F_ACCESS_PLATFORM; use virtio_queue::Queue; use vm_device::UserspaceMapping; use vm_memory::{GuestAddress, GuestMemoryAtomic}; @@ -332,6 +333,15 @@ impl VirtioCommon { // requires the addresses held by the descriptors to be translated. self.avail_features &= !(1 << VIRTIO_F_RING_INDIRECT_DESC); } + + /// Returns the access platform only if the feature has been acked. + pub fn access_platform(&self) -> Option> { + if self.feature_acked(VIRTIO_F_ACCESS_PLATFORM as u64) { + self.access_platform.clone() + } else { + None + } + } } impl Pausable for VirtioCommon { From 81ee260ac3336c32242b8b9872c7400d5ec76478 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 17 Apr 2026 08:56:44 +0100 Subject: [PATCH 1298/1893] 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 --- virtio-devices/src/block.rs | 2 +- virtio-devices/src/console.rs | 2 +- virtio-devices/src/net.rs | 4 ++-- virtio-devices/src/pmem.rs | 2 +- virtio-devices/src/rng.rs | 2 +- virtio-devices/src/vdpa.rs | 6 +++--- virtio-devices/src/vsock/device.rs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index bd740fc4a5..961ab9fdec 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -1140,7 +1140,7 @@ impl VirtioDevice for Block { .map(|r| r.new_handle()) .transpose() .unwrap(), - access_platform: self.common.access_platform.clone(), + access_platform: self.common.access_platform(), host_cpus: self.queue_affinity.get(&queue_idx).cloned(), acked_features: self.common.acked_features, disable_sector0_writes: self.disable_sector0_writes, diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index d2d57b9e50..b7519626d3 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -751,7 +751,7 @@ impl VirtioDevice for Console { self.resize_pipe.as_ref().map(|p| p.try_clone().unwrap()), kill_evt, pause_evt, - self.common.access_platform.clone(), + self.common.access_platform(), ); let paused = self.common.paused.clone(); diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index ed8c05eeb8..7c50b4f8b8 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -765,7 +765,7 @@ impl VirtioDevice for Net { ctrl_q: CtrlQueue::new(self.taps.clone()), queue: ctrl_queue, queue_evt: ctrl_queue_evt, - access_platform: self.common.access_platform.clone(), + access_platform: self.common.access_platform(), queue_index: ctrl_queue_index as u16, interrupt_cb: interrupt_cb.clone(), }; @@ -837,7 +837,7 @@ impl VirtioDevice for Net { rx_desc_avail: false, rx_rate_limiter, tx_rate_limiter, - access_platform: self.common.access_platform.clone(), + access_platform: self.common.access_platform(), }, mem: mem.clone(), queue_index_base: (i * 2) as u16, diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 3abec1c0f0..91e0f65797 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -404,7 +404,7 @@ impl VirtioDevice for Pmem { queue_evt, kill_evt, pause_evt, - access_platform: self.common.access_platform.clone(), + access_platform: self.common.access_platform(), }; let paused = self.common.paused.clone(); diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 6bb0269c5e..e8fd5b3742 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -273,7 +273,7 @@ impl VirtioDevice for Rng { queue_evt, kill_evt, pause_evt, - access_platform: self.common.access_platform.clone(), + access_platform: self.common.access_platform(), }; let paused = self.common.paused.clone(); diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index a35c35eb56..ff7305395e 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -251,21 +251,21 @@ impl Vdpa { desc_table_addr: queue .desc_table() .translate_gpa( - self.common.access_platform.as_deref(), + self.common.access_platform().as_deref(), queue_size as usize * std::mem::size_of::(), ) .map_err(Error::TranslateAddress)?, used_ring_addr: queue .used_ring() .translate_gpa( - self.common.access_platform.as_deref(), + self.common.access_platform().as_deref(), 4 + queue_size as usize * 8, ) .map_err(Error::TranslateAddress)?, avail_ring_addr: queue .avail_ring() .translate_gpa( - self.common.access_platform.as_deref(), + self.common.access_platform().as_deref(), 4 + queue_size as usize * 2, ) .map_err(Error::TranslateAddress)?, diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 25412503af..dd877fa55f 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -466,7 +466,7 @@ where pause_evt, interrupt_cb, backend: self.backend.clone(), - access_platform: self.common.access_platform.clone(), + access_platform: self.common.access_platform(), }; let paused = self.common.paused.clone(); From 636da215e3b594f452c670ceeeb5d89fc6cad8f4 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 17 Apr 2026 09:10:12 +0100 Subject: [PATCH 1299/1893] 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 --- virtio-devices/src/device.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index ca2e5834e5..89e1ee2eaf 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -188,6 +188,12 @@ pub trait VirtioDevice: Send { /// Set the access platform trait to let the device perform address /// translations if needed. fn set_access_platform(&mut self, _access_platform: Arc) {} + + /// Returns the access platform only if VIRTIO_F_ACCESS_PLATFORM was + /// negotiated with the guest. + fn access_platform(&self) -> Option> { + None + } } /// Trait to define address translation for devices managed by virtio-iommu From 128ee6d105d2594f67a4ed462e0d2e515e048a4d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 17 Apr 2026 09:11:47 +0100 Subject: [PATCH 1300/1893] 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 --- virtio-devices/src/block.rs | 4 ++++ virtio-devices/src/console.rs | 4 ++++ virtio-devices/src/net.rs | 4 ++++ virtio-devices/src/pmem.rs | 4 ++++ virtio-devices/src/rng.rs | 4 ++++ virtio-devices/src/vdpa.rs | 4 ++++ virtio-devices/src/vsock/device.rs | 4 ++++ 7 files changed, 28 insertions(+) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 961ab9fdec..71c0b7ed39 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -1223,6 +1223,10 @@ impl VirtioDevice for Block { fn set_access_platform(&mut self, access_platform: Arc) { self.common.set_access_platform(access_platform); } + + fn access_platform(&self) -> Option> { + self.common.access_platform() + } } impl Pausable for Block { diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index b7519626d3..f141d08b14 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -782,6 +782,10 @@ impl VirtioDevice for Console { fn set_access_platform(&mut self, access_platform: Arc) { self.common.set_access_platform(access_platform); } + + fn access_platform(&self) -> Option> { + self.common.access_platform() + } } impl Pausable for Console { diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 7c50b4f8b8..7ddbccca8e 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -900,6 +900,10 @@ impl VirtioDevice for Net { fn set_access_platform(&mut self, access_platform: Arc) { self.common.set_access_platform(access_platform); } + + fn access_platform(&self) -> Option> { + self.common.access_platform() + } } impl Pausable for Net { diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 91e0f65797..eb94e7104d 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -441,6 +441,10 @@ impl VirtioDevice for Pmem { fn set_access_platform(&mut self, access_platform: Arc) { self.common.set_access_platform(access_platform); } + + fn access_platform(&self) -> Option> { + self.common.access_platform() + } } impl Pausable for Pmem { diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index e8fd5b3742..2f43ac66d5 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -305,6 +305,10 @@ impl VirtioDevice for Rng { fn set_access_platform(&mut self, access_platform: Arc) { self.common.set_access_platform(access_platform); } + + fn access_platform(&self) -> Option> { + self.common.access_platform() + } } impl Pausable for Rng { diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index ff7305395e..f9bf7a39de 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -465,6 +465,10 @@ impl VirtioDevice for Vdpa { fn set_access_platform(&mut self, access_platform: Arc) { self.common.set_access_platform(access_platform); } + + fn access_platform(&self) -> Option> { + self.common.access_platform() + } } impl Pausable for Vdpa { diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index dd877fa55f..0b202945b0 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -501,6 +501,10 @@ where fn set_access_platform(&mut self, access_platform: Arc) { self.common.set_access_platform(access_platform); } + + fn access_platform(&self) -> Option> { + self.common.access_platform() + } } impl Pausable for Vsock From adc5bb79580d15e1266232f5d4a9348fbef83f96 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 17 Apr 2026 09:14:44 +0100 Subject: [PATCH 1301/1893] 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 --- .../src/transport/pci_common_config.rs | 104 +++++++----------- virtio-devices/src/transport/pci_device.rs | 28 ++--- vmm/src/device_manager.rs | 2 +- 3 files changed, 50 insertions(+), 84 deletions(-) diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 11e1d3ac70..379622bd97 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -14,7 +14,6 @@ use log::{debug, error, warn}; use serde::{Deserialize, Serialize}; use virtio_queue::{Queue, QueueT}; use vm_migration::{MigratableError, Pausable, Snapshot, Snapshottable}; -use vm_virtio::AccessPlatform; use super::pci_device::VIRTQ_MSI_NO_VECTOR; use crate::VirtioDevice; @@ -125,7 +124,7 @@ pub fn get_vring_size(t: VringType, queue_size: u16) -> u64 { /// le64 queue_avail; // 0x28 // read-write /// le64 queue_used; // 0x30 // read-write pub struct VirtioPciCommonConfig { - pub access_platform: Option>, + pub device: Arc>, pub driver_status: Arc, pub config_generation: u8, pub device_feature_select: u32, @@ -136,12 +135,9 @@ pub struct VirtioPciCommonConfig { } impl VirtioPciCommonConfig { - pub fn new( - state: VirtioPciCommonConfigState, - access_platform: Option>, - ) -> Self { + pub fn new(state: VirtioPciCommonConfigState, device: Arc>) -> Self { VirtioPciCommonConfig { - access_platform, + device, driver_status: Arc::new(AtomicU8::new(state.driver_status)), config_generation: state.config_generation, device_feature_select: state.device_feature_select, @@ -164,13 +160,7 @@ impl VirtioPciCommonConfig { } } - pub fn read( - &mut self, - offset: u64, - data: &mut [u8], - queues: &[Queue], - device: Arc>, - ) { + pub fn read(&mut self, offset: u64, data: &mut [u8], queues: &[Queue]) { assert!(data.len() <= 8); match data.len() { @@ -183,7 +173,7 @@ impl VirtioPciCommonConfig { LittleEndian::write_u16(data, v); } 4 => { - let v = self.read_common_config_dword(offset, device); + let v = self.read_common_config_dword(offset); LittleEndian::write_u32(data, v); } 8 => { @@ -194,26 +184,14 @@ impl VirtioPciCommonConfig { } } - #[allow(clippy::needless_pass_by_value)] - pub fn write( - &mut self, - offset: u64, - data: &[u8], - queues: &mut [Queue], - device: Arc>, - ) { + pub fn write(&mut self, offset: u64, data: &[u8], queues: &mut [Queue]) { assert!(data.len() <= 8); match data.len() { 1 => self.write_common_config_byte(offset, data[0]), 2 => self.write_common_config_word(offset, LittleEndian::read_u16(data), queues), 4 => { - self.write_common_config_dword( - offset, - LittleEndian::read_u32(data), - queues, - device, - ); + self.write_common_config_dword(offset, LittleEndian::read_u32(data), queues); } 8 => self.write_common_config_qword(offset, LittleEndian::read_u64(data), queues), _ => error!("invalid data length for virtio write: len {}", data.len()), @@ -285,8 +263,12 @@ impl VirtioPciCommonConfig { 0x1c => self.with_queue_mut(queues, |q| { let ready = value == 1; q.set_ready(ready); - // Translate address of descriptor table and vrings. - if ready && let Some(access_platform) = &self.access_platform { + let access_platform = if ready { + self.device.lock().unwrap().access_platform() + } else { + None + }; + if let Some(access_platform) = access_platform { let desc_table = match access_platform .translate_gva(q.desc_table(), get_vring_size(VringType::Desc, q.size())) { @@ -337,15 +319,12 @@ impl VirtioPciCommonConfig { } } - #[allow(clippy::needless_pass_by_value)] - fn read_common_config_dword(&self, offset: u64, device: Arc>) -> u32 { + fn read_common_config_dword(&self, offset: u64) -> u32 { debug!("read_common_config_dword: offset 0x{offset:x}"); match offset { 0x00 => self.device_feature_select, 0x04 => { - let locked_device = device.lock().unwrap(); - // Only 64 bits of features (2 pages) are defined for now, so limit - // device_feature_select to avoid shifting by 64 or more bits. + let locked_device = self.device.lock().unwrap(); if self.device_feature_select < 2 { (locked_device.features() >> (self.device_feature_select * 32)) as u32 } else { @@ -360,14 +339,7 @@ impl VirtioPciCommonConfig { } } - #[allow(clippy::needless_pass_by_value)] - fn write_common_config_dword( - &mut self, - offset: u64, - value: u32, - queues: &mut [Queue], - device: Arc>, - ) { + fn write_common_config_dword(&mut self, offset: u64, value: u32, queues: &mut [Queue]) { debug!("write_common_config_dword: offset 0x{offset:x}"); match offset { @@ -375,7 +347,7 @@ impl VirtioPciCommonConfig { 0x08 => self.driver_feature_select = value, 0x0c => { if self.driver_feature_select < 2 { - let mut locked_device = device.lock().unwrap(); + let mut locked_device = self.device.lock().unwrap(); locked_device .ack_features(u64::from(value) << (self.driver_feature_select * 32)); } @@ -472,8 +444,9 @@ mod unit_tests { #[test] fn write_base_regs() { + let dev: Arc> = Arc::new(Mutex::new(DummyDevice(0))); let mut regs = VirtioPciCommonConfig { - access_platform: None, + device: dev.clone(), driver_status: Arc::new(AtomicU8::new(0xaa)), config_generation: 0x55, device_feature_select: 0x0, @@ -483,72 +456,69 @@ mod unit_tests { msix_queues: Arc::new(Mutex::new(vec![0; 3])), }; - let dev = Arc::new(Mutex::new(DummyDevice(0))); let mut queues = Vec::new(); // Can set all bits of driver_status. - regs.write(0x14, &[0x55], &mut queues, dev.clone()); + regs.write(0x14, &[0x55], &mut queues); let mut read_back = vec![0x00]; - regs.read(0x14, &mut read_back, &queues, dev.clone()); + regs.read(0x14, &mut read_back, &queues); assert_eq!(read_back[0], 0x55); // The config generation register is read only. - regs.write(0x15, &[0xaa], &mut queues, dev.clone()); + regs.write(0x15, &[0xaa], &mut queues); let mut read_back = vec![0x00]; - regs.read(0x15, &mut read_back, &queues, dev.clone()); + regs.read(0x15, &mut read_back, &queues); assert_eq!(read_back[0], 0x55); // Device features is read-only and passed through from the device. - regs.write(0x04, &[0, 0, 0, 0], &mut queues, dev.clone()); + regs.write(0x04, &[0, 0, 0, 0], &mut queues); let mut read_back = vec![0, 0, 0, 0]; - regs.read(0x04, &mut read_back, &queues, dev.clone()); + regs.read(0x04, &mut read_back, &queues); assert_eq!(LittleEndian::read_u32(&read_back), DUMMY_FEATURES as u32); // Feature select registers are read/write. - regs.write(0x00, &[1, 2, 3, 4], &mut queues, dev.clone()); + regs.write(0x00, &[1, 2, 3, 4], &mut queues); let mut read_back = vec![0, 0, 0, 0]; - regs.read(0x00, &mut read_back, &queues, dev.clone()); + regs.read(0x00, &mut read_back, &queues); assert_eq!(LittleEndian::read_u32(&read_back), 0x0403_0201); - regs.write(0x08, &[1, 2, 3, 4], &mut queues, dev.clone()); + regs.write(0x08, &[1, 2, 3, 4], &mut queues); let mut read_back = vec![0, 0, 0, 0]; - regs.read(0x08, &mut read_back, &queues, dev.clone()); + regs.read(0x08, &mut read_back, &queues); assert_eq!(LittleEndian::read_u32(&read_back), 0x0403_0201); // 'queue_select' can be read and written. - regs.write(0x16, &[0xaa, 0x55], &mut queues, dev.clone()); + regs.write(0x16, &[0xaa, 0x55], &mut queues); let mut read_back = vec![0x00, 0x00]; - regs.read(0x16, &mut read_back, &queues, dev); + regs.read(0x16, &mut read_back, &queues); assert_eq!(read_back[0], 0xaa); assert_eq!(read_back[1], 0x55); } #[test] fn oob_queue_select_does_not_panic() { - // Regression test: reading/writing queue_msix_vector (offset 0x1a) - // with an out-of-bounds queue_select must not panic. + let dev: Arc> = Arc::new(Mutex::new(DummyDevice(0))); let mut regs = VirtioPciCommonConfig { - access_platform: None, + device: dev.clone(), driver_status: Arc::new(AtomicU8::new(0)), config_generation: 0, device_feature_select: 0, driver_feature_select: 0, queue_select: 0, msix_config: Arc::new(AtomicU16::new(0)), - msix_queues: Arc::new(Mutex::new(vec![0; 1])), // only 1 queue + msix_queues: Arc::new(Mutex::new(vec![0; 1])), }; - let dev = Arc::new(Mutex::new(DummyDevice(0))); let mut queues = vec![Queue::new(256).unwrap()]; // Set queue_select to an out-of-bounds value. - regs.write(0x16, &[0xFF, 0xFF], &mut queues, dev.clone()); + regs.write(0x16, &[0xFF, 0xFF], &mut queues); // Read queue_msix_vector — must not panic, should return VIRTQ_MSI_NO_VECTOR. let mut read_back = vec![0x00, 0x00]; - regs.read(0x1a, &mut read_back, &queues, dev.clone()); + regs.read(0x1a, &mut read_back, &queues); assert_eq!(LittleEndian::read_u16(&read_back), VIRTQ_MSI_NO_VECTOR); // Write queue_msix_vector — must not panic. - regs.write(0x1a, &[0xAB, 0xCD], &mut queues, dev); + regs.write(0x1a, &[0xAB, 0xCD], &mut queues); } } diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 54a29caa5d..98abb04935 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -404,7 +404,7 @@ impl VirtioPciDevice { memory: GuestMemoryAtomic, device: Arc>, msix_num: u16, - access_platform: Option>, + access_platform: Option<&Arc>, interrupt_manager: &dyn InterruptManager, pci_device_bdf: u32, activate_evt: EventFd, @@ -422,7 +422,7 @@ impl VirtioPciDevice { } let num_queues = locked_device.queue_max_sizes().len(); - if let Some(access_platform) = &access_platform { + if let Some(access_platform) = access_platform { locked_device.set_access_platform(access_platform.clone()); } @@ -518,7 +518,7 @@ impl VirtioPciDevice { })?; let common_config = if let Some(common_config_state) = common_config_state { - VirtioPciCommonConfig::new(common_config_state, access_platform) + VirtioPciCommonConfig::new(common_config_state, device.clone()) } else { VirtioPciCommonConfig::new( VirtioPciCommonConfigState { @@ -530,7 +530,7 @@ impl VirtioPciDevice { msix_config: VIRTQ_MSI_NO_VECTOR, msix_queues: vec![VIRTQ_MSI_NO_VECTOR; num_queues], }, - access_platform, + device.clone(), ) }; @@ -1139,12 +1139,10 @@ impl PciDevice for VirtioPciDevice { fn read_bar(&mut self, _base: u64, offset: u64, data: &mut [u8]) { match offset { - o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => self.common_config.read( - o - COMMON_CONFIG_BAR_OFFSET, - data, - &self.queues, - self.device.clone(), - ), + o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => { + self.common_config + .read(o - COMMON_CONFIG_BAR_OFFSET, data, &self.queues); + } o if (ISR_CONFIG_BAR_OFFSET..ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE).contains(&o) => { if let Some(v) = data.get_mut(0) { // Reading this register resets it to 0. @@ -1185,12 +1183,10 @@ impl PciDevice for VirtioPciDevice { fn write_bar(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { let initial_ready = self.is_driver_ready(); match offset { - o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => self.common_config.write( - o - COMMON_CONFIG_BAR_OFFSET, - data, - &mut self.queues, - self.device.clone(), - ), + o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => { + self.common_config + .write(o - COMMON_CONFIG_BAR_OFFSET, data, &mut self.queues); + } o if (ISR_CONFIG_BAR_OFFSET..ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE).contains(&o) => { if let Some(v) = data.first() { self.interrupt_status diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 9f65784ecb..eec0da83c6 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4405,7 +4405,7 @@ impl DeviceManager { memory, virtio_device, msix_num, - access_platform, + access_platform.as_ref(), self.msi_interrupt_manager.as_ref(), pci_device_bdf.into(), self.activate_evt From 0eca4d7c68562876a1bbc7421a464ba2d2322be5 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 17 Apr 2026 09:52:06 +0100 Subject: [PATCH 1302/1893] 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 --- virtio-devices/src/block.rs | 4 ++-- virtio-devices/src/console.rs | 4 ++-- virtio-devices/src/net.rs | 12 ++++++------ virtio-devices/src/pmem.rs | 4 ++-- virtio-devices/src/rng.rs | 4 ++-- virtio-devices/src/vhost_user/blk.rs | 8 ++++---- virtio-devices/src/vhost_user/fs.rs | 8 ++++---- virtio-devices/src/vhost_user/generic_vhost_user.rs | 8 ++++---- virtio-devices/src/vhost_user/net.rs | 8 ++++---- virtio-devices/src/vsock/device.rs | 4 ++-- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 71c0b7ed39..2e28c60da5 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -734,7 +734,7 @@ impl Block { mut disk_image: DiskBackend, disk_path: PathBuf, read_only: bool, - iommu: bool, + access_platform_enabled: bool, num_queues: usize, queue_size: u16, serial: Option, @@ -793,7 +793,7 @@ impl Block { warn!("sparse=on requested but backend does not support sparse operations"); } - if iommu { + if access_platform_enabled { avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index f141d08b14..0a4cf65cff 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -602,7 +602,7 @@ impl Console { id: String, endpoint: Endpoint, resize_pipe: Option, - iommu: bool, + access_platform_enabled: bool, seccomp_action: SeccompAction, exit_evt: EventFd, state: Option, @@ -619,7 +619,7 @@ impl Console { ) } else { let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_CONSOLE_F_SIZE); - if iommu { + if access_platform_enabled { avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 7ddbccca8e..d7e1d1f361 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -468,7 +468,7 @@ impl Net { id: String, taps: Vec, guest_mac: Option, - iommu: bool, + access_platform_enabled: bool, num_queues: usize, queue_size: u16, seccomp_action: SeccompAction, @@ -499,7 +499,7 @@ impl Net { | (1 << VIRTIO_RING_F_EVENT_IDX) | (1 << VIRTIO_F_VERSION_1); - if iommu { + if access_platform_enabled { avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } @@ -587,7 +587,7 @@ impl Net { guest_mac: Option, host_mac: &mut Option, mtu: Option, - iommu: bool, + access_platform_enabled: bool, num_queues: usize, queue_size: u16, seccomp_action: SeccompAction, @@ -613,7 +613,7 @@ impl Net { id, taps, guest_mac, - iommu, + access_platform_enabled, num_queues, queue_size, seccomp_action, @@ -632,7 +632,7 @@ impl Net { fds: &[RawFd], guest_mac: Option, mtu: Option, - iommu: bool, + access_platform_enabled: bool, queue_size: u16, seccomp_action: SeccompAction, rate_limiter_config: Option, @@ -666,7 +666,7 @@ impl Net { id, taps, guest_mac, - iommu, + access_platform_enabled, num_queue_pairs * 2, queue_size, seccomp_action, diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index eb94e7104d..94f2716b64 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -287,7 +287,7 @@ impl Pmem { disk: File, addr: GuestAddress, mapping: UserspaceMapping, - iommu: bool, + access_platform_enabled: bool, seccomp_action: SeccompAction, exit_evt: EventFd, state: Option, @@ -308,7 +308,7 @@ impl Pmem { let mut avail_features = 1u64 << VIRTIO_F_VERSION_1; - if iommu { + if access_platform_enabled { avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } (avail_features, 0, config, false) diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 2f43ac66d5..8d11a3d7b6 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -169,7 +169,7 @@ impl Rng { pub fn new( id: String, path: &str, - iommu: bool, + access_platform_enabled: bool, seccomp_action: SeccompAction, exit_evt: EventFd, state: Option, @@ -182,7 +182,7 @@ impl Rng { } else { let mut avail_features = 1u64 << VIRTIO_F_VERSION_1; - if iommu { + if access_platform_enabled { avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 83653147bd..2526b36f6b 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -45,7 +45,7 @@ pub struct Blk { guest_memory: Option>, seccomp_action: SeccompAction, exit_evt: EventFd, - iommu: bool, + access_platform_enabled: bool, } impl Blk { @@ -55,7 +55,7 @@ impl Blk { vu_cfg: VhostUserConfig, seccomp_action: SeccompAction, exit_evt: EventFd, - iommu: bool, + access_platform_enabled: bool, state: Option, ) -> Result { let num_queues = vu_cfg.num_queues; @@ -191,7 +191,7 @@ impl Blk { guest_memory: None, seccomp_action, exit_evt, - iommu, + access_platform_enabled, }) } @@ -217,7 +217,7 @@ impl VirtioDevice for Blk { fn features(&self) -> u64 { let mut features = self.vu_common.virtio_common.avail_features; - if self.iommu { + if self.access_platform_enabled { features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } features diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 509a7a34f3..c062edf19b 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -68,7 +68,7 @@ pub struct Fs { seccomp_action: SeccompAction, guest_memory: Option>, exit_evt: EventFd, - iommu: bool, + access_platform_enabled: bool, } impl Fs { @@ -83,7 +83,7 @@ impl Fs { cache: Option<(VirtioSharedMemoryList, MmapRegion)>, seccomp_action: SeccompAction, exit_evt: EventFd, - iommu: bool, + access_platform_enabled: bool, state: Option, ) -> Result { // Calculate the actual number of queues needed. @@ -200,7 +200,7 @@ impl Fs { seccomp_action, guest_memory: None, exit_evt, - iommu, + access_platform_enabled, }) } @@ -226,7 +226,7 @@ impl VirtioDevice for Fs { fn features(&self) -> u64 { let mut features = self.vu_common.virtio_common.avail_features; - if self.iommu { + if self.access_platform_enabled { features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } features diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index aed24b082d..dda891e912 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -42,7 +42,7 @@ pub struct GenericVhostUser { seccomp_action: SeccompAction, guest_memory: Option>, exit_evt: EventFd, - iommu: bool, + access_platform_enabled: bool, cfg_warning: AtomicBool, } @@ -57,7 +57,7 @@ impl GenericVhostUser { cache: Option<(VirtioSharedMemoryList, MmapRegion)>, seccomp_action: SeccompAction, exit_evt: EventFd, - iommu: bool, + access_platform_enabled: bool, state: Option, ) -> Result { // Calculate the actual number of queues needed. @@ -159,7 +159,7 @@ since the backend only supports {backend_num_queues}\n", seccomp_action, guest_memory: None, exit_evt, - iommu, + access_platform_enabled, cfg_warning: AtomicBool::new(false), }) } @@ -201,7 +201,7 @@ impl VirtioDevice for GenericVhostUser { fn features(&self) -> u64 { let mut features = self.vu_common.virtio_common.avail_features; - if self.iommu { + if self.access_platform_enabled { features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } features diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 4803ade33c..e5f9eda7d8 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -48,7 +48,7 @@ pub struct Net { ctrl_queue_epoll_thread: Option>, seccomp_action: SeccompAction, exit_evt: EventFd, - iommu: bool, + access_platform_enabled: bool, } impl Net { @@ -62,7 +62,7 @@ impl Net { server: bool, seccomp_action: SeccompAction, exit_evt: EventFd, - iommu: bool, + access_platform_enabled: bool, state: Option, offload_tso: bool, offload_ufo: bool, @@ -220,7 +220,7 @@ impl Net { ctrl_queue_epoll_thread: None, seccomp_action, exit_evt, - iommu, + access_platform_enabled, }) } @@ -252,7 +252,7 @@ impl VirtioDevice for Net { fn features(&self) -> u64 { let mut features = self.vu_common.virtio_common.avail_features; - if self.iommu { + if self.access_platform_enabled { features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } features diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 0b202945b0..c20288c2dc 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -339,7 +339,7 @@ where cid: u32, path: PathBuf, mut backend: B, - iommu: bool, + access_platform_enabled: bool, seccomp_action: SeccompAction, exit_evt: EventFd, state: Option, @@ -353,7 +353,7 @@ where } else { let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_F_IN_ORDER); - if iommu { + if access_platform_enabled { avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } (avail_features, 0, false) From 5bd6fdc17d3b4c1bb417b6723f0b9c5503c31eca Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 17 Apr 2026 09:58:33 +0100 Subject: [PATCH 1303/1893] 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 --- vmm/src/device_manager.rs | 32 ++++++++++++++++---------------- vmm/src/vm.rs | 15 ++++++++------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index eec0da83c6..2c31efe336 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1126,8 +1126,8 @@ pub struct DeviceManager { // pvpanic device pvpanic_device: Option>>, - // Flag to force setting the iommu on virtio devices - force_iommu: bool, + // Force VIRTIO_F_ACCESS_PLATFORM on all virtio devices (e.g. for TDX/SEV-SNP) + force_access_platform: bool, // io_uring availability if detected io_uring_supported: Option, @@ -1215,7 +1215,7 @@ impl DeviceManager { seccomp_action: SeccompAction, numa_nodes: NumaNodes, activate_evt: &EventFd, - force_iommu: bool, + force_access_platform: bool, boot_id_list: BTreeSet, #[cfg(not(target_arch = "riscv64"))] timestamp: Instant, snapshot: Option<&Snapshot>, @@ -1429,7 +1429,7 @@ impl DeviceManager { #[cfg(feature = "pvmemcontrol")] pvmemcontrol_devices: None, pvpanic_device: None, - force_iommu, + force_access_platform, io_uring_supported: None, aio_supported: None, boot_id_list, @@ -2440,7 +2440,7 @@ impl DeviceManager { self.console_resize_pipe .as_ref() .map(|p| p.try_clone().unwrap()), - self.force_iommu | console_config.iommu, + self.force_access_platform | console_config.iommu, self.seccomp_action.clone(), self.exit_evt .try_clone() @@ -2703,7 +2703,7 @@ impl DeviceManager { self.exit_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, - self.force_iommu, + self.force_access_platform, state_from_id(self.snapshot.as_ref(), id.as_str()) .map_err(DeviceManagerError::RestoreGetState)?, ) { @@ -2930,7 +2930,7 @@ impl DeviceManager { .ok_or(DeviceManagerError::NoDiskPath)? .clone(), disk_cfg.readonly, - self.force_iommu | disk_cfg.pci_common.iommu, + self.force_access_platform | disk_cfg.pci_common.iommu, disk_cfg.num_queues, disk_cfg.queue_size, disk_cfg.serial.clone(), @@ -3032,7 +3032,7 @@ impl DeviceManager { self.exit_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, - self.force_iommu, + self.force_access_platform, state_from_id(self.snapshot.as_ref(), id.as_str()) .map_err(DeviceManagerError::RestoreGetState)?, net_cfg.offload_tso, @@ -3063,7 +3063,7 @@ impl DeviceManager { Some(net_cfg.mac), &mut net_cfg.host_mac, net_cfg.mtu, - self.force_iommu | net_cfg.pci_common.iommu, + self.force_access_platform | net_cfg.pci_common.iommu, net_cfg.num_queues, net_cfg.queue_size, self.seccomp_action.clone(), @@ -3084,7 +3084,7 @@ impl DeviceManager { fds, Some(net_cfg.mac), net_cfg.mtu, - self.force_iommu | net_cfg.pci_common.iommu, + self.force_access_platform | net_cfg.pci_common.iommu, net_cfg.queue_size, self.seccomp_action.clone(), net_cfg.rate_limiter_config, @@ -3114,7 +3114,7 @@ impl DeviceManager { Some(net_cfg.mac), &mut net_cfg.host_mac, net_cfg.mtu, - self.force_iommu | net_cfg.pci_common.iommu, + self.force_access_platform | net_cfg.pci_common.iommu, net_cfg.num_queues, net_cfg.queue_size, self.seccomp_action.clone(), @@ -3177,7 +3177,7 @@ impl DeviceManager { virtio_devices::Rng::new( id.clone(), rng_path, - self.force_iommu | rng_config.iommu, + self.force_access_platform | rng_config.iommu, self.seccomp_action.clone(), self.exit_evt .try_clone() @@ -3239,7 +3239,7 @@ impl DeviceManager { self.exit_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, - self.force_iommu, + self.force_access_platform, state_from_id(self.snapshot.as_ref(), id.as_str()) .map_err(DeviceManagerError::RestoreGetState)?, ) @@ -3305,7 +3305,7 @@ impl DeviceManager { self.exit_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, - self.force_iommu, + self.force_access_platform, state_from_id(self.snapshot.as_ref(), id.as_str()) .map_err(DeviceManagerError::RestoreGetState)?, ) @@ -3484,7 +3484,7 @@ impl DeviceManager { file, GuestAddress(region_base), mapping, - self.force_iommu | pmem_cfg.pci_common.iommu, + self.force_access_platform | pmem_cfg.pci_common.iommu, self.seccomp_action.clone(), self.exit_evt .try_clone() @@ -3555,7 +3555,7 @@ impl DeviceManager { vsock_cfg.cid, vsock_cfg.socket.clone(), backend, - self.force_iommu | vsock_cfg.pci_common.iommu, + self.force_access_platform | vsock_cfg.pci_common.iommu, self.seccomp_action.clone(), self.exit_evt .try_clone() diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 6bb088cb0d..ffe7b8624a 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -567,8 +567,8 @@ impl Vm { let numa_nodes = Self::create_numa_nodes(config.lock().unwrap().numa.as_deref(), &memory_manager)?; - // Determine if IOMMU should be forced based on confidential computing features - let force_iommu = Self::should_force_iommu(&config); + // Determine if VIRTIO_F_ACCESS_PLATFORM should be forced (e.g. for TDX/SEV-SNP) + let force_access_platform = Self::should_force_access_platform(&config); let stop_on_boot = Self::should_stop_on_boot(&config); @@ -615,7 +615,7 @@ impl Vm { seccomp_action.clone(), numa_nodes.clone(), &activate_evt, - force_iommu, + force_access_platform, boot_id_list, #[cfg(not(target_arch = "riscv64"))] timestamp, @@ -694,8 +694,9 @@ impl Vm { }) } - /// Determine if IOMMU should be forced based on confidential computing features. - fn should_force_iommu(_config: &Arc>) -> bool { + /// Determine if VIRTIO_F_ACCESS_PLATFORM should be forced based on + /// confidential computing features. + fn should_force_access_platform(_config: &Arc>) -> bool { #[cfg(feature = "tdx")] if _config.lock().unwrap().is_tdx_enabled() { return true; @@ -802,7 +803,7 @@ impl Vm { seccomp_action: SeccompAction, numa_nodes: NumaNodes, activate_evt: &EventFd, - force_iommu: bool, + force_access_platform: bool, boot_id_list: BTreeSet, #[cfg(not(target_arch = "riscv64"))] timestamp: Instant, snapshot: Option<&Snapshot>, @@ -825,7 +826,7 @@ impl Vm { seccomp_action, numa_nodes, activate_evt, - force_iommu, + force_access_platform, boot_id_list, #[cfg(not(target_arch = "riscv64"))] timestamp, From 12dd72d88f100697ec074ee420134ad580d2380d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 17 Apr 2026 09:33:10 +0100 Subject: [PATCH 1304/1893] 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 --- fuzz/fuzz_targets/balloon.rs | 1 + virtio-devices/src/balloon.rs | 33 +++++++++++++++++++++++++++++---- vmm/src/device_manager.rs | 1 + 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/fuzz/fuzz_targets/balloon.rs b/fuzz/fuzz_targets/balloon.rs index 69f0c07e84..20260c5469 100644 --- a/fuzz/fuzz_targets/balloon.rs +++ b/fuzz/fuzz_targets/balloon.rs @@ -49,6 +49,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { BALLOON_SIZE, true, true, + false, SeccompAction::Allow, EventFd::new(EFD_NONBLOCK).unwrap(), None, diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index bb9c46cbc8..f9db09bd33 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -34,14 +34,15 @@ use vm_memory::{ GuestMemoryError, GuestMemoryRegion, }; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; +use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::{ ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, - GuestMemoryMmap, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, - VirtioInterrupt, VirtioInterruptType, + GuestMemoryMmap, VIRTIO_F_ACCESS_PLATFORM, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, + VirtioDeviceType, VirtioInterrupt, VirtioInterruptType, }; const QUEUE_SIZE: u16 = 128; @@ -160,6 +161,7 @@ struct BalloonEpollHandler { kill_evt: EventFd, pause_evt: EventFd, pbp: Option, + access_platform: Option>, } impl BalloonEpollHandler { @@ -277,7 +279,12 @@ impl BalloonEpollHandler { let mut offset = 0u64; while offset < desc.len() as u64 { - let addr = desc.addr().checked_add(offset).unwrap(); + let addr = desc + .addr() + .checked_add(offset) + .unwrap() + .translate_gva(self.access_platform.as_deref(), data_chunk_size) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?; let pfn: u32 = desc_chain .memory() .read_obj(addr) @@ -324,7 +331,11 @@ impl BalloonEpollHandler { let mut descs_len = 0; while let Some(desc) = desc_chain.next() { descs_len += desc.len(); - Self::release_memory_range(desc_chain.memory(), desc.addr(), desc.len() as usize)?; + let addr = desc + .addr() + .translate_gva(self.access_platform.as_deref(), desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?; + Self::release_memory_range(desc_chain.memory(), addr, desc.len() as usize)?; } self.queues[queue_index] @@ -437,11 +448,13 @@ pub struct Balloon { impl Balloon { // Create a new virtio-balloon. + #[allow(clippy::too_many_arguments)] pub fn new( id: String, size: u64, deflate_on_oom: bool, free_page_reporting: bool, + access_platform_enabled: bool, seccomp_action: SeccompAction, exit_evt: EventFd, state: Option, @@ -464,6 +477,9 @@ impl Balloon { if free_page_reporting { avail_features |= 1u64 << VIRTIO_BALLOON_F_REPORTING; } + if access_platform_enabled { + avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; + } let config = VirtioBalloonConfig { num_pages: (size >> VIRTIO_BALLOON_PFN_SHIFT) as u32, @@ -628,6 +644,7 @@ impl VirtioDevice for Balloon { kill_evt, pause_evt, pbp: None, + access_platform: self.common.access_platform(), }; let paused = self.common.paused.clone(); @@ -648,6 +665,14 @@ impl VirtioDevice for Balloon { Ok(()) } + fn set_access_platform(&mut self, access_platform: Arc) { + self.common.set_access_platform(access_platform); + } + + fn access_platform(&self) -> Option> { + self.common.access_platform() + } + fn reset(&mut self) -> Option> { let result = self.common.reset(); event!("virtio-device", "reset", "id", &self.id); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 2c31efe336..948afdfae7 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -3704,6 +3704,7 @@ impl DeviceManager { balloon_config.size, balloon_config.deflate_on_oom, balloon_config.free_page_reporting, + self.force_access_platform, self.seccomp_action.clone(), self.exit_evt .try_clone() From cdbe43f4230de5489595142b10ec78e36f76316a Mon Sep 17 00:00:00 2001 From: Keith Adler Date: Wed, 11 Mar 2026 17:27:22 -0500 Subject: [PATCH 1305/1893] 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 Signed-off-by: Keith Adler Signed-off-by: Ruben Hakobyan --- hypervisor/src/kvm/mod.rs | 233 ++++++++++++++++++++++++++++++++----- hypervisor/src/lib.rs | 20 ++++ vmm/src/seccomp_filters.rs | 19 +++ 3 files changed, 240 insertions(+), 32 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 8b21002d1d..89090294a9 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -14,7 +14,10 @@ use std::any::Any; use std::collections::HashMap; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use std::mem::offset_of; -#[cfg(feature = "tdx")] +#[cfg(feature = "sev_snp")] +use std::os::fd::FromRawFd; +use std::os::fd::OwnedFd; +#[cfg(any(feature = "sev_snp", feature = "tdx"))] use std::os::unix::io::AsRawFd; #[cfg(feature = "tdx")] use std::os::unix::io::RawFd; @@ -26,9 +29,12 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use anyhow::anyhow; +#[cfg(feature = "sev_snp")] +use kvm_bindings::kvm_create_guest_memfd; use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd}; #[cfg(target_arch = "x86_64")] use log::warn; +use vmm_sys_util::errno; use vmm_sys_util::eventfd::EventFd; #[cfg(target_arch = "aarch64")] @@ -50,8 +56,6 @@ pub use crate::riscv64::{ }; #[cfg(target_arch = "riscv64")] use crate::riscv64_reg_id; -use crate::vm::{self, InterruptSourceConfig, VmOps}; -use crate::{HypervisorType, HypervisorVmConfig, cpu, hypervisor}; // x86_64 dependencies #[cfg(target_arch = "x86_64")] pub mod x86_64; @@ -73,7 +77,12 @@ use crate::arch::x86::{ CpuIdEntry, FpuState, LapicState, MTRR_MSR_INDICES, MsrEntry, NUM_IOAPIC_PINS, SpecialRegisters, XsaveState, }; -use crate::{CpuState, IoEventAddress, IrqRoutingEntry, MpState, StandardRegisters}; +use crate::{ + CpuState, HypervisorType, HypervisorVmConfig, InterruptSourceConfig, IoEventAddress, + IrqRoutingEntry, MpState, StandardRegisters, USER_MEMORY_REGION_GUEST_MEMFD, + USER_MEMORY_REGION_LOG_DIRTY, USER_MEMORY_REGION_READ, USER_MEMORY_REGION_WRITE, + UserMemoryRegion, VmOps, cpu, hypervisor, vm, +}; // aarch64 dependencies #[cfg(target_arch = "aarch64")] pub mod aarch64; @@ -83,6 +92,8 @@ pub mod riscv64; #[cfg(target_arch = "aarch64")] use std::mem; +#[cfg(target_arch = "x86_64")] +use kvm_bindings::KVM_X86_DEFAULT_VM; /// /// Export generically-named wrappers of kvm-bindings for Unix-based platforms /// @@ -92,10 +103,11 @@ pub use kvm_bindings::kvm_vcpu_events as VcpuEvents; use kvm_bindings::nested::KvmNestedStateBuffer; pub use kvm_bindings::{ self, KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_IRQ_ROUTING_IRQCHIP, - KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID, - kvm_clock_data, kvm_create_device, kvm_create_device as CreateDevice, + KVM_IRQ_ROUTING_MSI, KVM_MEM_GUEST_MEMFD, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, + KVM_MSI_VALID_DEVID, kvm_clock_data, kvm_create_device, kvm_create_device as CreateDevice, kvm_device_attr as DeviceAttr, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_guest_debug, kvm_irq_routing, kvm_irq_routing_entry, kvm_mp_state, kvm_run, kvm_userspace_memory_region, + kvm_userspace_memory_region2, }; #[cfg(target_arch = "aarch64")] use kvm_bindings::{ @@ -107,7 +119,7 @@ use kvm_bindings::{ #[cfg(target_arch = "riscv64")] use kvm_bindings::{KVM_REG_RISCV_CORE, kvm_riscv_core}; #[cfg(feature = "tdx")] -use kvm_bindings::{KVM_X86_DEFAULT_VM, KVM_X86_SW_PROTECTED_VM, KVMIO, kvm_run__bindgen_ty_1}; +use kvm_bindings::{KVM_X86_SW_PROTECTED_VM, KVMIO, kvm_run__bindgen_ty_1}; #[cfg(target_arch = "x86_64")] use kvm_bindings::{Xsave as xsave2, kvm_xsave2}; pub use kvm_ioctls::{self, Cap, Kvm, VcpuExit}; @@ -238,6 +250,61 @@ pub struct KvmTdxExitVmcall { pub out_rdx: u64, } +impl From for UserMemoryRegion { + fn from(region: kvm_userspace_memory_region2) -> Self { + let mut flags = USER_MEMORY_REGION_READ; + if region.flags & KVM_MEM_READONLY == 0 { + flags |= USER_MEMORY_REGION_WRITE; + } + if region.flags & KVM_MEM_LOG_DIRTY_PAGES != 0 { + flags |= USER_MEMORY_REGION_LOG_DIRTY; + } + if region.flags & KVM_MEM_GUEST_MEMFD != 0 { + flags |= USER_MEMORY_REGION_GUEST_MEMFD; + } + + UserMemoryRegion { + slot: region.slot, + guest_phys_addr: region.guest_phys_addr, + memory_size: region.memory_size, + userspace_addr: region.userspace_addr, + flags, + guest_memfd: Some(region.guest_memfd), + guest_memfd_offset: Some(region.guest_memfd_offset), + } + } +} + +impl From for kvm_userspace_memory_region2 { + fn from(region: UserMemoryRegion) -> Self { + assert!( + region.flags & USER_MEMORY_REGION_READ != 0, + "KVM mapped memory is always readable" + ); + + let mut flags = 0; + if region.flags & USER_MEMORY_REGION_WRITE == 0 { + flags |= KVM_MEM_READONLY; + } + if region.flags & USER_MEMORY_REGION_LOG_DIRTY != 0 { + flags |= KVM_MEM_LOG_DIRTY_PAGES; + } + if region.flags & USER_MEMORY_REGION_GUEST_MEMFD != 0 { + flags |= KVM_MEM_GUEST_MEMFD; + } + + kvm_userspace_memory_region2 { + slot: region.slot, + guest_phys_addr: region.guest_phys_addr, + memory_size: region.memory_size, + userspace_addr: region.userspace_addr, + flags, + guest_memfd: region.guest_memfd.unwrap_or(0), + guest_memfd_offset: region.guest_memfd_offset.unwrap_or(0), + ..Default::default() + } + } +} impl From for MpState { fn from(s: kvm_mp_state) -> Self { MpState::Kvm(s) @@ -424,6 +491,9 @@ struct KvmDirtyLogSlot { guest_phys_addr: u64, memory_size: u64, userspace_addr: u64, + // Following fields are used by kvm_userspace_memory_region2. + guest_memfd_offset: u64, + guest_memfd: u32, } /// Wrapper over KVM VM ioctls. @@ -432,6 +502,7 @@ pub struct KvmVm { #[cfg(target_arch = "x86_64")] msrs: Vec, dirty_log_slots: RwLock>, + guest_memfds: Option>>, } impl KvmVm { @@ -494,6 +565,47 @@ impl KvmVm { fn translate_msi_ext_dest_id(address_lo: u32, address_hi: u32) -> (u32, u32) { (address_lo, address_hi) } + + /// Set user memory region to use guest_memfd when available. + /// guest_memfd is available on host linux kernel v6.8+ + /// + /// # Safety + /// + /// `region.userspace_addr` must point to `region.memory_size` bytes of + /// memory that will stay mapped until the slot is removed via + /// `remove_user_memory_region`. The memory region must + /// be uniquely owned by the caller, as mapping it into the guest + /// effectively creates a long-lived mutable reference. + unsafe fn set_user_memory_region( + &self, + region: kvm_userspace_memory_region2, + ) -> Result<(), errno::Error> { + if self.guest_memfds.is_some() { + // SAFETY: Safe as the caller guarantees that region is safe to map + // the guest and is non-overlapping. + unsafe { self.fd.set_user_memory_region2(region) } + } else { + // SAFETY: Safe because guest regions are guaranteed not to overlap. + unsafe { + self.fd.set_user_memory_region(kvm_userspace_memory_region { + slot: region.slot, + guest_phys_addr: region.guest_phys_addr, + userspace_addr: region.userspace_addr, + flags: region.flags, + memory_size: region.memory_size, + }) + } + } + } + + /// Get flag for kvm_userspace_memory_region based on memfd support. + fn get_kvm_userspace_memory_region_flag(&self, flag: u32) -> u32 { + flag | if self.guest_memfds.is_some() { + KVM_MEM_GUEST_MEMFD + } else { + 0 + } + } } /// Implementation of Vm trait for KVM @@ -759,14 +871,43 @@ impl vm::Vm for KvmVm { const _: () = assert!(core::mem::size_of::() <= core::mem::size_of::()); - let mut region = kvm_userspace_memory_region { + // Create a per-region guest_memfd when supported. + // Each region gets its own fd sized exactly to memory_size + #[cfg(feature = "sev_snp")] + let guest_memfd = if let Some(memfds) = &self.guest_memfds { + // SAFETY: Safe because guest regions are guaranteed not to overlap. + let fd = unsafe { + OwnedFd::from_raw_fd( + self.fd + .create_guest_memfd(kvm_create_guest_memfd { + size: memory_size as u64, + ..Default::default() + }) + .map_err(|e| vm::HypervisorVmError::CreateUserMemory(e.into()))?, + ) + }; + let raw_fd = fd.as_raw_fd() as u32; + memfds.write().unwrap().insert(slot, fd); + raw_fd + } else { + 0 + }; + #[cfg(not(feature = "sev_snp"))] + let guest_memfd = 0; + + let mut region = kvm_userspace_memory_region2 { slot, + flags: self.get_kvm_userspace_memory_region_flag(flags), guest_phys_addr, memory_size: memory_size as u64, userspace_addr: userspace_addr as usize as u64, - flags, + #[cfg(not(target_arch = "riscv64"))] + guest_memfd, + // Each guest_memfd is per-region and sized to memory_size, + // so the region's data always starts at offset 0. + guest_memfd_offset: 0, + ..Default::default() }; - if (region.flags & KVM_MEM_LOG_DIRTY_PAGES) != 0 { if (region.flags & KVM_MEM_READONLY) != 0 { return Err(vm::HypervisorVmError::CreateUserMemory(anyhow!( @@ -782,20 +923,22 @@ impl vm::Vm for KvmVm { guest_phys_addr: region.guest_phys_addr, memory_size: region.memory_size, userspace_addr: region.userspace_addr, + guest_memfd_offset: region.guest_memfd_offset, + guest_memfd: region.guest_memfd, }, ); // Always create guest physical memory region without `KVM_MEM_LOG_DIRTY_PAGES`. // For regions that need this flag, dirty pages log will be turned on in `start_dirty_log`. - region.flags = 0; + region.flags = self.get_kvm_userspace_memory_region_flag(0); } // SAFETY: Safe because caller promised this is safe. unsafe { - self.fd - .set_user_memory_region(region) - .map_err(|e| vm::HypervisorVmError::CreateUserMemory(e.into())) + self.set_user_memory_region(region) + .map_err(|e| vm::HypervisorVmError::CreateUserMemory(e.into()))?; } + Ok(()) } /// Removes a guest physical memory region. @@ -823,12 +966,13 @@ impl vm::Vm for KvmVm { const _: () = assert!(core::mem::size_of::() <= core::mem::size_of::()); - let mut region = kvm_userspace_memory_region { + let mut region = kvm_userspace_memory_region2 { slot, guest_phys_addr, memory_size: memory_size as u64, userspace_addr: userspace_addr as usize as u64, flags, + ..Default::default() }; // Remove the corresponding entry from "self.dirty_log_slots" if needed @@ -838,10 +982,16 @@ impl vm::Vm for KvmVm { region.memory_size = 0; // SAFETY: Safe because caller promised this is safe. unsafe { - self.fd - .set_user_memory_region(region) - .map_err(|e| vm::HypervisorVmError::RemoveUserMemory(e.into())) + self.set_user_memory_region(region) + .map_err(|e| vm::HypervisorVmError::RemoveUserMemory(e.into()))?; } + + // Close the per-region guest_memfd if one was created for this slot + if let Some(memfds) = &self.guest_memfds { + memfds.write().unwrap().remove(&slot); + } + + Ok(()) } /// @@ -932,17 +1082,19 @@ impl vm::Vm for KvmVm { fn start_dirty_log(&self) -> vm::Result<()> { let dirty_log_slots = self.dirty_log_slots.read().unwrap(); for (_, s) in dirty_log_slots.iter() { - let region = kvm_userspace_memory_region { + let region = kvm_userspace_memory_region2 { slot: s.slot, guest_phys_addr: s.guest_phys_addr, memory_size: s.memory_size, userspace_addr: s.userspace_addr, - flags: KVM_MEM_LOG_DIRTY_PAGES, + flags: self.get_kvm_userspace_memory_region_flag(KVM_MEM_LOG_DIRTY_PAGES), + guest_memfd: s.guest_memfd, + guest_memfd_offset: s.guest_memfd_offset, + ..Default::default() }; // SAFETY: Safe because guest regions are guaranteed not to overlap. unsafe { - self.fd - .set_user_memory_region(region) + self.set_user_memory_region(region) .map_err(|e| vm::HypervisorVmError::StartDirtyLog(e.into()))?; } } @@ -956,17 +1108,19 @@ impl vm::Vm for KvmVm { fn stop_dirty_log(&self) -> vm::Result<()> { let dirty_log_slots = self.dirty_log_slots.read().unwrap(); for (_, s) in dirty_log_slots.iter() { - let region = kvm_userspace_memory_region { + let region = kvm_userspace_memory_region2 { slot: s.slot, guest_phys_addr: s.guest_phys_addr, memory_size: s.memory_size, userspace_addr: s.userspace_addr, - flags: 0, + flags: self.get_kvm_userspace_memory_region_flag(0), + guest_memfd: s.guest_memfd, + guest_memfd_offset: s.guest_memfd_offset, + ..Default::default() }; // SAFETY: Safe because guest regions are guaranteed not to overlap. unsafe { - self.fd - .set_user_memory_region(region) + self.set_user_memory_region(region) .map_err(|e| vm::HypervisorVmError::StartDirtyLog(e.into()))?; } } @@ -1228,11 +1382,17 @@ impl hypervisor::Hypervisor for KvmHypervisor { vm_type = self.kvm.get_host_ipa_limit().try_into().unwrap(); } - #[cfg(feature = "tdx")] - if _config.tdx_enabled { - vm_type = KVM_X86_SW_PROTECTED_VM.into(); - } else { - vm_type = KVM_X86_DEFAULT_VM.into(); + #[cfg(target_arch = "x86_64")] + cfg_if::cfg_if! { + if #[cfg(feature = "tdx")] { + if _config.tdx_enabled { + vm_type = KVM_X86_SW_PROTECTED_VM.into(); + } else { + vm_type = KVM_X86_DEFAULT_VM.into(); + } + } else { + vm_type = KVM_X86_DEFAULT_VM.into(); + } } loop { @@ -1255,7 +1415,7 @@ impl hypervisor::Hypervisor for KvmHypervisor { { let msr_list = self.get_msr_list()?; let num_msrs = msr_list.as_fam_struct_ref().nmsrs as usize; - let mut msrs: Vec = vec![ + let mut msrs = vec![ MsrEntry { ..Default::default() }; @@ -1266,10 +1426,18 @@ impl hypervisor::Hypervisor for KvmHypervisor { msrs[pos].index = *index; } + #[allow(unused_mut)] + let mut guest_memfds = None; + #[cfg(feature = "sev_snp")] + if _config.sev_snp_enabled && fd.check_extension(Cap::GuestMemfd) { + guest_memfds = Some(RwLock::new(HashMap::new())); + } + Ok(Arc::new(KvmVm { fd, msrs, dirty_log_slots: RwLock::new(HashMap::new()), + guest_memfds, })) } @@ -1278,6 +1446,7 @@ impl hypervisor::Hypervisor for KvmHypervisor { Ok(Arc::new(KvmVm { fd, dirty_log_slots: RwLock::new(HashMap::new()), + guest_memfds: None, })) } } diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 3d919e45ce..787cfbf1f4 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -118,6 +118,26 @@ pub fn vec_with_array_field(count: usize) -> Vec { vec_with_size_in_bytes(vec_size_bytes) } +/// User memory region structure +#[derive(Debug, Default, Eq, PartialEq)] +pub struct UserMemoryRegion { + pub slot: u32, + pub guest_phys_addr: u64, + pub memory_size: u64, + pub userspace_addr: u64, + pub flags: u32, + pub guest_memfd: Option, + pub guest_memfd_offset: Option, +} + +/// Flags for user memory region +pub const USER_MEMORY_REGION_READ: u32 = 1; +pub const USER_MEMORY_REGION_WRITE: u32 = 1 << 1; +pub const USER_MEMORY_REGION_EXECUTE: u32 = 1 << 2; +pub const USER_MEMORY_REGION_LOG_DIRTY: u32 = 1 << 3; +pub const USER_MEMORY_REGION_ADJUSTABLE: u32 = 1 << 4; +pub const USER_MEMORY_REGION_GUEST_MEMFD: u32 = 1 << 5; + #[derive(Debug)] pub enum MpState { #[cfg(feature = "kvm")] diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 25da7f9c9a..008fc60f29 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -90,6 +90,9 @@ mod kvm { pub const KVM_HAS_DEVICE_ATTR: u64 = 0x4018_aee3; pub const KVM_SET_ONE_REG: u64 = 0x4010_aeac; pub const KVM_SET_USER_MEMORY_REGION: u64 = 0x4020_ae46; + pub const KVM_SET_USER_MEMORY_REGION2: u64 = 0x40a0_ae49; + pub const KVM_SET_MEMORY_ATTRIBUTES: u64 = 0x4020_aed2; + pub const KVM_CREATE_GUEST_MEMFD: u64 = 0xc040_aed4; pub const KVM_IRQFD: u64 = 0x4020_ae76; pub const KVM_IOEVENTFD: u64 = 0x4040_ae79; pub const KVM_SET_VCPU_EVENTS: u64 = 0x4040_aea0; @@ -252,6 +255,14 @@ fn create_vmm_ioctl_seccomp_rule_common_kvm() -> Result, Backen and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_ONE_REG)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_REGS)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + KVM_SET_USER_MEMORY_REGION2, + )?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_MEMORY_ATTRIBUTES,)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_CREATE_GUEST_MEMFD,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_VCPU_EVENTS,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_NMI)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_NESTED_STATE)?], @@ -750,6 +761,14 @@ fn create_vcpu_ioctl_seccomp_rule_kvm() -> Result, BackendError and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_DEVICE_ATTR,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_GSI_ROUTING,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?], + and![Cond::new( + 1, + ArgLen::Dword, + Eq, + KVM_SET_USER_MEMORY_REGION2, + )?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_CREATE_GUEST_MEMFD,)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_MEMORY_ATTRIBUTES,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_RUN,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_NMI)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_NESTED_STATE)?], From c31f5d4998e1f249be6c4e73244b8a11f9f0e806 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 07:59:20 -0700 Subject: [PATCH 1306/1893] 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 --- vmm/src/vm_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 9162045404..01d3bb0101 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -849,7 +849,7 @@ impl PayloadConfig { #[cfg(feature = "igvm")] { if self.igvm.is_some() { - if self.firmware.is_some() || self.kernel.is_some() { + if self.firmware.is_some() { return Err(PayloadConfigError::IgvmPlusOtherPayloads); } return Ok(()); From 8ee0a07ab112441286432c29a61f13768447afae Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 08:05:30 -0700 Subject: [PATCH 1307/1893] 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 Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Signed-off-by: Ruben Hakobyan --- arch/src/x86_64/mod.rs | 23 ++++++++++--------- hypervisor/src/lib.rs | 2 +- vmm/src/cpu.rs | 51 +++++++++++++++++++++++++++++++----------- vmm/src/vm.rs | 10 +++++++++ 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index d35a878e61..09577b436c 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -819,6 +819,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)); @@ -892,17 +893,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)?; + 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)?; + } regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?; - - // 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)?; } interrupts::set_lint(vcpu).map_err(|e| Error::LocalIntConfiguration(e.into()))?; Ok(()) diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 787cfbf1f4..8357e63b79 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -64,7 +64,7 @@ pub use vm::{ pub use crate::hypervisor::{Hypervisor, HypervisorError}; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum HypervisorType { #[cfg(feature = "kvm")] Kvm, diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 1450e0a8e2..261f4c1c75 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -546,6 +546,7 @@ impl Vcpu { #[cfg(target_arch = "x86_64")] kvm_hyperv: bool, #[cfg(target_arch = "x86_64")] topology: (u16, u16, u16, u16), #[cfg(target_arch = "x86_64")] nested: bool, + #[cfg(feature = "igvm")] igvm_enabled: bool, ) -> Result<()> { #[cfg(target_arch = "aarch64")] { @@ -558,17 +559,32 @@ impl Vcpu { .map_err(Error::VcpuConfiguration)?; info!("Configuring vCPU: cpu_id = {}", self.id); #[cfg(target_arch = "x86_64")] - arch::configure_vcpu( - self.vcpu.as_ref(), - self.id, - boot_setup, - cpuid, - kvm_hyperv, - self.vendor, - topology, - nested, - ) - .map_err(Error::VcpuConfiguration)?; + { + // When IGVM is enabled, skip standard register setup here — the IGVM + // loader populates vCPU registers from the VMSA via set_sev_control_register + // (currently KVM-specific; MSHV handles this through its own import path). + // igvm_enabled is kept as an explicit flag rather than derived from sev_snp + // state because IGVM could theoretically be used independently of SEV-SNP. + cfg_if::cfg_if! { + if #[cfg(feature = "igvm")] { + let setup_registers = !igvm_enabled; + } else { + let setup_registers = true; + } + } + arch::configure_vcpu( + self.vcpu.as_ref(), + self.id, + boot_setup, + cpuid, + kvm_hyperv, + self.vendor, + topology, + nested, + setup_registers, + ) + .map_err(Error::VcpuConfiguration)?; + } Ok(()) } @@ -697,6 +713,8 @@ pub struct CpuManager { sev_snp_enabled: bool, // State of the core scheduling group leader election (VM mode). core_scheduling_group_leader: Arc, + #[cfg(feature = "igvm")] + igvm_enabled: bool, } /// State of the core scheduling group leader election for VM-wide cookie @@ -826,6 +844,7 @@ impl CpuManager { #[cfg(feature = "tdx")] tdx_enabled: bool, numa_nodes: &NumaNodes, #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, + #[cfg(feature = "igvm")] igvm_enabled: bool, ) -> Result>> { if config.max_vcpus > hypervisor.get_max_vcpus() { return Err(Error::MaximumVcpusExceeded( @@ -902,6 +921,8 @@ impl CpuManager { core_scheduling_group_leader: Arc::new(AtomicI32::new( CoreSchedulingLeader::Initial as i32, )), + #[cfg(feature = "igvm")] + igvm_enabled, }))) } @@ -980,8 +1001,10 @@ impl CpuManager { vcpu: &mut Vcpu, boot_setup: Option<(EntryPoint, &GuestMemoryAtomic)>, ) -> Result<()> { - #[cfg(feature = "sev_snp")] - if self.sev_snp_enabled { + #[cfg(all(feature = "sev_snp", feature = "mshv"))] + if self.sev_snp_enabled + && self.hypervisor.hypervisor_type() == hypervisor::HypervisorType::Mshv + { if let Some((kernel_entry_point, _)) = boot_setup { vcpu.set_sev_control_register( kernel_entry_point.entry_addr.0 / crate::igvm::HV_PAGE_SIZE, @@ -1022,6 +1045,8 @@ impl CpuManager { self.config.kvm_hyperv, topology, self.config.nested, + #[cfg(feature = "igvm")] + self.igvm_enabled, )?; #[cfg(target_arch = "aarch64")] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index ffe7b8624a..f67d5bc108 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -738,6 +738,14 @@ impl Vm { let tdx_enabled = config.lock().unwrap().is_tdx_enabled(); #[cfg(feature = "sev_snp")] let sev_snp_enabled = config.lock().unwrap().is_sev_snp_enabled(); + #[cfg(feature = "igvm")] + let igvm_enabled = config + .lock() + .unwrap() + .payload + .as_ref() + .and_then(|p| p.igvm.as_ref()) + .is_some(); let cpus_config = config.lock().unwrap().cpus.clone(); let cpu_manager = cpu::CpuManager::new( @@ -755,6 +763,8 @@ impl Vm { numa_nodes, #[cfg(feature = "sev_snp")] sev_snp_enabled, + #[cfg(feature = "igvm")] + igvm_enabled, ) .map_err(Error::CpuManager)?; From b545b2fc4eb8f402c4c40f0e39c196dd0e1e0a0f Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 08:09:58 -0700 Subject: [PATCH 1308/1893] 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 Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Signed-off-by: Ruben Hakobyan --- hypervisor/src/mshv/mod.rs | 4 ++-- hypervisor/src/vm.rs | 4 +++- vmm/Cargo.toml | 7 ++++++- vmm/src/vm.rs | 18 +++++++++++++++++- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 8623531c5b..1119691273 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -58,7 +58,7 @@ pub use aarch64::VcpuMshvState; #[cfg(target_arch = "aarch64")] use aarch64::gic::{BASE_SPI_IRQ, MshvGicV2M}; #[cfg(feature = "sev_snp")] -use igvm_defs::IGVM_VHS_SNP_ID_BLOCK; +use igvm_defs::{IGVM_VHS_SNP_ID_BLOCK, SnpPolicy}; #[cfg(feature = "sev_snp")] use snp_constants::*; use vmm_sys_util::eventfd::EventFd; @@ -2254,7 +2254,7 @@ impl vm::Vm for MshvVm { /// Initialize the SEV-SNP VM #[cfg(feature = "sev_snp")] - fn sev_snp_init(&self) -> vm::Result<()> { + fn sev_snp_init(&self, _guest_policy: SnpPolicy) -> vm::Result<()> { self.fd .set_partition_property( hv_partition_property_code_HV_PARTITION_PROPERTY_ISOLATION_STATE, diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 9d7e60a8be..e6787d19ef 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -17,6 +17,8 @@ use std::sync::Mutex; #[cfg(feature = "sev_snp")] use igvm_defs::IGVM_VHS_SNP_ID_BLOCK; +#[cfg(feature = "sev_snp")] +use igvm_defs::SnpPolicy; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; @@ -392,7 +394,7 @@ pub trait Vm: Send + Sync + Any { fn get_dirty_log(&self, slot: u32, base_gpa: u64, memory_size: u64) -> Result>; #[cfg(feature = "sev_snp")] /// Initialize SEV-SNP on this VM - fn sev_snp_init(&self) -> Result<()> { + fn sev_snp_init(&self, _guest_policy: SnpPolicy) -> Result<()> { unimplemented!() } #[cfg(feature = "tdx")] diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 1fe5e0e47b..7c8354a0cb 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -32,7 +32,12 @@ mshv = [ "vm-device/mshv", ] pvmemcontrol = ["devices/pvmemcontrol"] -sev_snp = ["arch/sev_snp", "hypervisor/sev_snp", "virtio-devices/sev_snp"] +sev_snp = [ + "arch/sev_snp", + "hypervisor/sev_snp", + "igvm_defs", + "virtio-devices/sev_snp", +] tdx = ["arch/tdx", "hypervisor/tdx"] tracing = ["tracer/tracing"] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index f67d5bc108..a60ee722d8 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -47,6 +47,8 @@ use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs; #[cfg(target_arch = "aarch64")] use hypervisor::arch::aarch64::regs::AARCH64_PMU_IRQ; use hypervisor::{HypervisorVmConfig, HypervisorVmError, VmOps}; +#[cfg(feature = "sev_snp")] +use igvm_defs::SnpPolicy; use libc::{SIGWINCH, termios}; use linux_loader::cmdline::Cmdline; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] @@ -534,6 +536,19 @@ pub struct Vm { impl Vm { pub const HANDLED_SIGNALS: [i32; 1] = [SIGWINCH]; + #[cfg(feature = "sev_snp")] + pub fn get_default_sev_snp_guest_policy() -> SnpPolicy { + SnpPolicy::new() + .with_abi_minor(0) + .with_abi_major(0) + // SMT permitted: allows the guest to run on an SMT-enabled host. + // This is the permissive default; future work can expose this as a + // configurable platform option. + .with_smt(1) + .with_reserved_must_be_one(1) + .with_migrate_ma(0) + } + #[allow(clippy::needless_pass_by_value)] #[allow(clippy::too_many_arguments)] pub fn new_from_memory_manager( @@ -982,7 +997,8 @@ impl Vm { .map_err(Error::CpuManager)?; // Initialize SEV-SNP - transitions guest into secure state - vm.sev_snp_init().map_err(Error::InitializeSevSnpVm)?; + vm.sev_snp_init(Self::get_default_sev_snp_guest_policy()) + .map_err(Error::InitializeSevSnpVm)?; // Load payload for SEV-SNP (IGVM parser needs cpu_manager for cpuid) let load_payload_handle = if snapshot.is_none() { From 7d65187350a12f6d6a4ea338e1e87e6160ca5b69 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 18:39:37 -0700 Subject: [PATCH 1309/1893] vmm: make RSDP address optional in configure_system Change configure_system to take an Option since rsdp is wrapped into an option anyways (we use configure system to setup the mptables). Co-authored-by: Keith Adler Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Signed-off-by: Ruben Hakobyan --- vmm/src/vm.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index a60ee722d8..ef3d0ebac1 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1676,7 +1676,11 @@ impl Vm { } #[cfg(target_arch = "x86_64")] - fn configure_system(&mut self, rsdp_addr: GuestAddress, entry_addr: EntryPoint) -> Result<()> { + fn configure_system( + &mut self, + rsdp_addr: Option, + entry_addr: EntryPoint, + ) -> Result<()> { trace_scoped!("configure_system"); info!("Configuring system"); let mem = self.memory_manager.lock().unwrap().boot_guest_memory(); @@ -1687,7 +1691,6 @@ impl Vm { }; let boot_vcpus = self.cpu_manager.lock().unwrap().boot_vcpus(); - let rsdp_addr = Some(rsdp_addr); let serial_number = self .config @@ -1739,7 +1742,7 @@ impl Vm { #[cfg(target_arch = "aarch64")] fn configure_system( &mut self, - _rsdp_addr: GuestAddress, + _rsdp_addr: Option, _entry_addr: EntryPoint, ) -> Result<()> { let cmdline = Self::generate_cmdline( @@ -2776,16 +2779,11 @@ impl Vm { let rsdp_addr = self.create_acpi_tables(); #[cfg(not(target_arch = "riscv64"))] - { - #[cfg(not(any(feature = "sev_snp", feature = "tdx")))] - assert!(rsdp_addr.is_some()); - // Configure shared state based on loaded kernel - if let Some(rsdp_adr) = rsdp_addr { - entry_point - .map(|entry_point| self.configure_system(rsdp_adr, entry_point)) - .transpose()?; - } - } + // Configure shared state based on loaded kernel + entry_point + .map(|entry_point| self.configure_system(rsdp_addr, entry_point)) + .transpose()?; + #[cfg(target_arch = "riscv64")] self.configure_system().unwrap(); From 4f1119a78891e632a33ec2610f75f0a70f292ea1 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 18:40:26 -0700 Subject: [PATCH 1310/1893] 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 Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Signed-off-by: Ruben Hakobyan --- vmm/src/vm.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index ef3d0ebac1..beec204f79 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -942,8 +942,6 @@ impl Vm { config, #[cfg(feature = "igvm")] cpu_manager, - #[cfg(feature = "sev_snp")] - false, )? } else { None @@ -1007,7 +1005,6 @@ impl Vm { config, #[cfg(feature = "igvm")] cpu_manager, - true, )? } else { None @@ -1583,19 +1580,19 @@ impl Vm { payload: &PayloadConfig, memory_manager: Arc>, #[cfg(feature = "igvm")] cpu_manager: Arc>, - #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, ) -> Result { trace_scoped!("load_payload"); #[cfg(feature = "igvm")] { if let Some(_igvm_file) = &payload.igvm { let igvm = File::open(_igvm_file).map_err(Error::IgvmFile)?; - #[cfg(feature = "sev_snp")] - if sev_snp_enabled { - return Self::load_igvm(igvm, memory_manager, cpu_manager, &payload.host_data); - } - #[cfg(not(feature = "sev_snp"))] - return Self::load_igvm(igvm, memory_manager, cpu_manager); + return Self::load_igvm( + igvm, + memory_manager, + cpu_manager, + #[cfg(feature = "sev_snp")] + &payload.host_data, + ); } } match (&payload.firmware, &payload.kernel) { @@ -1639,7 +1636,6 @@ impl Vm { memory_manager: &Arc>, config: &Arc>, #[cfg(feature = "igvm")] cpu_manager: &Arc>, - #[cfg(feature = "sev_snp")] sev_snp_enabled: bool, ) -> Result>>> { // Kernel with TDX is loaded in a different manner #[cfg(feature = "tdx")] @@ -1666,8 +1662,6 @@ impl Vm { memory_manager, #[cfg(feature = "igvm")] cpu_manager, - #[cfg(feature = "sev_snp")] - sev_snp_enabled, ) }) .map_err(Error::KernelLoadThreadSpawn) From 425609a8b5fc10c9c0b35a44dc4f8b08a4751031 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Thu, 9 Apr 2026 16:58:58 -0700 Subject: [PATCH 1311/1893] 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 --- vmm/src/igvm/igvm_loader.rs | 14 ++++------- vmm/src/igvm/mod.rs | 9 ++++++++ vmm/src/lib.rs | 2 ++ vmm/src/vm.rs | 46 +++++++++++++++++++++++++++++-------- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 6e256c1ecb..444fbcc539 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -4,12 +4,11 @@ // use std::collections::HashMap; use std::ffi::CString; -use std::io::{Read, Seek, SeekFrom}; use std::mem::size_of; use std::sync::{Arc, Mutex}; use igvm::snp_defs::SevVmsa; -use igvm::{IgvmDirectiveHeader, IgvmFile, IgvmPlatformHeader, IsolationType}; +use igvm::{IgvmDirectiveHeader, IgvmFile, IgvmPlatformHeader}; #[cfg(feature = "sev_snp")] use igvm_defs::{IGVM_VHS_MEMORY_MAP_ENTRY, MemoryMapEntryType}; use igvm_defs::{ @@ -51,6 +50,8 @@ pub enum Error { FailedToDecodeHostData(#[source] hex::FromHexError), #[error("Error allocating address space")] MemoryManager(MemoryManagerError), + #[error("IGVM file not provided")] + MissingIgvm, } #[allow(dead_code)] @@ -135,7 +136,7 @@ fn import_parameter( /// any isolation. #[allow(clippy::needless_pass_by_value)] pub fn load_igvm( - mut file: &std::fs::File, + igvm_file: IgvmFile, memory_manager: Arc>, cpu_manager: Arc>, cmdline: &str, @@ -143,7 +144,6 @@ pub fn load_igvm( ) -> Result, Error> { let mut loaded_info: Box = Box::default(); let command_line = CString::new(cmdline).map_err(Error::InvalidCommandLine)?; - let mut file_contents = Vec::new(); let memory = memory_manager.lock().as_ref().unwrap().guest_memory(); let mut gpas: Vec = Vec::new(); let proc_count = cpu_manager.lock().unwrap().vcpus().len() as u32; @@ -156,12 +156,6 @@ pub fn load_igvm( .map_err(Error::FailedToDecodeHostData)?; } - file.seek(SeekFrom::Start(0)).map_err(Error::Igvm)?; - file.read_to_end(&mut file_contents).map_err(Error::Igvm)?; - - let igvm_file = IgvmFile::new_from_binary(&file_contents, Some(IsolationType::Snp)) - .map_err(Error::InvalidIgvmFile)?; - let mask = match &igvm_file.platforms()[0] { IgvmPlatformHeader::SupportedPlatform(info) => { debug_assert!(info.platform_type == IgvmPlatformType::SEV_SNP); diff --git a/vmm/src/igvm/mod.rs b/vmm/src/igvm/mod.rs index 62c32d4e89..ded102bd35 100644 --- a/vmm/src/igvm/mod.rs +++ b/vmm/src/igvm/mod.rs @@ -27,10 +27,19 @@ pub mod igvm_loader; mod loader; +use std::path::Path; + use igvm::snp_defs::SevVmsa; +use igvm::{IgvmFile, IsolationType}; use igvm_defs::IGVM_VHS_SNP_ID_BLOCK; use zerocopy::FromZeros; +pub fn parse_igvm(igvm_path: &Path) -> Result { + let file_contents = std::fs::read(igvm_path).map_err(igvm_loader::Error::Igvm)?; + IgvmFile::new_from_binary(&file_contents, Some(IsolationType::Snp)) + .map_err(igvm_loader::Error::InvalidIgvmFile) +} + #[derive(Debug, Clone)] pub struct IgvmLoadedInfo { pub gpas: Vec, diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 5dfea07e78..f98c5b7258 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1161,6 +1161,8 @@ impl Vmm { self.console_resize_pipe.clone(), Arc::clone(&self.original_termios_opt), Some(&snapshot), + #[cfg(feature = "igvm")] + None, ) .map_err(|e| { MigratableError::MigrateReceive(anyhow!("Error creating VM from snapshot: {e:?}")) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index beec204f79..fe74e6a75e 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -47,6 +47,8 @@ use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs; #[cfg(target_arch = "aarch64")] use hypervisor::arch::aarch64::regs::AARCH64_PMU_IRQ; use hypervisor::{HypervisorVmConfig, HypervisorVmError, VmOps}; +#[cfg(feature = "igvm")] +use igvm::IgvmFile; #[cfg(feature = "sev_snp")] use igvm_defs::SnpPolicy; use libc::{SIGWINCH, termios}; @@ -333,10 +335,6 @@ pub enum Error { #[error("Error coredumping VM")] Coredump(#[source] GuestDebuggableError), - #[cfg(feature = "igvm")] - #[error("Cannot open igvm file")] - IgvmFile(#[source] io::Error), - #[cfg(feature = "igvm")] #[error("Cannot load the igvm into memory")] IgvmLoad(#[source] igvm_loader::Error), @@ -567,6 +565,7 @@ impl Vm { console_resize_pipe: Option>, original_termios: Arc>>, snapshot: Option<&Snapshot>, + #[cfg(feature = "igvm")] igvm_file: Option, ) -> Result { trace_scoped!("Vm::new_from_memory_manager"); @@ -649,6 +648,8 @@ impl Vm { console_resize_pipe.as_ref(), &original_termios, snapshot, + #[cfg(feature = "igvm")] + igvm_file, )?; // Load kernel and initramfs files @@ -879,6 +880,7 @@ impl Vm { console_resize_pipe: Option<&Arc>, original_termios: &Arc>>, snapshot: Option<&Snapshot>, + #[cfg(feature = "igvm")] igvm_file: Option, ) -> Result>>> { #[cfg(feature = "mshv")] let is_mshv = matches!( @@ -913,6 +915,8 @@ impl Vm { console_resize_pipe, original_termios, snapshot, + #[cfg(feature = "igvm")] + igvm_file, ); } @@ -942,6 +946,8 @@ impl Vm { config, #[cfg(feature = "igvm")] cpu_manager, + #[cfg(feature = "igvm")] + igvm_file, )? } else { None @@ -986,6 +992,7 @@ impl Vm { console_resize_pipe: Option<&Arc>, original_termios: &Arc>>, snapshot: Option<&Snapshot>, + #[cfg(feature = "igvm")] igvm_file: Option, ) -> Result>>> { // Create boot vCPUs before SEV-SNP initialization cpu_manager @@ -1005,6 +1012,8 @@ impl Vm { config, #[cfg(feature = "igvm")] cpu_manager, + #[cfg(feature = "igvm")] + igvm_file, )? } else { None @@ -1306,6 +1315,18 @@ impl Vm { vm_config.lock().unwrap().is_tdx_enabled() }; + #[cfg(feature = "igvm")] + let igvm_file = { + let config = vm_config.lock().unwrap(); + config + .payload + .as_ref() + .and_then(|p| p.igvm.as_ref()) + .map(|igvm_path| crate::igvm::parse_igvm(igvm_path)) + .transpose() + .map_err(Error::IgvmLoad)? + }; + let vm = Self::create_hypervisor_vm( hypervisor.as_ref(), vm_config.as_ref().lock().unwrap().deref().into(), @@ -1366,6 +1387,8 @@ impl Vm { console_resize_pipe, original_termios, snapshot, + #[cfg(feature = "igvm")] + igvm_file, ) } @@ -1484,13 +1507,13 @@ impl Vm { #[cfg(feature = "igvm")] #[allow(clippy::needless_pass_by_value)] fn load_igvm( - igvm: File, + igvm_file: IgvmFile, memory_manager: Arc>, cpu_manager: Arc>, #[cfg(feature = "sev_snp")] host_data: &Option, ) -> Result { let res = igvm_loader::load_igvm( - &igvm, + igvm_file, memory_manager, cpu_manager.clone(), "", @@ -1580,14 +1603,16 @@ impl Vm { payload: &PayloadConfig, memory_manager: Arc>, #[cfg(feature = "igvm")] cpu_manager: Arc>, + #[cfg(feature = "igvm")] igvm_file: Option, ) -> Result { trace_scoped!("load_payload"); #[cfg(feature = "igvm")] { - if let Some(_igvm_file) = &payload.igvm { - let igvm = File::open(_igvm_file).map_err(Error::IgvmFile)?; + if payload.igvm.is_some() { + let igvm_file = + igvm_file.ok_or(Error::IgvmLoad(igvm_loader::Error::MissingIgvm))?; return Self::load_igvm( - igvm, + igvm_file, memory_manager, cpu_manager, #[cfg(feature = "sev_snp")] @@ -1636,6 +1661,7 @@ impl Vm { memory_manager: &Arc>, config: &Arc>, #[cfg(feature = "igvm")] cpu_manager: &Arc>, + #[cfg(feature = "igvm")] igvm_file: Option, ) -> Result>>> { // Kernel with TDX is loaded in a different manner #[cfg(feature = "tdx")] @@ -1662,6 +1688,8 @@ impl Vm { memory_manager, #[cfg(feature = "igvm")] cpu_manager, + #[cfg(feature = "igvm")] + igvm_file, ) }) .map_err(Error::KernelLoadThreadSpawn) From 2e004521e01f8c7a8d50908d4b6b3396ab03160f Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 14:23:13 -0700 Subject: [PATCH 1312/1893] 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 Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Co-authored-by: Rob Bradford Signed-off-by: Rob Bradford Signed-off-by: Ruben Hakobyan --- hypervisor/src/cpu.rs | 5 +- hypervisor/src/hypervisor.rs | 5 ++ hypervisor/src/kvm/mod.rs | 77 +++++++++++++++++---- hypervisor/src/kvm/x86_64/mod.rs | 3 + hypervisor/src/kvm/x86_64/sev.rs | 113 +++++++++++++++++++++++++++++++ hypervisor/src/lib.rs | 2 + hypervisor/src/vm.rs | 4 +- vmm/src/device_manager.rs | 8 +-- vmm/src/igvm/igvm_loader.rs | 15 ++++ vmm/src/lib.rs | 2 + vmm/src/seccomp_filters.rs | 2 + vmm/src/vm.rs | 14 ++-- 12 files changed, 225 insertions(+), 25 deletions(-) create mode 100644 hypervisor/src/kvm/x86_64/sev.rs diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index 4bc348a98d..a4a029e989 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -587,10 +587,11 @@ pub trait Vcpu: Send + Sync { ) -> Result<[u32; 4]> { unimplemented!() } - #[cfg(feature = "mshv")] - fn set_sev_control_register(&self, _reg: u64) -> Result<()> { + #[cfg(feature = "sev_snp")] + fn set_sev_control_register(&self, _vmsa_pfn: u64) -> Result<()> { unimplemented!() } + /// /// Sets the value of GIC redistributor address /// diff --git a/hypervisor/src/hypervisor.rs b/hypervisor/src/hypervisor.rs index a25f8a9bf7..05852a230f 100644 --- a/hypervisor/src/hypervisor.rs +++ b/hypervisor/src/hypervisor.rs @@ -96,6 +96,11 @@ pub enum HypervisorError { #[cfg(target_arch = "x86_64")] #[error("Failed to enable AMX tile state components")] CouldNotEnableAmxStateComponents(#[source] crate::arch::x86::AmxGuestSupportError), + /// + /// Failed to retrieve SEV-SNP capabilities + /// + #[error("Failed to retrieve SEV-SNP capabilities:{0}")] + SevSnpCapabilities(#[source] anyhow::Error), } /// diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 89090294a9..a697d5df79 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -140,6 +140,9 @@ use crate::kvm::x86_64::XsaveStateError; #[cfg(target_arch = "x86_64")] ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a); +#[cfg(feature = "sev_snp")] +use kvm_bindings::{KVM_MEMORY_ATTRIBUTE_PRIVATE, KVM_X86_SNP_VM, kvm_memory_attributes}; + #[cfg(feature = "tdx")] const KVM_EXIT_TDX: u32 = 50; #[cfg(feature = "tdx")] @@ -498,9 +501,11 @@ struct KvmDirtyLogSlot { /// Wrapper over KVM VM ioctls. pub struct KvmVm { - fd: VmFd, + fd: Arc, #[cfg(target_arch = "x86_64")] msrs: Vec, + #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] + sev_fd: Option, dirty_log_slots: RwLock>, guest_memfds: Option>>, } @@ -621,6 +626,15 @@ impl KvmVm { /// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// ``` impl vm::Vm for KvmVm { + #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] + fn sev_snp_init(&self, guest_policy: igvm_defs::SnpPolicy) -> vm::Result<()> { + self.sev_fd + .as_ref() + .unwrap() + .launch_start(&self.fd, guest_policy) + .map_err(|e| vm::HypervisorVmError::InitializeSevSnp(e.into())) + } + #[cfg(target_arch = "x86_64")] /// /// Sets the address of the one-page region in the VM's address space. @@ -938,6 +952,18 @@ impl vm::Vm for KvmVm { self.set_user_memory_region(region) .map_err(|e| vm::HypervisorVmError::CreateUserMemory(e.into()))?; } + + #[cfg(feature = "sev_snp")] + if self.guest_memfds.is_some() { + self.fd + .set_memory_attributes(kvm_memory_attributes { + address: region.guest_phys_addr, + size: region.memory_size, + attributes: KVM_MEMORY_ATTRIBUTE_PRIVATE as u64, + flags: 0, + }) + .map_err(|e| vm::HypervisorVmError::CreateUserMemory(e.into()))?; + } Ok(()) } @@ -1383,15 +1409,17 @@ impl hypervisor::Hypervisor for KvmHypervisor { } #[cfg(target_arch = "x86_64")] - cfg_if::cfg_if! { - if #[cfg(feature = "tdx")] { - if _config.tdx_enabled { - vm_type = KVM_X86_SW_PROTECTED_VM.into(); - } else { - vm_type = KVM_X86_DEFAULT_VM.into(); - } - } else { - vm_type = KVM_X86_DEFAULT_VM.into(); + { + vm_type = KVM_X86_DEFAULT_VM.into(); + + #[cfg(feature = "sev_snp")] + if _config.sev_snp_enabled { + vm_type = KVM_X86_SNP_VM.into(); + } + + #[cfg(feature = "tdx")] + if _config.tdx_enabled { + vm_type = KVM_X86_SW_PROTECTED_VM.into(); } } @@ -1433,10 +1461,35 @@ impl hypervisor::Hypervisor for KvmHypervisor { guest_memfds = Some(RwLock::new(HashMap::new())); } + #[cfg(feature = "sev_snp")] + let sev_fd = { + let sev_snp_enabled = vm_type == KVM_X86_SNP_VM as u64; + if sev_snp_enabled { + let mask = self.kvm.check_extension_int(crate::kvm::Cap::ExitHypercall); + let cap = kvm_bindings::kvm_enable_cap { + cap: kvm_bindings::KVM_CAP_EXIT_HYPERCALL, + args: [mask as _, 0, 0, 0], + ..Default::default() + }; + fd.enable_cap(&cap) + .map_err(|e| hypervisor::HypervisorError::VmCreate(e.into()))?; + let sev_dev = x86_64::sev::SevFd::new("/dev/sev") + .map_err(|e| hypervisor::HypervisorError::SevSnpCapabilities(e.into()))?; + sev_dev + .init2(&fd, _config.vmsa_features) + .map_err(|e| hypervisor::HypervisorError::VmCreate(e.into()))?; + Some(sev_dev) + } else { + None + } + }; + Ok(Arc::new(KvmVm { - fd, + fd: Arc::new(fd), msrs, dirty_log_slots: RwLock::new(HashMap::new()), + #[cfg(feature = "sev_snp")] + sev_fd, guest_memfds, })) } @@ -1444,7 +1497,7 @@ impl hypervisor::Hypervisor for KvmHypervisor { #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] { Ok(Arc::new(KvmVm { - fd, + fd: Arc::new(fd), dirty_log_slots: RwLock::new(HashMap::new()), guest_memfds: None, })) diff --git a/hypervisor/src/kvm/x86_64/mod.rs b/hypervisor/src/kvm/x86_64/mod.rs index e338346c3f..62185fd84e 100644 --- a/hypervisor/src/kvm/x86_64/mod.rs +++ b/hypervisor/src/kvm/x86_64/mod.rs @@ -31,6 +31,9 @@ use crate::arch::x86::{ }; use crate::kvm::{Cap, Kvm, KvmError, KvmResult}; +#[cfg(feature = "sev_snp")] +pub(crate) mod sev; + /// /// Check KVM extension for Linux /// diff --git a/hypervisor/src/kvm/x86_64/sev.rs b/hypervisor/src/kvm/x86_64/sev.rs new file mode 100644 index 0000000000..d3497bed3b --- /dev/null +++ b/hypervisor/src/kvm/x86_64/sev.rs @@ -0,0 +1,113 @@ +// Copyright 2025 Google LLC. +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::fs::OpenOptions; +use std::os::fd::{AsRawFd, OwnedFd}; +use std::os::unix::fs::OpenOptionsExt; +use std::path::Path; + +use igvm_defs::SnpPolicy; +use kvm_bindings::kvm_sev_cmd; +use kvm_ioctls::VmFd; +use log::{error, info}; +use vmm_sys_util::errno; + +pub(crate) type Result = std::result::Result; + +// KVM SEV command IDs — linux/include/uapi/linux/kvm.h +const KVM_SEV_INIT2: u32 = 22; +const KVM_SEV_SNP_LAUNCH_START: u32 = 100; + +// SNP in VMSA - linux/arch/x86/include/asm/svm.h +const SVM_SEV_FEAT_SNP_ACTIVE: u64 = 1 << 0; + +fn sev_op(vm: &VmFd, sev_cmd: &mut kvm_sev_cmd, name: &str) -> Result<()> { + let ret = vm.encrypt_op_sev(sev_cmd); + if ret.is_err() { + error!("{name} op failed. error code: 0x{:x}", sev_cmd.error); + } + ret +} + +#[derive(Debug)] +pub struct SevFd { + pub fd: OwnedFd, +} + +// These ioctl structs must match the kernel layout exactly. +// Layouts from linux/arch/x86/include/uapi/asm/kvm.h + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct KvmSevInit { + pub vmsa_features: u64, + pub flags: u32, + pub ghcb_version: u16, + pub pad1: u16, + pub pad2: [u32; 8], +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct KvmSevSnpLaunchStart { + pub policy: u64, + pub gosvw: [u8; 16], + pub flags: u16, + pub pad0: [u8; 6], + pub pad1: [u64; 4], +} + +impl SevFd { + pub(crate) fn new(sev_path: impl AsRef) -> Result { + let file = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(libc::O_CLOEXEC) + .open(sev_path.as_ref()) + .map_err(|e| errno::Error::new(e.raw_os_error().unwrap_or(libc::EINVAL)))?; + Ok(SevFd { + fd: OwnedFd::from(file), + }) + } + + pub(crate) fn init2(&self, vm: &VmFd, vmsa_features: u64) -> Result<()> { + // Clear the SNP bit, KVM sets it directly + let vmsa_features = vmsa_features & !SVM_SEV_FEAT_SNP_ACTIVE; + + // TODO: Query KVM for supported VMSA features before calling init2 + if vmsa_features != 0 { + info!("SEV-SNP: requesting vmsa_features: {vmsa_features:#x}"); + } + + let mut init = KvmSevInit { + vmsa_features, + ..Default::default() + }; + let mut sev_cmd = kvm_sev_cmd { + id: KVM_SEV_INIT2, + data: &mut init as *mut KvmSevInit as _, + sev_fd: self.fd.as_raw_fd() as _, + ..Default::default() + }; + sev_op(vm, &mut sev_cmd, "KVM_SEV_INIT2") + } + + pub(crate) fn launch_start(&self, vm: &VmFd, guest_policy: SnpPolicy) -> Result<()> { + // See AMD Spec Section 4.3 - Guest Policy + // Bit 17 is reserved and has to be one. + // https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP + let mut start: KvmSevSnpLaunchStart = KvmSevSnpLaunchStart { + policy: guest_policy.into_bits(), + ..Default::default() + }; + let mut sev_cmd = kvm_sev_cmd { + id: KVM_SEV_SNP_LAUNCH_START, + data: &mut start as *mut KvmSevSnpLaunchStart as _, + sev_fd: self.fd.as_raw_fd() as _, + ..Default::default() + }; + sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_START") + } +} diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 8357e63b79..f224e7217c 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -190,6 +190,8 @@ pub struct HypervisorVmConfig { pub sev_snp_enabled: bool, #[cfg(feature = "sev_snp")] pub mem_size: u64, + #[cfg(feature = "sev_snp")] + pub vmsa_features: u64, pub nested: bool, pub smt_enabled: bool, } diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index e6787d19ef..36aae27b08 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -394,9 +394,7 @@ pub trait Vm: Send + Sync + Any { fn get_dirty_log(&self, slot: u32, base_gpa: u64, memory_size: u64) -> Result>; #[cfg(feature = "sev_snp")] /// Initialize SEV-SNP on this VM - fn sev_snp_init(&self, _guest_policy: SnpPolicy) -> Result<()> { - unimplemented!() - } + fn sev_snp_init(&self, guest_policy: SnpPolicy) -> Result<()>; #[cfg(feature = "tdx")] /// Initialize TDX on this VM fn tdx_init(&self, _cpuid: &[CpuIdEntry], _max_vcpus: u32) -> Result<()> { diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 948afdfae7..dc3d827e73 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -957,26 +957,26 @@ pub struct AcpiPlatformAddresses { pub sleep_status_reg_address: Option, } -#[cfg(all(feature = "mshv", feature = "sev_snp"))] +#[cfg(feature = "sev_snp")] struct SevSnpPageAccessProxy { vm: Arc, } -#[cfg(all(feature = "mshv", feature = "sev_snp"))] +#[cfg(feature = "sev_snp")] impl std::fmt::Debug for SevSnpPageAccessProxy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "SNP Page access proxy") } } -#[cfg(all(feature = "mshv", feature = "sev_snp"))] +#[cfg(feature = "sev_snp")] impl SevSnpPageAccessProxy { fn new(vm: Arc) -> SevSnpPageAccessProxy { SevSnpPageAccessProxy { vm } } } -#[cfg(all(feature = "mshv", feature = "sev_snp"))] +#[cfg(feature = "sev_snp")] impl AccessPlatform for SevSnpPageAccessProxy { fn translate_gpa(&self, base: u64, _size: u64) -> std::result::Result { Ok(base) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 444fbcc539..d7a8f0cae8 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -129,6 +129,21 @@ fn import_parameter( Ok(()) } +/// +/// Extract sev_features from the boot CPU (vp_index 0) VMSA. +/// +#[cfg(feature = "sev_snp")] +pub fn extract_sev_features(igvm_file: &IgvmFile) -> u64 { + for header in igvm_file.directives() { + if let IgvmDirectiveHeader::SnpVpContext { vp_index, vmsa, .. } = header + && *vp_index == 0 + { + return vmsa.sev_features.into(); + } + } + 0 +} + /// /// Load the given IGVM file to guest memory. /// Right now it only supports SNP based isolation. diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index f98c5b7258..b0117d729c 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -234,6 +234,8 @@ impl From<&VmConfig> for hypervisor::HypervisorVmConfig { sev_snp_enabled: _value.is_sev_snp_enabled(), #[cfg(feature = "sev_snp")] mem_size: _value.memory.total_size(), + #[cfg(feature = "sev_snp")] + vmsa_features: 0, nested: _value.cpus.nested, smt_enabled: _value .cpus diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 008fc60f29..1258959434 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -111,6 +111,7 @@ mod kvm { pub const KVM_NMI: u64 = 0xae9a; pub const KVM_GET_NESTED_STATE: u64 = 3229658814; pub const KVM_SET_NESTED_STATE: u64 = 1082175167; + pub const KVM_SEV_SNP_LAUNCH_START: u64 = 0x4018_aeb4; } mod iommufd { @@ -267,6 +268,7 @@ fn create_vmm_ioctl_seccomp_rule_common_kvm() -> Result, Backen and![Cond::new(1, ArgLen::Dword, Eq, KVM_NMI)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_NESTED_STATE)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_NESTED_STATE)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_SEV_SNP_LAUNCH_START)?], ]) } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index fe74e6a75e..74ca6a7ed0 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1327,10 +1327,16 @@ impl Vm { .map_err(Error::IgvmLoad)? }; - let vm = Self::create_hypervisor_vm( - hypervisor.as_ref(), - vm_config.as_ref().lock().unwrap().deref().into(), - )?; + let vm = { + #[allow(unused_mut)] + let mut hv_config: hypervisor::HypervisorVmConfig = + vm_config.as_ref().lock().unwrap().deref().into(); + #[cfg(all(feature = "igvm", feature = "sev_snp"))] + if let Some(ref igvm) = igvm_file { + hv_config.vmsa_features = igvm_loader::extract_sev_features(igvm); + } + Self::create_hypervisor_vm(hypervisor.as_ref(), hv_config)? + }; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] if vm_config.lock().unwrap().max_apic_id() > MAX_SUPPORTED_CPUS_LEGACY { From 24db5e1efd4c5db19bb5e9e86352622c01b59bb0 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 16:43:32 -0700 Subject: [PATCH 1313/1893] 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 Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Signed-off-by: Ruben Hakobyan --- hypervisor/src/kvm/mod.rs | 40 +++++++++++++++++++++++++++ hypervisor/src/kvm/x86_64/sev.rs | 47 ++++++++++++++++++++++++++++++++ hypervisor/src/mshv/mod.rs | 1 + hypervisor/src/vm.rs | 1 + vmm/src/igvm/igvm_loader.rs | 13 +++++++++ vmm/src/seccomp_filters.rs | 2 ++ 6 files changed, 104 insertions(+) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index a697d5df79..db18c64169 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -142,6 +142,8 @@ ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a); #[cfg(feature = "sev_snp")] use kvm_bindings::{KVM_MEMORY_ATTRIBUTE_PRIVATE, KVM_X86_SNP_VM, kvm_memory_attributes}; +#[cfg(feature = "sev_snp")] +use x86_64::sev; #[cfg(feature = "tdx")] const KVM_EXIT_TDX: u32 = 50; @@ -635,6 +637,44 @@ impl vm::Vm for KvmVm { .map_err(|e| vm::HypervisorVmError::InitializeSevSnp(e.into())) } + #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] + fn import_isolated_pages( + &self, + page_type: u32, + page_size: u32, + // host page frame numbers + pfns: &[u64], + uaddrs: &[u64], + ) -> vm::Result<()> { + if pfns.is_empty() { + return Ok(()); + } + assert_eq!(pfns.len(), uaddrs.len()); + // VMSA pages are not supported by launch_update + // https://elixir.bootlin.com/linux/v6.11/source/arch/x86/kvm/svm/sev.c#L2377 + if page_type == sev::SNP_PAGE_TYPE_VMSA { + return Ok(()); + } + for i in 0..pfns.len() { + self.fd + .set_memory_attributes(kvm_memory_attributes { + address: pfns[i] << sev::GPA_METADATA_SHIFT_OFFSET, + size: page_size as u64, + attributes: kvm_bindings::KVM_MEMORY_ATTRIBUTE_PRIVATE as u64, + // Flags must be zero o/w error (flags aren't being used here yet) + flags: 0, + }) + .map_err(|e| vm::HypervisorVmError::ImportIsolatedPages(e.into()))?; + self.sev_fd + .as_ref() + .unwrap() + .launch_update(&self.fd, uaddrs[i], page_size as u64, pfns[i], page_type) + .map_err(|e| vm::HypervisorVmError::ImportIsolatedPages(e.into()))?; + } + + Ok(()) + } + #[cfg(target_arch = "x86_64")] /// /// Sets the address of the one-page region in the VM's address space. diff --git a/hypervisor/src/kvm/x86_64/sev.rs b/hypervisor/src/kvm/x86_64/sev.rs index d3497bed3b..a2aebdd191 100644 --- a/hypervisor/src/kvm/x86_64/sev.rs +++ b/hypervisor/src/kvm/x86_64/sev.rs @@ -19,6 +19,14 @@ pub(crate) type Result = std::result::Result; // KVM SEV command IDs — linux/include/uapi/linux/kvm.h const KVM_SEV_INIT2: u32 = 22; const KVM_SEV_SNP_LAUNCH_START: u32 = 100; +const KVM_SEV_SNP_LAUNCH_UPDATE: u32 = 101; +// SNP_LAUNCH_UPDATE page types — linux/arch/x86/include/uapi/asm/sev-guest.h +pub const SNP_PAGE_TYPE_VMSA: u32 = 2; + +// See AMD Spec Section 8.17 — SNP_LAUNCH_UPDATE +// The last 12 bits are metadata about the guest context +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +pub const GPA_METADATA_SHIFT_OFFSET: u32 = 12; // SNP in VMSA - linux/arch/x86/include/asm/svm.h const SVM_SEV_FEAT_SNP_ACTIVE: u64 = 1 << 0; @@ -59,6 +67,19 @@ pub(crate) struct KvmSevSnpLaunchStart { pub pad1: [u64; 4], } +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct KvmSevSnpLaunchUpdate { + pub gfn_start: u64, + pub uaddr: u64, + pub len: u64, + pub type_: u8, + pub pad0: u8, + pub flags: u16, + pub pad1: u32, + pub pad2: [u64; 4], +} + impl SevFd { pub(crate) fn new(sev_path: impl AsRef) -> Result { let file = OpenOptions::new() @@ -110,4 +131,30 @@ impl SevFd { }; sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_START") } + + pub(crate) fn launch_update( + &self, + vm: &VmFd, + // host virtual address + hva: u64, + size: u64, + // guest frame number + gfn_start: u64, + page_type: u32, + ) -> Result<()> { + let mut update = KvmSevSnpLaunchUpdate { + gfn_start, + uaddr: hva, + len: size, + type_: page_type as u8, + ..Default::default() + }; + let mut sev_cmd = kvm_sev_cmd { + id: KVM_SEV_SNP_LAUNCH_UPDATE, + data: &mut update as *mut KvmSevSnpLaunchUpdate as _, + sev_fd: self.fd.as_raw_fd() as _, + ..Default::default() + }; + sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_UPDATE") + } } diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 1119691273..a61f2e44ef 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -2272,6 +2272,7 @@ impl vm::Vm for MshvVm { page_type: u32, page_size: u32, pages: &[u64], + _uaddrs: &[u64], ) -> vm::Result<()> { debug_assert!(page_size == hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB); if pages.is_empty() { diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 36aae27b08..6d3a4a4ae5 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -429,6 +429,7 @@ pub trait Vm: Send + Sync + Any { _page_type: u32, _page_size: u32, _pages: &[u64], + _uaddrs: &[u64], ) -> Result<()> { unimplemented!() } diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index d7a8f0cae8..e22841c88d 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -19,6 +19,8 @@ use log::debug; use log::info; use mshv_bindings::*; use thiserror::Error; +#[cfg(feature = "sev_snp")] +use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemory}; use zerocopy::IntoBytes; #[cfg(feature = "sev_snp")] @@ -471,6 +473,16 @@ pub fn load_igvm( .iter() .map(|gpa| gpa.gpa >> HV_HYP_PAGE_SHIFT) .collect(); + let guest_memory = memory_manager.lock().unwrap().guest_memory().memory(); + let uaddrs: Vec<_> = group + .iter() + .map(|gpa| { + let guest_region_mmap = guest_memory.to_region_addr(GuestAddress(gpa.gpa)); + let uaddr_base = guest_region_mmap.unwrap().0.as_ptr() as u64; + let uaddr_offset: u64 = guest_region_mmap.unwrap().1.0; + uaddr_base + uaddr_offset + }) + .collect(); memory_manager .lock() .unwrap() @@ -479,6 +491,7 @@ pub fn load_igvm( group[0].page_type, hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, &pfns, + &uaddrs, ) .map_err(Error::ImportIsolatedPages)?; } diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 1258959434..748e3b3b60 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -112,6 +112,7 @@ mod kvm { pub const KVM_GET_NESTED_STATE: u64 = 3229658814; pub const KVM_SET_NESTED_STATE: u64 = 1082175167; pub const KVM_SEV_SNP_LAUNCH_START: u64 = 0x4018_aeb4; + pub const KVM_SEV_SNP_LAUNCH_UPDATE: u64 = 0x8018_aeb5; } mod iommufd { @@ -269,6 +270,7 @@ fn create_vmm_ioctl_seccomp_rule_common_kvm() -> Result, Backen and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_NESTED_STATE)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_NESTED_STATE)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SEV_SNP_LAUNCH_START)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_SEV_SNP_LAUNCH_UPDATE)?], ]) } From 4b2538f522f0babd634b8c68885bb1d094cd33cd Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 16:57:42 -0700 Subject: [PATCH 1314/1893] 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 Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Signed-off-by: Ruben Hakobyan --- hypervisor/src/kvm/mod.rs | 19 +++++++++++++++ hypervisor/src/kvm/x86_64/sev.rs | 42 +++++++++++++++++++++++++++++++- vmm/src/seccomp_filters.rs | 2 ++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index db18c64169..8cf43de4e5 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -675,6 +675,25 @@ impl vm::Vm for KvmVm { Ok(()) } + #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] + fn complete_isolated_import( + &self, + snp_id_block: igvm_defs::IGVM_VHS_SNP_ID_BLOCK, + host_data: [u8; 32], + id_block_enabled: u8, + ) -> vm::Result<()> { + self.sev_fd + .as_ref() + .unwrap() + .launch_finish( + &self.fd, + host_data, + id_block_enabled, + snp_id_block.author_key_enabled, + ) + .map_err(|e| vm::HypervisorVmError::CompleteIsolatedImport(e.into())) + } + #[cfg(target_arch = "x86_64")] /// /// Sets the address of the one-page region in the VM's address space. diff --git a/hypervisor/src/kvm/x86_64/sev.rs b/hypervisor/src/kvm/x86_64/sev.rs index a2aebdd191..6249468fef 100644 --- a/hypervisor/src/kvm/x86_64/sev.rs +++ b/hypervisor/src/kvm/x86_64/sev.rs @@ -11,7 +11,7 @@ use std::path::Path; use igvm_defs::SnpPolicy; use kvm_bindings::kvm_sev_cmd; use kvm_ioctls::VmFd; -use log::{error, info}; +use log::{debug, error, info}; use vmm_sys_util::errno; pub(crate) type Result = std::result::Result; @@ -20,6 +20,7 @@ pub(crate) type Result = std::result::Result; const KVM_SEV_INIT2: u32 = 22; const KVM_SEV_SNP_LAUNCH_START: u32 = 100; const KVM_SEV_SNP_LAUNCH_UPDATE: u32 = 101; +const KVM_SEV_SNP_LAUNCH_FINISH: u32 = 102; // SNP_LAUNCH_UPDATE page types — linux/arch/x86/include/uapi/asm/sev-guest.h pub const SNP_PAGE_TYPE_VMSA: u32 = 2; @@ -80,6 +81,21 @@ pub(crate) struct KvmSevSnpLaunchUpdate { pub pad2: [u64; 4], } +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct KvmSevSnpLaunchFinish { + pub id_block_uaddr: u64, + pub id_auth_uaddr: u64, + pub id_block_en: u8, + pub auth_key_en: u8, + pub vcek_disabled: u8, + pub host_data: [u8; 32], + pub pad0: [u8; 3], + // must be zero https://elixir.bootlin.com/linux/v6.11/source/arch/x86/kvm/svm/sev.c#L2506 + pub flags: u16, + pub pad1: [u64; 4], +} + impl SevFd { pub(crate) fn new(sev_path: impl AsRef) -> Result { let file = OpenOptions::new() @@ -157,4 +173,28 @@ impl SevFd { }; sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_UPDATE") } + + pub(crate) fn launch_finish( + &self, + vm: &VmFd, + host_data: [u8; 32], + id_block_en: u8, + auth_key_en: u8, + ) -> Result<()> { + let mut finish = KvmSevSnpLaunchFinish { + host_data, + id_block_en, + auth_key_en, + ..Default::default() + }; + let mut sev_cmd = kvm_sev_cmd { + id: KVM_SEV_SNP_LAUNCH_FINISH, + data: &mut finish as *mut KvmSevSnpLaunchFinish as _, + sev_fd: self.fd.as_raw_fd() as _, + ..Default::default() + }; + let flags = finish.flags; + debug!("Calling KVM_SEV_SNP_LAUNCH_FINISH, flags: {flags}"); + sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_FINISH") + } } diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 748e3b3b60..bc17cde4e8 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -113,6 +113,7 @@ mod kvm { pub const KVM_SET_NESTED_STATE: u64 = 1082175167; pub const KVM_SEV_SNP_LAUNCH_START: u64 = 0x4018_aeb4; pub const KVM_SEV_SNP_LAUNCH_UPDATE: u64 = 0x8018_aeb5; + pub const KVM_SEV_SNP_LAUNCH_FINISH: u64 = 0x4008_aeb7; } mod iommufd { @@ -271,6 +272,7 @@ fn create_vmm_ioctl_seccomp_rule_common_kvm() -> Result, Backen and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_NESTED_STATE)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SEV_SNP_LAUNCH_START)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SEV_SNP_LAUNCH_UPDATE)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_SEV_SNP_LAUNCH_FINISH)?], ]) } From 4a0cfa02de9d90d0f4024f718a96cc79cc5fe190 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 17:04:58 -0700 Subject: [PATCH 1315/1893] 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 Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Signed-off-by: Ruben Hakobyan --- hypervisor/src/kvm/mod.rs | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 8cf43de4e5..27a5358728 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -32,6 +32,8 @@ use anyhow::anyhow; #[cfg(feature = "sev_snp")] use kvm_bindings::kvm_create_guest_memfd; use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd}; +#[cfg(feature = "sev_snp")] +use log::debug; #[cfg(target_arch = "x86_64")] use log::warn; use vmm_sys_util::errno; @@ -140,6 +142,8 @@ use crate::kvm::x86_64::XsaveStateError; #[cfg(target_arch = "x86_64")] ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a); +#[cfg(feature = "sev_snp")] +use igvm_defs::PAGE_SIZE_4K; #[cfg(feature = "sev_snp")] use kvm_bindings::{KVM_MEMORY_ATTRIBUTE_PRIVATE, KVM_X86_SNP_VM, kvm_memory_attributes}; #[cfg(feature = "sev_snp")] @@ -774,6 +778,8 @@ impl vm::Vm for KvmVm { hyperv_synic: AtomicBool::new(false), #[cfg(target_arch = "x86_64")] xsave_size, + #[cfg(feature = "sev_snp")] + vm_fd: self.fd.clone(), }; Ok(Box::new(vcpu)) } @@ -1643,6 +1649,8 @@ pub struct KvmVcpu { hyperv_synic: AtomicBool, #[cfg(target_arch = "x86_64")] xsave_size: i32, + #[cfg(feature = "sev_snp")] + vm_fd: Arc, } /// Implementation of Vcpu trait for KVM @@ -2302,6 +2310,49 @@ impl cpu::Vcpu for KvmVcpu { #[cfg(feature = "tdx")] VcpuExit::Unsupported(KVM_EXIT_TDX) => Ok(cpu::VmExit::Tdx), VcpuExit::Debug(_) => Ok(cpu::VmExit::Debug), + #[cfg(feature = "sev_snp")] + VcpuExit::Hypercall(hypercall) => { + // https://docs.kernel.org/virt/kvm/x86/hypercalls.html#kvm-hc-map-gpa-range + const KVM_HC_MAP_GPA_RANGE: u64 = 12; + // 4th bit of attributes argument is encrypted page bit + match hypercall.nr { + KVM_HC_MAP_GPA_RANGE => { + // guest physical address of start page + let address = hypercall.args[0]; + // num pages to map from start address + let num_pages = hypercall.args[1]; + // bits[0-3] = page size encoding + // bits[4] = 1 if private, 0 if shared + // bits[5-63] = zero + let attributes = hypercall.args[2]; + // TODO: Add 2mb page support + let size = num_pages * PAGE_SIZE_4K; + // bit 4 = private attribute encoding + const PRIVATE_ENCODING_BITMASK: u64 = 0b10000; + debug!( + "KVM_HC_MAP_GPA_RANGE: address={address:#x}, pages={num_pages}, attributes={attributes:#x}" + ); + let set_private_attr = if attributes & PRIVATE_ENCODING_BITMASK > 0 { + KVM_MEMORY_ATTRIBUTE_PRIVATE as u64 + } else { + // the only attribute available is private, o/w 0 + // https://docs.kernel.org/virt/kvm/api.html#kvm-set-memory-attributes + 0u64 + }; + let mem_attributes = kvm_memory_attributes { + address, + size, + attributes: set_private_attr, + ..Default::default() + }; + self.vm_fd + .set_memory_attributes(mem_attributes) + .map(|_| cpu::VmExit::Ignore) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into())) + } + _ => Ok(cpu::VmExit::Ignore), + } + } r => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( "Unexpected exit reason on vcpu run: {r:?}" From b5ddcdc74ad286c0a88a00ac9637f3e19c460d17 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 17:21:15 -0700 Subject: [PATCH 1316/1893] 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 --- hypervisor/src/kvm/mod.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 27a5358728..8b2e296ef0 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -2354,6 +2354,38 @@ impl cpu::Vcpu for KvmVcpu { } } + #[cfg(feature = "sev_snp")] + VcpuExit::MemoryFault { flags, gpa, size } => { + debug!("VcpuExit::MemoryFault: flags={flags:#x}, gpa={gpa:#x}, size={size:#x}"); + + const KVM_MEMORY_EXIT_FLAG_PRIVATE: u64 = + kvm_bindings::KVM_MEMORY_EXIT_FLAG_PRIVATE as u64; + + if flags & !KVM_MEMORY_EXIT_FLAG_PRIVATE != 0 { + return Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( + "VcpuExit::MemoryFault: unknown flags {flags:#x}" + ))); + } + + let attributes = if flags & KVM_MEMORY_EXIT_FLAG_PRIVATE != 0 { + KVM_MEMORY_ATTRIBUTE_PRIVATE as u64 + } else { + // the only attribute available is private, o/w 0 + // https://docs.kernel.org/virt/kvm/api.html#kvm-set-memory-attributes + 0u64 + }; + + self.vm_fd + .set_memory_attributes(kvm_memory_attributes { + address: gpa, + size, + attributes, + flags: 0, + }) + .map(|_| cpu::VmExit::Ignore) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into())) + } + r => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( "Unexpected exit reason on vcpu run: {r:?}" ))), From 75ed2c9f903f44356355e6b94d2d3b0784ffc478 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 17:30:24 -0700 Subject: [PATCH 1317/1893] 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 Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Co-authored-by: Dylan Reid Signed-off-by: Dylan Reid Signed-off-by: Ruben Hakobyan --- hypervisor/src/cpu.rs | 4 + hypervisor/src/kvm/mod.rs | 128 ++++++++++++++- vmm/src/config.rs | 28 +++- vmm/src/cpu.rs | 17 +- vmm/src/igvm/igvm_loader.rs | 318 ++++++++++++++++++++++++++++++------ 5 files changed, 435 insertions(+), 60 deletions(-) diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index a4a029e989..044c81a2e8 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -591,6 +591,10 @@ pub trait Vcpu: Send + Sync { fn set_sev_control_register(&self, _vmsa_pfn: u64) -> Result<()> { unimplemented!() } + #[cfg(feature = "sev_snp")] + fn setup_sev_snp_regs(&self, _vmsa: igvm::snp_defs::SevVmsa) -> Result<()> { + unimplemented!() + } /// /// Sets the value of GIC redistributor address diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 8b2e296ef0..2f7137d55a 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -145,10 +145,61 @@ ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a); #[cfg(feature = "sev_snp")] use igvm_defs::PAGE_SIZE_4K; #[cfg(feature = "sev_snp")] -use kvm_bindings::{KVM_MEMORY_ATTRIBUTE_PRIVATE, KVM_X86_SNP_VM, kvm_memory_attributes}; +use kvm_bindings::{ + KVM_MEMORY_ATTRIBUTE_PRIVATE, KVM_X86_SNP_VM, kvm_memory_attributes, kvm_segment as Segment, +}; +use vm_memory::GuestAddress; #[cfg(feature = "sev_snp")] use x86_64::sev; +// Hardcoded GPA of a bootloader and VMSA page for KVM +// TODO: Derive these from the IGVM file's PageData/SnpVpContext directives +// instead of using fixed constants, to support arbitrary bootloader layouts. +pub const BOOTLOADER_START: GuestAddress = GuestAddress(0xffc0_0000); +pub const BOOTLOADER_SIZE: usize = 0x40_0000; // 4 MiB +pub const KVM_VMSA_PAGE_ADDRESS: GuestAddress = GuestAddress(0xffff_ffff_f000); +pub const KVM_VMSA_PAGE_SIZE: usize = 0x1000; // 4 KiB + +#[cfg(feature = "sev_snp")] +#[bitfield_struct::bitfield(u32)] +#[derive(PartialEq, Eq)] +/// AMD VMCB segment attributes +/// linux/arch/x86/include/asm/svm.h +pub struct SegAccess { + #[bits(4)] + pub seg_type: u8, + pub s_code_data: bool, + #[bits(2)] + pub priv_level: u8, + pub present: bool, + pub available: bool, + pub l_64bit: bool, + pub db_size_32: bool, + pub granularity: bool, + #[bits(20)] + _reserved: u32, +} + +#[cfg(feature = "sev_snp")] +fn make_segment(sev_selector: igvm::snp_defs::SevSelector) -> Segment { + let flags = SegAccess::from_bits(sev_selector.attrib.into()); + Segment { + base: sev_selector.base, + limit: sev_selector.limit, + selector: sev_selector.selector, + type_: flags.seg_type(), + s: flags.s_code_data() as u8, + dpl: flags.priv_level(), + present: flags.present() as u8, + avl: flags.available() as u8, + db: flags.db_size_32() as u8, + g: flags.granularity() as u8, + l: flags.l_64bit() as u8, + unusable: 0, + ..Default::default() + } +} + #[cfg(feature = "tdx")] const KVM_EXIT_TDX: u32 = 50; #[cfg(feature = "tdx")] @@ -3238,6 +3289,81 @@ impl cpu::Vcpu for KvmVcpu { Ok(_) => Ok(()), } } + + #[cfg(feature = "sev_snp")] + fn set_sev_control_register(&self, _vmsa_pfn: u64) -> cpu::Result<()> { + Ok(()) + } + + #[cfg(feature = "sev_snp")] + fn setup_sev_snp_regs(&self, vmsa: igvm::snp_defs::SevVmsa) -> cpu::Result<()> { + let mut sregs = self + .fd + .get_sregs() + .map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::GetSpecialRegs(e.into()))?; + sregs.cs = make_segment(vmsa.cs); + sregs.ds = make_segment(vmsa.ds); + sregs.es = make_segment(vmsa.es); + sregs.fs = make_segment(vmsa.fs); + sregs.gs = make_segment(vmsa.gs); + sregs.ss = make_segment(vmsa.ss); + sregs.tr = make_segment(vmsa.tr); + sregs.ldt = make_segment(vmsa.ldtr); + + sregs.cr0 = vmsa.cr0; + sregs.cr4 = vmsa.cr4; + sregs.cr3 = vmsa.cr3; + sregs.efer = vmsa.efer; + + sregs.idt.base = vmsa.idtr.base; + sregs.idt.limit = vmsa + .idtr + .limit + .try_into() + .map_err(|e: std::num::TryFromIntError| { + cpu::HypervisorCpuError::SetSpecialRegs(anyhow!(e)) + })?; + sregs.gdt.base = vmsa.gdtr.base; + sregs.gdt.limit = vmsa + .gdtr + .limit + .try_into() + .map_err(|e: std::num::TryFromIntError| { + cpu::HypervisorCpuError::SetSpecialRegs(anyhow!(e)) + })?; + self.fd + .set_sregs(&sregs) + .map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::SetSpecialRegs(e.into()))?; + + let mut regs = self + .fd + .get_regs() + .map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::GetRegister(e.into()))?; + regs.rip = vmsa.rip; + regs.rdx = vmsa.rdx; + regs.rflags = vmsa.rflags; + regs.rsp = vmsa.rsp; + regs.rax = vmsa.rax; + regs.rbx = vmsa.rbx; + regs.rcx = vmsa.rcx; + regs.rbp = vmsa.rbp; + regs.rsi = vmsa.rsi; + regs.rdi = vmsa.rdi; + regs.r8 = vmsa.r8; + regs.r9 = vmsa.r9; + regs.r10 = vmsa.r10; + regs.r11 = vmsa.r11; + regs.r12 = vmsa.r12; + regs.r13 = vmsa.r13; + regs.r14 = vmsa.r14; + regs.r15 = vmsa.r15; + + self.fd + .set_regs(®s) + .map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::SetRegister(e.into()))?; + + Ok(()) + } } impl KvmVcpu { diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 72f0f6d47f..d023f98a1b 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -352,6 +352,9 @@ pub enum ValidationError { #[cfg(feature = "sev_snp")] #[error("Invalid host data format")] InvalidHostData, + #[cfg(all(feature = "sev_snp", feature = "igvm"))] + #[error("SEV-SNP requires an IGVM payload (--payload igvm=)")] + SevSnpRequiresIgvm, /// Restore expects all net ids that have fds #[error("Net id {0} is associated with FDs and is required")] RestoreMissingRequiredNetId(String), @@ -2823,12 +2826,25 @@ impl VmConfig { #[cfg(feature = "sev_snp")] { - let host_data_opt = &self.payload.as_ref().unwrap().host_data; - - if let Some(host_data) = host_data_opt - && host_data.len() != 64 - { - return Err(ValidationError::InvalidHostData); + let sev_snp_enabled = self.platform.as_ref().is_some_and(|p| p.sev_snp); + if sev_snp_enabled { + let host_data_opt = &self.payload.as_ref().unwrap().host_data; + if let Some(host_data) = host_data_opt + && host_data.len() != 64 + { + return Err(ValidationError::InvalidHostData); + } + // KVM SEV-SNP requires an IGVM payload to initialise the VMSA. + // Without IGVM the vCPU register state is undefined and VM entry fails. + #[cfg(feature = "igvm")] + if self + .payload + .as_ref() + .and_then(|p| p.igvm.as_ref()) + .is_none() + { + return Err(ValidationError::SevSnpRequiresIgvm); + } } } // The 'conflict' check is introduced in commit 24438e0390d3 diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 261f4c1c75..cb445cda15 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -212,6 +212,9 @@ pub enum Error { #[cfg(feature = "sev_snp")] #[error("Failed to set sev control register")] SetSevControlRegister(#[source] hypervisor::HypervisorCpuError), + #[cfg(feature = "sev_snp")] + #[error("Failed to set up SEV-SNP vCPU registers")] + SetupSevSnpRegs(#[source] hypervisor::HypervisorCpuError), #[cfg(target_arch = "x86_64")] #[error("Failed to inject NMI")] @@ -644,6 +647,13 @@ impl Vcpu { .map_err(Error::SetSevControlRegister) } + #[cfg(feature = "sev_snp")] + pub fn setup_sev_snp_regs(&self, vmsa: igvm::snp_defs::SevVmsa) -> Result<()> { + self.vcpu + .setup_sev_snp_regs(vmsa) + .map_err(Error::SetupSevSnpRegs) + } + /// /// Sets the vCPU's GIC redistributor base address. /// @@ -2199,7 +2209,7 @@ impl CpuManager { &self.vcpus_kill_signalled } - #[cfg(feature = "igvm")] + #[cfg(all(feature = "igvm", feature = "mshv"))] pub(crate) fn get_cpuid_leaf( &self, cpu_id: u8, @@ -2222,6 +2232,11 @@ impl CpuManager { self.sev_snp_enabled } + #[cfg(feature = "igvm")] + pub(crate) fn hypervisor_type(&self) -> hypervisor::HypervisorType { + self.hypervisor.hypervisor_type() + } + pub(crate) fn nmi(&mut self) -> Result<()> { self.vcpus_kick_signalled.store(true, Ordering::SeqCst); self.signal_vcpus()?; diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index e22841c88d..5cdb0d01b4 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -7,6 +7,7 @@ use std::ffi::CString; use std::mem::size_of; use std::sync::{Arc, Mutex}; +use hypervisor::HypervisorType; use igvm::snp_defs::SevVmsa; use igvm::{IgvmDirectiveHeader, IgvmFile, IgvmPlatformHeader}; #[cfg(feature = "sev_snp")] @@ -15,13 +16,20 @@ use igvm_defs::{ IGVM_VHS_PARAMETER, IGVM_VHS_PARAMETER_INSERT, IgvmPageDataType, IgvmPlatformType, }; use log::debug; +#[cfg(all(feature = "kvm", feature = "sev_snp"))] +use log::error; #[cfg(feature = "sev_snp")] use log::info; +#[cfg(feature = "mshv")] use mshv_bindings::*; use thiserror::Error; #[cfg(feature = "sev_snp")] -use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemory}; +use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemory}; +#[cfg(all(feature = "kvm", feature = "sev_snp"))] +use vm_migration::Snapshottable; use zerocopy::IntoBytes; +#[cfg(feature = "sev_snp")] +use zerocopy::{FromBytes, FromZeros}; #[cfg(feature = "sev_snp")] use crate::GuestMemoryMmap; @@ -30,6 +38,36 @@ use crate::igvm::loader::Loader; use crate::igvm::{BootPageAcceptance, HV_PAGE_SIZE, IgvmLoadedInfo, StartupMemoryType}; use crate::memory_manager::{Error as MemoryManagerError, MemoryManager}; +#[cfg(feature = "sev_snp")] +const ISOLATED_PAGE_SHIFT: u32 = 12; +#[cfg(feature = "sev_snp")] +const SNP_CPUID_LIMIT: u32 = 64; +// see section 7.1 +// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf +#[cfg(feature = "sev_snp")] +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq, IntoBytes, FromBytes)] +pub struct SnpCpuidFunc { + pub eax_in: u32, + pub ecx_in: u32, + pub xcr0_in: u64, + pub xss_in: u64, + pub eax: u32, + pub ebx: u32, + pub ecx: u32, + pub edx: u32, + pub reserved: u64, +} + +#[cfg(feature = "sev_snp")] +#[repr(C)] +#[derive(Debug, Clone, FromBytes, IntoBytes)] +pub struct SnpCpuidInfo { + pub count: u32, + pub _reserved1: u32, + pub _reserved2: u64, + pub entries: [SnpCpuidFunc; SNP_CPUID_LIMIT as usize], +} #[derive(Debug, Error)] pub enum Error { #[error("command line is not a valid C string")] @@ -54,6 +92,30 @@ pub enum Error { MemoryManager(MemoryManagerError), #[error("IGVM file not provided")] MissingIgvm, + #[error("Error applying VMSA to vCPU registers: {0}")] + SetVmsa(#[source] crate::cpu::Error), +} + +// KVM SNP page types — linux/arch/x86/include/uapi/asm/sev-guest.h +#[cfg(feature = "kvm")] +const KVM_SNP_PAGE_TYPE_NORMAL: u32 = 1; +#[cfg(feature = "kvm")] +const KVM_SNP_PAGE_TYPE_VMSA: u32 = 2; +#[cfg(feature = "kvm")] +const KVM_SNP_PAGE_TYPE_UNMEASURED: u32 = 4; +#[cfg(feature = "kvm")] +const KVM_SNP_PAGE_TYPE_SECRETS: u32 = 5; +#[cfg(feature = "kvm")] +const KVM_SNP_PAGE_TYPE_CPUID: u32 = 6; + +// Consolidated page type/size configuration per hypervisor. +struct PageTypeConfig { + isolated_page_size_4kb: u32, + normal: u32, + unmeasured: u32, + cpuid: u32, + secrets: u32, + vmsa: u32, } #[allow(dead_code)] @@ -151,6 +213,10 @@ pub fn extract_sev_features(igvm_file: &IgvmFile) -> u64 { /// Right now it only supports SNP based isolation. /// We can boot legacy VM with an igvm file without /// any isolation. +/// +/// NOTE: KVM and MSHV have different page type values and CPUID/VMSA handling. +/// Hypervisor-specific code paths are gated by runtime type checks. A future +/// refactor could split these into separate KVM/MSHV loader implementations. #[allow(clippy::needless_pass_by_value)] pub fn load_igvm( igvm_file: IgvmFile, @@ -159,6 +225,28 @@ pub fn load_igvm( cmdline: &str, #[cfg(feature = "sev_snp")] host_data: &Option, ) -> Result, Error> { + let hypervisor_type = cpu_manager.lock().unwrap().hypervisor_type(); + let page_types = match hypervisor_type { + #[cfg(feature = "mshv")] + HypervisorType::Mshv => PageTypeConfig { + isolated_page_size_4kb: mshv_bindings::hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, + normal: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_NORMAL, + unmeasured: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED, + cpuid: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_NORMAL, + secrets: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED, + vmsa: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_VMSA, + }, + #[cfg(feature = "kvm")] + HypervisorType::Kvm => PageTypeConfig { + isolated_page_size_4kb: HV_PAGE_SIZE as u32, + normal: KVM_SNP_PAGE_TYPE_NORMAL, + unmeasured: KVM_SNP_PAGE_TYPE_UNMEASURED, + cpuid: KVM_SNP_PAGE_TYPE_CPUID, + secrets: KVM_SNP_PAGE_TYPE_SECRETS, + vmsa: KVM_SNP_PAGE_TYPE_VMSA, + }, + }; + let mut loaded_info: Box = Box::default(); let command_line = CString::new(cmdline).map_err(Error::InvalidCommandLine)?; let memory = memory_manager.lock().as_ref().unwrap().guest_memory(); @@ -173,6 +261,8 @@ pub fn load_igvm( .map_err(Error::FailedToDecodeHostData)?; } + #[cfg(feature = "sev_snp")] + let sev_snp_enabled = cpu_manager.lock().unwrap().sev_snp_enabled(); let mask = match &igvm_file.platforms()[0] { IgvmPlatformHeader::SupportedPlatform(info) => { debug_assert!(info.platform_type == IgvmPlatformType::SEV_SNP); @@ -205,15 +295,15 @@ pub fn load_igvm( if flags.unmeasured() { gpas.push(GpaPages { gpa: *gpa, - page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED, - page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, + page_type: page_types.unmeasured, + page_size: page_types.isolated_page_size_4kb, }); BootPageAcceptance::ExclusiveUnmeasured } else { gpas.push(GpaPages { gpa: *gpa, - page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_NORMAL, - page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, + page_type: page_types.normal, + page_size: page_types.isolated_page_size_4kb, }); BootPageAcceptance::Exclusive } @@ -221,43 +311,46 @@ pub fn load_igvm( IgvmPageDataType::SECRETS => { gpas.push(GpaPages { gpa: *gpa, - page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_SECRETS, - page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, + page_type: page_types.secrets, + page_size: page_types.isolated_page_size_4kb, }); BootPageAcceptance::SecretsPage } IgvmPageDataType::CPUID_DATA => { - // SAFETY: CPUID is readonly - unsafe { - let cpuid_page_p: *mut hv_psp_cpuid_page = - data.as_ptr() as *mut hv_psp_cpuid_page; // as *mut hv_psp_cpuid_page; - let cpuid_page: &mut hv_psp_cpuid_page = &mut *cpuid_page_p; - for i in 0..cpuid_page.count { - let leaf = cpuid_page.cpuid_leaf_info[i as usize]; - let mut in_leaf = cpu_manager - .lock() - .unwrap() - .get_cpuid_leaf( - 0, - leaf.eax_in, - leaf.ecx_in, - leaf.xfem_in, - leaf.xss_in, - ) - .unwrap(); - if leaf.eax_in == 1 { - in_leaf[2] &= 0x7FFFFFFF; + #[cfg(feature = "mshv")] + if hypervisor_type == HypervisorType::Mshv { + // SAFETY: CPUID is readonly + unsafe { + let cpuid_page_p: *mut hv_psp_cpuid_page = + data.as_ptr() as *mut hv_psp_cpuid_page; // as *mut hv_psp_cpuid_page; + let cpuid_page: &mut hv_psp_cpuid_page = &mut *cpuid_page_p; + for i in 0..cpuid_page.count { + let leaf = cpuid_page.cpuid_leaf_info[i as usize]; + let mut in_leaf = cpu_manager + .lock() + .unwrap() + .get_cpuid_leaf( + 0, + leaf.eax_in, + leaf.ecx_in, + leaf.xfem_in, + leaf.xss_in, + ) + .unwrap(); + if leaf.eax_in == 1 { + in_leaf[2] &= 0x7FFFFFFF; + } + cpuid_page.cpuid_leaf_info[i as usize].eax_out = in_leaf[0]; + cpuid_page.cpuid_leaf_info[i as usize].ebx_out = in_leaf[1]; + cpuid_page.cpuid_leaf_info[i as usize].ecx_out = in_leaf[2]; + cpuid_page.cpuid_leaf_info[i as usize].edx_out = in_leaf[3]; } - cpuid_page.cpuid_leaf_info[i as usize].eax_out = in_leaf[0]; - cpuid_page.cpuid_leaf_info[i as usize].ebx_out = in_leaf[1]; - cpuid_page.cpuid_leaf_info[i as usize].ecx_out = in_leaf[2]; - cpuid_page.cpuid_leaf_info[i as usize].edx_out = in_leaf[3]; } } gpas.push(GpaPages { gpa: *gpa, - page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_CPUID, - page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, + page_type: page_types.cpuid, + page_size: page_types.isolated_page_size_4kb, }); BootPageAcceptance::CpuidPage } @@ -265,9 +358,69 @@ pub fn load_igvm( _ => todo!("unsupported IgvmPageDataType"), }; - loader - .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, data) - .map_err(Error::Loader)?; + #[allow(unused_mut)] + let mut imported_page = false; + #[cfg(all(feature = "kvm", feature = "sev_snp"))] + if hypervisor_type == HypervisorType::Kvm + && *data_type == IgvmPageDataType::CPUID_DATA + { + let mut new_cp = SnpCpuidInfo::new_zeroed(); + + let entries = cpu_manager.lock().unwrap().common_cpuid(); + let cp_count = std::cmp::min(SNP_CPUID_LIMIT as usize, entries.len()); + // TODO: Filter cpuid rather than truncate + for (i, entry) in entries.iter().enumerate().take(cp_count) { + new_cp.entries[i].eax_in = entry.function; + new_cp.entries[i].ecx_in = entry.index; + new_cp.entries[i].eax = entry.eax; + new_cp.entries[i].ebx = entry.ebx; + new_cp.entries[i].ecx = entry.ecx; + new_cp.entries[i].edx = entry.edx; + /* + * Guest kernels will calculate EBX themselves using the 0xD + * subfunctions corresponding to the individual XSAVE areas, so only + * encode the base XSAVE size in the initial leaves, corresponding + * to the initial XCR0=1 state. (https://tinyurl.com/qemu-cpuid) + */ + if new_cp.entries[i].eax_in == 0xd + && (new_cp.entries[i].ecx_in == 0x0 || new_cp.entries[i].ecx_in == 0x1) + { + new_cp.entries[i].ebx = 0x240; + new_cp.entries[i].xcr0_in = 1; + new_cp.entries[i].xss_in = 0; + } + + // KVM SNP launch may reject a CPUID page with bits it intends + // to sanitize internally. Pre-clearing the known unsafe bits keeps + // the CPUID page stable across launch updates. + match (new_cp.entries[i].eax_in, new_cp.entries[i].ecx_in) { + (0x1, 0x0) => { + new_cp.entries[i].ecx &= !(1 << 24); + } + (0x7, 0x0) => { + new_cp.entries[i].ebx &= !0x2; + new_cp.entries[i].edx = 0; + } + (0x80000008, 0x0) => { + new_cp.entries[i].ebx &= !0x0200_0000; + } + (0x80000021, 0x0) => { + new_cp.entries[i].ecx = 0; + } + _ => {} + } + } + new_cp.count = cp_count as u32; + loader + .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, new_cp.as_mut_bytes()) + .map_err(Error::Loader)?; + imported_page = true; + } + if !imported_page { + loader + .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, data) + .map_err(Error::Loader)?; + } } IgvmDirectiveHeader::ParameterArea { number_of_bytes, @@ -299,16 +452,16 @@ pub fn load_igvm( IgvmDirectiveHeader::MmioRanges(_info) => { todo!("unsupported IgvmPageDataType"); } - IgvmDirectiveHeader::MemoryMap(_info) => { + IgvmDirectiveHeader::MemoryMap(_info) => + { #[cfg(feature = "sev_snp")] - { + if sev_snp_enabled { let guest_mem = memory_manager.lock().unwrap().boot_guest_memory(); let memory_map = generate_memory_map(&guest_mem)?; import_parameter(&mut parameter_areas, _info, memory_map.as_bytes())?; + } else { + todo!("Not implemented"); } - - #[cfg(not(feature = "sev_snp"))] - todo!("Not implemented"); } IgvmDirectiveHeader::CommandLine(info) => { import_parameter(&mut parameter_areas, info, command_line.as_bytes_with_nul())?; @@ -336,7 +489,7 @@ pub fn load_igvm( vmsa, } => { assert_eq!(gpa % HV_PAGE_SIZE, 0); - let mut data: [u8; 4096] = [0; 4096]; + let mut data: [u8; HV_PAGE_SIZE as usize] = [0; HV_PAGE_SIZE as usize]; let len = size_of::(); loaded_info.vmsa_gpa = *gpa; loaded_info.vmsa = **vmsa; @@ -348,10 +501,28 @@ pub fn load_igvm( .map_err(Error::Loader)?; } + // Set vCPU initial register state from VMSA before SNP_LAUNCH_FINISH + #[cfg(all(feature = "kvm", feature = "sev_snp"))] + if hypervisor_type == HypervisorType::Kvm { + let vcpus = cpu_manager.lock().unwrap().vcpus(); + for vcpu in vcpus { + let vcpu_locked = vcpu.lock().unwrap(); + let vcpu_id: u16 = vcpu_locked.id().parse().unwrap(); + if vcpu_id == *vp_index { + vcpu_locked + .setup_sev_snp_regs(loaded_info.vmsa) + .map_err(Error::SetVmsa)?; + vcpu_locked + .set_sev_control_register(0) + .map_err(Error::SetVmsa)?; + } + } + } + gpas.push(GpaPages { gpa: *gpa, - page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_VMSA, - page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, + page_type: page_types.vmsa, + page_size: page_types.isolated_page_size_4kb, }); } IgvmDirectiveHeader::SnpIdBlock { @@ -419,8 +590,8 @@ pub fn load_igvm( *area = ParameterAreaState::Inserted; gpas.push(GpaPages { gpa: *gpa, - page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED, - page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, + page_type: page_types.unmeasured, + page_size: page_types.isolated_page_size_4kb, }); } IgvmDirectiveHeader::ErrorRange { .. } => { @@ -433,7 +604,7 @@ pub fn load_igvm( } #[cfg(feature = "sev_snp")] - { + if sev_snp_enabled { memory_manager .lock() .unwrap() @@ -471,7 +642,7 @@ pub fn load_igvm( // of PFN for importing the isolated pages let pfns: Vec = group .iter() - .map(|gpa| gpa.gpa >> HV_HYP_PAGE_SHIFT) + .map(|gpa| gpa.gpa >> ISOLATED_PAGE_SHIFT) .collect(); let guest_memory = memory_manager.lock().unwrap().guest_memory().memory(); let uaddrs: Vec<_> = group @@ -483,17 +654,50 @@ pub fn load_igvm( uaddr_base + uaddr_offset }) .collect(); - memory_manager + #[cfg(feature = "kvm")] + let page_type = group[0].page_type; + let mut new_cp = SnpCpuidInfo::new_zeroed(); + let _ = guest_memory.read(new_cp.as_mut_bytes(), GuestAddress(group[0].gpa)); + let import_result = memory_manager .lock() .unwrap() .vm .import_isolated_pages( group[0].page_type, - hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, + page_types.isolated_page_size_4kb, &pfns, &uaddrs, ) - .map_err(Error::ImportIsolatedPages)?; + .map_err(Error::ImportIsolatedPages); + #[cfg(feature = "kvm")] + if hypervisor_type == HypervisorType::Kvm + && import_result.is_err() + && page_type == page_types.cpuid + { + // When we import the CPUID page, the firmware will change any cpuid fns that + // could lead to an insecure guest, we must then make sure to import the updated cpuid + // https://elixir.bootlin.com/linux/v6.11/source/arch/x86/kvm/svm/sev.c#L2322 + let mut updated_cp = SnpCpuidInfo::new_zeroed(); + let _ = guest_memory.read(updated_cp.as_mut_bytes(), GuestAddress(group[0].gpa)); + for (set, got) in std::iter::zip(new_cp.entries.iter(), updated_cp.entries.iter()) { + if set != got { + error!("Set cpuid fn: {set:#x?}, but firmware expects: {got:#x?}"); + } + } + memory_manager + .lock() + .unwrap() + .vm + .import_isolated_pages( + group[0].page_type, + page_types.isolated_page_size_4kb, + &pfns, + &uaddrs, + ) + .map_err(Error::ImportIsolatedPages)?; + continue; + } + import_result?; } info!( @@ -502,13 +706,23 @@ pub fn load_igvm( gpas.len() ); + let id_block_enabled = if hypervisor_type == HypervisorType::Mshv { + 1 + } else { + 0 + }; + now = Instant::now(); // Call Complete Isolated Import since we are done importing isolated pages memory_manager .lock() .unwrap() .vm - .complete_isolated_import(loaded_info.snp_id_block, host_data_contents, 1) + .complete_isolated_import( + loaded_info.snp_id_block, + host_data_contents, + id_block_enabled, + ) .map_err(Error::CompleteIsolatedImport)?; info!( From 883ca3feb2965c5fc9742498d826fddd673a1157 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 18:14:43 -0700 Subject: [PATCH 1318/1893] 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 Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Signed-off-by: Ruben Hakobyan --- vmm/src/seccomp_filters.rs | 1 + vmm/src/vm.rs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index bc17cde4e8..877ea907d2 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -917,6 +917,7 @@ fn vcpu_thread_rules( (libc::SYS_sendto, vec![]), (libc::SYS_shutdown, vec![]), (libc::SYS_sigaltstack, vec![]), + (libc::SYS_statx, vec![]), (libc::SYS_tgkill, vec![]), (libc::SYS_tkill, vec![]), #[cfg(target_arch = "x86_64")] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 74ca6a7ed0..6006896691 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -46,6 +46,10 @@ use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs; use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs; #[cfg(target_arch = "aarch64")] use hypervisor::arch::aarch64::regs::AARCH64_PMU_IRQ; +#[cfg(all(feature = "kvm", feature = "sev_snp"))] +use hypervisor::kvm::{ + BOOTLOADER_SIZE, BOOTLOADER_START, KVM_VMSA_PAGE_ADDRESS, KVM_VMSA_PAGE_SIZE, +}; use hypervisor::{HypervisorVmConfig, HypervisorVmError, VmOps}; #[cfg(feature = "igvm")] use igvm::IgvmFile; @@ -1040,6 +1044,9 @@ impl Vm { ) .map_err(Error::DeviceManager)?; + #[cfg(feature = "fw_cfg")] + Self::create_fw_cfg_if_enabled(config, device_manager)?; + Ok(load_payload_handle) } @@ -1510,6 +1517,16 @@ impl Vm { Ok(EntryPoint { entry_addr }) } + #[cfg(all(feature = "kvm", feature = "sev_snp"))] + fn reserve_bootloader_regions(memory_manager: &Arc>) -> Result<()> { + let mut mm = memory_manager.lock().unwrap(); + mm.add_ram_region(BOOTLOADER_START, BOOTLOADER_SIZE) + .map_err(Error::MemoryManager)?; + mm.add_ram_region(KVM_VMSA_PAGE_ADDRESS, KVM_VMSA_PAGE_SIZE) + .map_err(Error::MemoryManager)?; + Ok(()) + } + #[cfg(feature = "igvm")] #[allow(clippy::needless_pass_by_value)] fn load_igvm( @@ -1518,6 +1535,13 @@ impl Vm { cpu_manager: Arc>, #[cfg(feature = "sev_snp")] host_data: &Option, ) -> Result { + // Only reserve bootloader/VMSA regions for KVM + SEV-SNP; other hypervisors + // (e.g. MSHV) handle this through their own import path. + #[cfg(all(feature = "kvm", feature = "sev_snp"))] + if cpu_manager.lock().unwrap().sev_snp_enabled() { + Self::reserve_bootloader_regions(&memory_manager)?; + } + let res = igvm_loader::load_igvm( igvm_file, memory_manager, From d48d1dcd3d228a6650a52e37090ae549324420e3 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 3 Apr 2026 16:10:20 -0700 Subject: [PATCH 1319/1893] 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 --- devices/src/legacy/fw_cfg.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index f33179d831..6ad86d109b 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -649,6 +649,18 @@ impl FwCfg { let kernel_start = bp.text_offset; #[cfg(target_arch = "x86_64")] let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512; + + #[cfg(target_arch = "x86_64")] + if kernel_start <= buffer.len() { + buffer.truncate(kernel_start); + } else { + buffer.resize(kernel_start, 0); + file.read_exact_at( + &mut buffer[size_of::()..], + size_of::() as u64, + )?; + } + self.known_items[FW_CFG_SETUP_SIZE as usize] = FwCfgContent::U32(buffer.len() as u32); self.known_items[FW_CFG_SETUP_DATA as usize] = FwCfgContent::Bytes(buffer); self.known_items[FW_CFG_KERNEL_SIZE as usize] = From d5179a73c20aa5d78092ed39a6eb1aa6a37ab487 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 3 Apr 2026 16:11:22 -0700 Subject: [PATCH 1320/1893] 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 --- vmm/src/device_manager.rs | 56 ++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index dc3d827e73..0a263e7c42 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1200,6 +1200,14 @@ fn create_mmio_allocators( mmio_allocators } +fn use_64bit_bar_for_virtio_device( + device_type: u32, + pci_segment_id: u16, + is_hotplug: bool, +) -> bool { + pci_segment_id > 0 || device_type != VirtioDeviceType::Block as u32 || is_hotplug +} + impl DeviceManager { #[allow(clippy::too_many_arguments)] pub fn new( @@ -1714,6 +1722,7 @@ impl DeviceManager { &mapping, &id, handle.pci_common.pci_segment, + false, handle.dma_handler, handle.pci_common.pci_device_id, )?; @@ -1747,8 +1756,15 @@ impl DeviceManager { } if let Some(iommu_device) = iommu_device { - let dev_id = - self.add_virtio_pci_device(iommu_device, &None, &iommu_id, 0, None, None)?; + let dev_id = self.add_virtio_pci_device( + iommu_device, + &None, + &iommu_id, + 0, + false, + None, + None, + )?; self.iommu_attached_devices = Some((dev_id, iommu_attached_devices)); } } @@ -4307,12 +4323,14 @@ impl DeviceManager { Ok(vec![]) } + #[allow(clippy::too_many_arguments)] fn add_virtio_pci_device( &mut self, virtio_device: Arc>, iommu_mapping: &Option>, virtio_device_id: &str, pci_segment_id: u16, + is_hotplug: bool, dma_handler: Option>, pci_device_id: Option, ) -> DeviceManagerResult { @@ -4412,11 +4430,10 @@ impl DeviceManager { self.activate_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, - // All device types *except* virtio block devices should be allocated a 64-bit bar - // The block devices should be given a 32-bit BAR so that they are easily accessible - // to firmware without requiring excessive identity mapping. - // The exception being if not on the default PCI segment. - pci_segment_id > 0 || device_type != VirtioDeviceType::Block as u32, + // Boot-time block devices stay in 32-bit BAR space so early firmware can access + // them without additional identity mapping. Hot-plugged block devices do not have + // that constraint and should use 64-bit BARs like the rest of the virtio devices. + use_64bit_bar_for_virtio_device(device_type, pci_segment_id, is_hotplug), dma_handler, self.pending_activations.clone(), vm_migration::snapshot_from_id(self.snapshot.as_ref(), id.as_str()), @@ -5120,6 +5137,7 @@ impl DeviceManager { &mapping, &id, handle.pci_common.pci_segment, + true, handle.dma_handler, handle.pci_common.pci_device_id, )?; @@ -5868,6 +5886,30 @@ impl Drop for DeviceManager { mod unit_tests { use super::*; + #[test] + fn test_hotplugged_block_devices_use_64bit_bars() { + assert!(!use_64bit_bar_for_virtio_device( + VirtioDeviceType::Block as u32, + 0, + false, + )); + assert!(use_64bit_bar_for_virtio_device( + VirtioDeviceType::Block as u32, + 0, + true, + )); + assert!(use_64bit_bar_for_virtio_device( + VirtioDeviceType::Net as u32, + 0, + false, + )); + assert!(use_64bit_bar_for_virtio_device( + VirtioDeviceType::Block as u32, + 1, + false, + )); + } + #[test] fn test_create_mmio_allocators() { let res = create_mmio_allocators(0x100000, 0x3fffff, 1, &[1], 4 << 10); From ea2df946f63d6662cae3a93ef3299d397a543a10 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Fri, 17 Apr 2026 04:12:35 -0700 Subject: [PATCH 1321/1893] 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 Signed-off-by: Ruben Hakobyan --- .github/workflows/build.yaml | 3 +++ .github/workflows/quality.yaml | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e3b1a9e7f7..628c163db8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -64,6 +64,9 @@ jobs: - name: Build (sev_snp) run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "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 (igvm) run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "igvm" diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 47156beaba..1290b0f872 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -146,6 +146,26 @@ jobs: target: ${{ matrix.target }} args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -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)" From a10d9a309988331141b13b2ffb5c1f4cc8da6399 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Wed, 8 Apr 2026 17:45:35 +0000 Subject: [PATCH 1322/1893] 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 --- cloud-hypervisor/tests/integration.rs | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 0dc7a8d64d..fb262b00cd 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -10895,41 +10895,73 @@ mod live_migration { use super::*; #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_basic() { _test_live_migration(false, false); } #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_local() { _test_live_migration(false, true); } #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_tcp() { _test_live_migration_tcp(NonZeroU32::new(1).unwrap()); } #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_tcp_parallel_connections() { _test_live_migration_tcp(NonZeroU32::new(8).unwrap()); } #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_tcp_timeout_cancel() { _test_live_migration_tcp_timeout(TimeoutStrategy::Cancel); } #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_tcp_timeout_ignore() { _test_live_migration_tcp_timeout(TimeoutStrategy::Ignore); } #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_watchdog() { _test_live_migration_watchdog(false, false); } #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_watchdog_local() { _test_live_migration_watchdog(false, true); } @@ -10982,11 +11014,19 @@ mod live_migration { } #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_balloon() { _test_live_migration_balloon(false, false); } #[test] + #[cfg_attr( + all(feature = "mshv", target_arch = "aarch64"), + ignore = "live migration not yet supported on mshv arm64" + )] fn test_live_migration_balloon_local() { _test_live_migration_balloon(false, true); } From e4e3375a8d7d40ef817030ddcf4c41160dff8c64 Mon Sep 17 00:00:00 2001 From: Keith Adler Date: Tue, 14 Apr 2026 14:47:24 -0500 Subject: [PATCH 1323/1893] 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 --- vmm/src/config.rs | 21 ++++----------------- vmm/src/vm_config.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index d023f98a1b..a099897307 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -373,18 +373,6 @@ pub enum ValidationError { /// Invalid block device serial length #[error("Block device serial length ({0}) exceeds maximum allowed length ({1})")] InvalidSerialLength(usize, usize), - #[cfg(feature = "fw_cfg")] - /// FwCfg missing kernel - #[error("Error --fw-cfg-config: missing --kernel")] - FwCfgMissingKernel, - #[cfg(feature = "fw_cfg")] - /// FwCfg missing cmdline - #[error("Error --fw-cfg-config: missing --cmdline")] - FwCfgMissingCmdline, - #[cfg(feature = "fw_cfg")] - /// FwCfg missing initramfs - #[error("Error --fw-cfg-config: missing --initramfs")] - FwCfgMissingInitramfs, #[cfg(feature = "ivshmem")] /// Invalid Ivshmem input size #[error("Invalid ivshmem input size")] @@ -2038,14 +2026,13 @@ impl FwCfgConfig { items, }) } - pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { - let payload = vm_config.payload.as_ref().unwrap(); + pub fn validate(&self, payload: &PayloadConfig) -> std::result::Result<(), PayloadConfigError> { if self.kernel && payload.kernel.is_none() { - return Err(ValidationError::FwCfgMissingKernel); + return Err(PayloadConfigError::FwCfgMissingKernel); } else if self.cmdline && payload.cmdline.is_none() { - return Err(ValidationError::FwCfgMissingCmdline); + return Err(PayloadConfigError::FwCfgMissingCmdline); } else if self.initramfs && payload.initramfs.is_none() { - return Err(ValidationError::FwCfgMissingInitramfs); + return Err(PayloadConfigError::FwCfgMissingInitramfs); } Ok(()) } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 01d3bb0101..3753b4b491 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -747,6 +747,18 @@ pub enum PayloadConfigError { /// Specifying a kernel or firmware is not supported when an igvm is provided. #[error("Specifying a kernel or firmware is not supported when an igvm is provided")] IgvmPlusOtherPayloads, + #[cfg(feature = "fw_cfg")] + /// FwCfg missing kernel + #[error("Error --fw-cfg-config: missing --kernel")] + FwCfgMissingKernel, + #[cfg(feature = "fw_cfg")] + /// FwCfg missing cmdline + #[error("Error --fw-cfg-config: missing --cmdline")] + FwCfgMissingCmdline, + #[cfg(feature = "fw_cfg")] + /// FwCfg missing initramfs + #[error("Error --fw-cfg-config: missing --initramfs")] + FwCfgMissingInitramfs, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -872,6 +884,11 @@ impl PayloadConfig { (None, None) => Err(PayloadConfigError::MissingBootitem), }?; + #[cfg(feature = "fw_cfg")] + if let Some(fw_cfg_config) = &self.fw_cfg_config { + fw_cfg_config.validate(self)?; + } + Ok(()) } } From 926dd1e141124ee86ea03a35bc040e307b6cb830 Mon Sep 17 00:00:00 2001 From: Keith Adler Date: Tue, 14 Apr 2026 14:48:49 -0500 Subject: [PATCH 1324/1893] 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 --- cloud-hypervisor/tests/integration.rs | 41 +++++++++ devices/src/legacy/fw_cfg.rs | 26 ++++++ docs/fw_cfg.md | 19 ++++- vmm/src/config.rs | 116 ++++++++++++++++++++++---- vmm/src/vm.rs | 21 +++-- vmm/src/vm_config.rs | 10 ++- 6 files changed, 209 insertions(+), 24 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index fb262b00cd..75250bcb20 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -11538,4 +11538,45 @@ mod fw_cfg { handle_child_output(r, &output); } + + #[test] + #[cfg_attr(feature = "mshv", ignore = "See #7434")] + fn test_fw_cfg_string() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let mut cmd = GuestCommand::new(&guest); + + let kernel_path = direct_kernel_boot_path(); + let cmd_line = DIRECT_KERNEL_BOOT_CMDLINE; + + cmd.args(["--cpus", "boot=4"]) + .default_memory() + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", cmd_line]) + .default_disks() + .default_net() + .args([ + "--fw-cfg-config", + "initramfs=off,items=[name=opt/org.test/test-string,string=hello-from-vmm]", + ]) + .capture_output(); + + let mut child = cmd.spawn().unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + thread::sleep(std::time::Duration::new(3, 0)); + let result = guest + .ssh_command( + "sudo cat /sys/firmware/qemu_fw_cfg/by_name/opt/org.test/test-string/raw", + ) + .unwrap(); + assert_eq!(result, "hello-from-vmm"); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } } diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 6ad86d109b..876ceaaf3c 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -909,6 +909,32 @@ mod unit_tests { } } + #[test] + fn test_string_item() { + let gm = GuestMemoryAtomic::new( + GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(), + ); + + let mut fw_cfg = FwCfg::new(gm); + + // Simulate OVMF X-PciMmio64Mb string item for GPU CC passthrough + let item = FwCfgItem { + name: "opt/ovmf/X-PciMmio64Mb".to_owned(), + content: FwCfgContent::Bytes("262144".as_bytes().to_vec()), + }; + fw_cfg.add_item(item).unwrap(); + + let expected = b"262144"; + let mut data = vec![0u8]; + + // Select the first file item (FW_CFG_FILE_FIRST = 0x20) + fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_FILE_FIRST as u8, 0]); + for &byte in expected.iter() { + fw_cfg.read(0, DATA_OFFSET, &mut data); + assert_eq!(data[0], byte); + } + } + #[test] fn test_dma() { let code = [ diff --git a/docs/fw_cfg.md b/docs/fw_cfg.md index 73f10a7808..76e6951f45 100644 --- a/docs/fw_cfg.md +++ b/docs/fw_cfg.md @@ -39,9 +39,10 @@ The `fw_cfg` device is configured using the `--fw-cfg-config` command-line optio * `cmdline=on|off`: (Default: `on`) Whether to add the kernel command line (specified by `--cmdline`) to `fw_cfg`. * `initramfs=on|off`: (Default: `on`) Whether to add the initramfs image (specified by `--initramfs`) to `fw_cfg`. * `acpi_table=on|off`: (Default: `on`) Whether to add generated ACPI tables to `fw_cfg`. -* `items=[... : ...]`: A list of custom key-value pairs to be exposed via `fw_cfg`. +* `items=[... : ...]`: A list of custom key-value pairs to be exposed via `fw_cfg`. Multiple items are separated by `:`. * `name=`: The path under which the item will appear in the guest's sysfs (e.g., `opt/org.example/my-data`). - * `file=`: The path to the file on the host whose content will be provided to the guest for this item. + * `file=`: The path to a file on the host whose content will be provided to the guest for this item. + * `string=`: An inline string value to provide to the guest for this item. Each item must have exactly one of `file` or `string`, not both. **Example Usage:** @@ -57,7 +58,19 @@ The `fw_cfg` device is configured using the `--fw-cfg-config` command-line optio ``` In the guest, `/tmp/guest_setup.txt` from the host will be accessible at `/sys/firmware/qemu_fw_cfg/by_name/opt/org.mycorp/setup_info/raw`. -2. **Disabling `fw_cfg` explicitly:** +2. **Inline string items (e.g., OVMF MMIO64 configuration for GPU passthrough):** + + ```bash + cloud-hypervisor \ + --firmware /path/to/OVMF.fd \ + --disk path=/path/to/rootfs.img \ + --device path=/sys/bus/pci/devices/0000:41:00.0 \ + --fw-cfg-config items=[name=opt/ovmf/X-PciMmio64Mb,string=262144] \ + ... + ``` + The string `262144` is passed directly to the guest as the content of `opt/ovmf/X-PciMmio64Mb`. + +3. **Disabling `fw_cfg` explicitly:** ```bash cloud-hypervisor \ diff --git a/vmm/src/config.rs b/vmm/src/config.rs index a099897307..7efda7c05a 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1970,7 +1970,7 @@ impl FsConfig { impl FwCfgConfig { pub const SYNTAX: &'static str = "Boot params to pass to FW CFG device \ \"e820=on|off,kernel=on|off,cmdline=on|off,initramfs=on|off,acpi_table=on|off, \ - items=[name0=,file0=:name1=,file1=]\""; + items=[name=,file=:name=,string=]\""; pub fn parse(fw_cfg_config: &str) -> Result { let mut parser = OptionParser::new(); parser @@ -2034,6 +2034,15 @@ impl FwCfgConfig { } else if self.initramfs && payload.initramfs.is_none() { return Err(PayloadConfigError::FwCfgMissingInitramfs); } + + if let Some(items) = &self.items { + for item in &items.item_list { + if item.file.is_some() == item.string.is_some() { + return Err(PayloadConfigError::FwCfgInvalidItem(item.name.clone())); + } + } + } + Ok(()) } } @@ -2042,7 +2051,7 @@ impl FwCfgConfig { impl FwCfgItem { pub fn parse(fw_cfg: &str) -> Result { let mut parser = OptionParser::new(); - parser.add("name").add("file"); + parser.add("name").add("file").add("string"); parser.parse(fw_cfg).map_err(Error::ParseFwCfgItem)?; let name = @@ -2051,13 +2060,9 @@ impl FwCfgItem { .ok_or(Error::ParseFwCfgItem(OptionParserError::InvalidValue( "missing FwCfgItem name".to_string(), )))?; - let file = parser - .get("file") - .map(PathBuf::from) - .ok_or(Error::ParseFwCfgItem(OptionParserError::InvalidValue( - "missing FwCfgItem file path".to_string(), - )))?; - Ok(FwCfgItem { name, file }) + let file = parser.get("file").map(PathBuf::from); + let string = parser.get("string"); + Ok(FwCfgItem { name, file, string }) } } @@ -4981,6 +4986,33 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" )) ); + #[cfg(feature = "fw_cfg")] + { + let mut invalid_config = valid_config.clone(); + if let Some(payload) = invalid_config.payload.as_mut() { + payload.fw_cfg_config = Some(FwCfgConfig { + e820: true, + kernel: false, + cmdline: false, + initramfs: false, + acpi_tables: true, + items: Some(FwCfgItemList { + item_list: vec![FwCfgItem { + name: "opt/org.test/invalid".to_string(), + file: None, + string: None, + }], + }), + }); + } + assert_eq!( + invalid_config.validate(), + Err(ValidationError::PayloadError( + PayloadConfigError::FwCfgInvalidItem("opt/org.test/invalid".to_string()) + )) + ); + } + let mut invalid_config = valid_config.clone(); invalid_config.serial.mode = ConsoleOutputMode::File; invalid_config.serial.file = None; @@ -5747,7 +5779,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" // Missing closing bracket FwCfgConfig::parse("items=[name=opt/org.test/fw_cfg_test_item,file=/tmp/fw_cfg_test_item") .unwrap_err(); - // Single Item + // Single file Item assert_eq!( FwCfgConfig::parse( "items=[name=opt/org.test/fw_cfg_test_item,file=/tmp/fw_cfg_test_item]" @@ -5756,13 +5788,14 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" items: Some(FwCfgItemList { item_list: vec![FwCfgItem { name: "opt/org.test/fw_cfg_test_item".to_string(), - file: PathBuf::from("/tmp/fw_cfg_test_item"), + file: Some(PathBuf::from("/tmp/fw_cfg_test_item")), + string: None, }] }), ..Default::default() }, ); - // Multiple Items + // Multiple file Items assert_eq!( FwCfgConfig::parse( "items=[name=opt/org.test/fw_cfg_test_item,file=/tmp/fw_cfg_test_item:name=opt/org.test/fw_cfg_test_item2,file=/tmp/fw_cfg_test_item2]" @@ -5772,17 +5805,72 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" item_list: vec![ FwCfgItem { name: "opt/org.test/fw_cfg_test_item".to_string(), - file: PathBuf::from("/tmp/fw_cfg_test_item"), + file: Some(PathBuf::from("/tmp/fw_cfg_test_item")), + string: None, }, FwCfgItem { name: "opt/org.test/fw_cfg_test_item2".to_string(), - file: PathBuf::from("/tmp/fw_cfg_test_item2"), + file: Some(PathBuf::from("/tmp/fw_cfg_test_item2")), + string: None, } ] }), ..Default::default() }, ); + // Single string Item (for OVMF MMIO64 config, GPU CC passthrough, etc.) + assert_eq!( + FwCfgConfig::parse("items=[name=opt/ovmf/X-PciMmio64Mb,string=262144]")?, + FwCfgConfig { + items: Some(FwCfgItemList { + item_list: vec![FwCfgItem { + name: "opt/ovmf/X-PciMmio64Mb".to_string(), + file: None, + string: Some("262144".to_string()), + }] + }), + ..Default::default() + }, + ); + // Mixed file and string Items + assert_eq!( + FwCfgConfig::parse( + "items=[name=opt/org.test/fw_cfg_test_item,file=/tmp/fw_cfg_test_item:name=opt/ovmf/X-PciMmio64Mb,string=262144]" + )?, + FwCfgConfig { + items: Some(FwCfgItemList { + item_list: vec![ + FwCfgItem { + name: "opt/org.test/fw_cfg_test_item".to_string(), + file: Some(PathBuf::from("/tmp/fw_cfg_test_item")), + string: None, + }, + FwCfgItem { + name: "opt/ovmf/X-PciMmio64Mb".to_string(), + file: None, + string: Some("262144".to_string()), + } + ] + }), + ..Default::default() + }, + ); + // Missing both file and string parses OK but fails validation + let missing_content = + FwCfgConfig::parse("items=[name=opt/org.test/missing_content]").unwrap(); + assert_eq!( + missing_content.items.as_ref().unwrap().item_list[0].file, + None + ); + assert_eq!( + missing_content.items.as_ref().unwrap().item_list[0].string, + None + ); + // Both file and string parses OK but fails validation + let both = FwCfgConfig::parse("items=[name=opt/org.test/both,file=/tmp/test,string=test]") + .unwrap(); + assert!(both.items.as_ref().unwrap().item_list[0].file.is_some()); + assert!(both.items.as_ref().unwrap().item_list[0].string.is_some()); Ok(()) } } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 6006896691..cd118782e9 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1185,15 +1185,24 @@ impl Vm { initramfs_option = initramfs; } let mut fw_cfg_item_list_option: Option> = None; - if let Some(fw_cfg_files) = &fw_cfg_config.items { + if let Some(fw_cfg_items) = &fw_cfg_config.items { let mut fw_cfg_item_list = vec![]; - for fw_cfg_file in fw_cfg_files.item_list.clone() { - fw_cfg_item_list.push(FwCfgItem { - name: fw_cfg_file.name, - content: devices::legacy::fw_cfg::FwCfgContent::File( + for fw_cfg_item in fw_cfg_items.item_list.clone() { + let content = match (fw_cfg_item.string, fw_cfg_item.file) { + (Some(string_val), None) => { + devices::legacy::fw_cfg::FwCfgContent::Bytes(string_val.into_bytes()) + } + (None, Some(file_path)) => devices::legacy::fw_cfg::FwCfgContent::File( 0, - File::open(fw_cfg_file.file).map_err(Error::AddingFwCfgItem)?, + File::open(file_path).map_err(Error::AddingFwCfgItem)?, + ), + _ => unreachable!( + "PayloadConfig::validate() ensures either 'file' or 'string' is present" ), + }; + fw_cfg_item_list.push(FwCfgItem { + name: fw_cfg_item.name, + content, }); } fw_cfg_item_list_option = Some(fw_cfg_item_list); diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 3753b4b491..b0fbb531a2 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -759,6 +759,12 @@ pub enum PayloadConfigError { /// FwCfg missing initramfs #[error("Error --fw-cfg-config: missing --initramfs")] FwCfgMissingInitramfs, + #[cfg(feature = "fw_cfg")] + /// Invalid fw_cfg item content + #[error( + "Error --fw-cfg-config: invalid item '{0}' (exactly one of 'file' or 'string' is required)" + )] + FwCfgInvalidItem(String), } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -819,7 +825,9 @@ pub struct FwCfgItem { #[serde(default)] pub name: String, #[serde(default)] - pub file: PathBuf, + pub file: Option, + #[serde(default)] + pub string: Option, } #[cfg(feature = "fw_cfg")] From a77b6231b45e4530971b326476d315e2c390d6b5 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 20 Apr 2026 12:21:25 +0100 Subject: [PATCH 1325/1893] 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 --- cloud-hypervisor/tests/common/tests_wrappers.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 71087a9009..67033956e1 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -1060,6 +1060,11 @@ pub(crate) fn _test_virtio_fs( }); let (r, hotplug_daemon_child) = if r.is_ok() && hotplug { + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); + // Remove the stale socket so wait_for_virtiofsd_socket actually waits + let _ = std::fs::remove_file(&virtiofsd_socket_path); + let (daemon_child, virtiofsd_socket_path) = prepare_daemon(&guest.tmp_dir, shared_dir.to_str().unwrap()); From ae646f9220f51320edcd53324538f60b0df175cd Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 20 Apr 2026 12:24:52 +0100 Subject: [PATCH 1326/1893] 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 --- cloud-hypervisor/tests/integration.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 75250bcb20..f8915ec4a1 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5844,16 +5844,18 @@ mod common_parallel { })); // Calculate the succeeding device ID let device_id_to_allocate = first_free_device_id + 1; - // We expect the succeeding device ID to be free (single attempt, no retries) - assert!(matches!( - ssh_command_ip_with_auth( - &format!("lspci -n | grep \"00:{device_id_to_allocate:02x}.0\""), - &default_guest_auth(), - &guest.network.guest_ip0, - Some(Duration::from_secs(1)), - ), - Err(SshCommandError::NonZeroExitStatus(1)) - )); + // We expect the succeeding device ID to be free. + assert!(wait_until(Duration::from_secs(10), || { + matches!( + ssh_command_ip_with_auth( + &format!("lspci -n | grep \"00:{device_id_to_allocate:02x}.0\""), + &default_guest_auth(), + &guest.network.guest_ip0, + Some(Duration::from_secs(5)), + ), + Err(SshCommandError::NonZeroExitStatus(1)) + ) + })); // Add a device to the next device slot explicitly let (cmd_success, cmd_stdout, _) = remote_command_w_output( From 305451cce4f5bbfbe7a0aa7820063f7d3fbe251b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 17 Apr 2026 12:39:52 -0700 Subject: [PATCH 1327/1893] 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 --- scripts/dev_cli.sh | 75 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 6e9d58eb7b..3b94513216 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -53,6 +53,46 @@ CARGO_TARGET_DIR="${CLH_BUILD_DIR}/cargo_target" # Let tests know that the special environment is set up. RUSTFLAGS="${RUSTFLAGS} --cfg devcli_testenv" +# Container name used for cleanup on signal. The PID makes it unique per +# invocation so parallel runs do not collide. +CLH_CTR_NAME="clh-dev-$$" + +# PID of the docker run process launched by run_container(). +CLH_CTR_PID="" + +# Cleanup handler: kill the running container (if any) and all child +# processes, then exit. +cleanup() { + echo "[$CLI_NAME] Caught signal, terminating..." + # Disable the trap to prevent recursion + trap - INT TERM + # Kill the Docker/Podman container by name + $DOCKER_RUNTIME kill "$CLH_CTR_NAME" 2>/dev/null + $DOCKER_RUNTIME kill "${CLH_CTR_NAME}-fix" 2>/dev/null + # Kill the docker run process tracked by run_container() + [ -n "$CLH_CTR_PID" ] && kill -TERM "$CLH_CTR_PID" 2>/dev/null + # Kill any remaining child processes + pkill -TERM -P $$ 2>/dev/null + wait 2>/dev/null + exit 1 +} + +trap cleanup INT TERM + +# Run a command in the background and wait for it. Bash defers trap +# handling while a foreground process is running, which makes Ctrl+C +# unresponsive during long-running container commands (wget, qemu-img, +# cargo build, etc). By backgrounding the command and using `wait`, +# the trap fires immediately when a signal arrives. +run_container() { + "$@" & + CLH_CTR_PID=$! + wait $CLH_CTR_PID + local rc=$? + CLH_CTR_PID="" + return $rc +} + # Send a decorated message to stdout, followed by a new line # say() { @@ -146,6 +186,7 @@ fix_dir_perms() { # Yes, running Docker to get elevated privileges, just to chown some files # is a dirty hack. $DOCKER_RUNTIME run \ + --name "${CLH_CTR_NAME}-fix" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --volume /dev:/dev \ @@ -314,7 +355,8 @@ cmd_build() { rustflags="$rustflags -C link-args=-Wl,-Bstatic -C link-args=-lc" fi - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --user "$(id -u):$(id -g)" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ @@ -335,7 +377,8 @@ cmd_clean() { ensure_build_dir ensure_latest_ctr - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --user "$(id -u):$(id -g)" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ @@ -432,7 +475,8 @@ cmd_tests() { if [[ "$unit" = true ]]; then say "Running unit tests for $target..." - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --device $exported_device \ @@ -451,7 +495,8 @@ cmd_tests() { if [ "$integration" = true ]; then say "Running integration tests for $target..." - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --privileged \ @@ -477,7 +522,8 @@ cmd_tests() { mkdir -p "$DEST_IGVM_FILES_PATH" copy_igvm_files "$SRC_IGVM_FILES_PATH" "$DEST_IGVM_FILES_PATH" say "Running CVM integration tests for $target..." - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --privileged \ @@ -502,7 +548,8 @@ cmd_tests() { if [ "$integration_vfio" = true ]; then say "Running VFIO integration tests for $target..." - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --privileged \ @@ -525,7 +572,8 @@ cmd_tests() { if [ "$integration_windows" = true ]; then say "Running Windows integration tests for $target..." - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --privileged \ @@ -548,7 +596,8 @@ cmd_tests() { if [ "$integration_live_migration" = true ]; then say "Running 'live migration' integration tests for $target..." - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --privileged \ @@ -573,7 +622,8 @@ cmd_tests() { if [ "$integration_rate_limiter" = true ]; then say "Running 'rate limiter' integration tests for $target..." - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --privileged \ @@ -596,7 +646,8 @@ cmd_tests() { if [ "$metrics" = true ]; then say "Generating performance metrics for $target..." - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --privileged \ @@ -620,7 +671,8 @@ cmd_tests() { if [ "$coverage" = true ]; then say "Generating code coverage information for $target..." - $DOCKER_RUNTIME run \ + run_container "$DOCKER_RUNTIME" run \ + --name "$CLH_CTR_NAME" \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ --privileged \ @@ -721,6 +773,7 @@ cmd_shell() { fi $DOCKER_RUNTIME run \ + --name "$CLH_CTR_NAME" \ $tty_args \ --workdir "$CTR_CLH_ROOT_DIR" \ --rm \ From 2a089db26921cc75f71b32537fe3b8baea7202e1 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 18 Apr 2026 11:13:57 +0100 Subject: [PATCH 1328/1893] 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 --- net_util/src/ctrl_queue.rs | 8 +++----- net_util/src/queue_pair.rs | 11 +++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/net_util/src/ctrl_queue.rs b/net_util/src/ctrl_queue.rs index b14b380364..8b34a33a7a 100644 --- a/net_util/src/ctrl_queue.rs +++ b/net_util/src/ctrl_queue.rs @@ -174,12 +174,10 @@ impl CtrlQueue { .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?, ) .map_err(Error::GuestMemory)?; - // Per virtio spec 2.6.8, used_len is the number of bytes written - // to device-writable descriptors. Only the status byte is written. - let len = status_desc.len(); - + // Per the virtio spec the used length is bytes the device wrote + // to device-writable descriptors; here just the 1-byte ack. queue - .add_used(desc_chain.memory(), desc_chain.head_index(), len) + .add_used(desc_chain.memory(), desc_chain.head_index(), 1) .map_err(Error::QueueAddUsed)?; if !queue diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index a569031815..ee4d4d61cf 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -97,7 +97,7 @@ impl TxVirtio { next_desc = desc_chain.next(); } - let len = if iovecs.is_empty() { + let bytes_sent = if iovecs.is_empty() { 0 } else { // SAFETY: FFI call with correct arguments @@ -129,7 +129,7 @@ impl TxVirtio { self.counter_bytes += Wrapping(result as u64 - vnet_hdr_len() as u64); self.counter_frames += Wrapping(1); - result as u32 + result as u64 }; // For the sake of simplicity (similar to the RX rate limiting), we always @@ -137,11 +137,14 @@ impl TxVirtio { // limit, and simply stop processing oncoming `avail_desc` if any. if let Some(rate_limiter) = rate_limiter { rate_limit_reached = !rate_limiter.consume(1, TokenType::Ops) - || !rate_limiter.consume(len as u64, TokenType::Bytes); + || !rate_limiter.consume(bytes_sent, TokenType::Bytes); } + // TX descriptors are device-readable only; the device wrote + // nothing back to guest memory, so per the virtio spec the used + // length is 0. queue - .add_used(desc_chain.memory(), desc_chain.head_index(), len) + .add_used(desc_chain.memory(), desc_chain.head_index(), 0) .map_err(NetQueuePairError::QueueAddUsed)?; if !queue From 82d426162d87bb2d9abd8588ad3fed11e2251a49 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 18 Apr 2026 11:18:46 +0100 Subject: [PATCH 1329/1893] 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 --- virtio-devices/src/mem.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 067100164e..727fd72c01 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -293,7 +293,7 @@ impl Request { if desc.is_write_only() { return Err(Error::UnexpectedWriteOnlyDescriptor); } - if desc.len() as usize != size_of::() { + if (desc.len() as usize) < size_of::() { return Err(Error::InvalidRequest); } let req: VirtioMemReq = desc_chain From 8685eb5c5333dd4487457d582cd61080d4cedc8d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 18 Apr 2026 11:18:46 +0100 Subject: [PATCH 1330/1893] 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 --- virtio-devices/src/pmem.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 94f2716b64..9024bcfa25 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -115,7 +115,7 @@ impl Request { return Err(Error::UnexpectedWriteOnlyDescriptor); } - if desc.len() as usize != size_of::() { + if (desc.len() as usize) < size_of::() { return Err(Error::InvalidRequest); } From ae3282dc86fda35872a786423c7dd1525400994b Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 20 Apr 2026 09:53:11 +0100 Subject: [PATCH 1331/1893] 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 --- virtio-devices/src/pmem.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 9024bcfa25..80d41ad5d7 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -191,7 +191,17 @@ impl PmemEpollHandler { Ok(ref req) => { // Currently, there is only one virtio-pmem request, FLUSH. error!("Invalid virtio request type {:?}", req.type_); - 0 + // The virtio spec requires a status response even on error. + let resp = VirtioPmemResp { + ret: VIRTIO_PMEM_RESP_TYPE_EIO, + }; + match desc_chain.memory().write_obj(resp, req.status_addr) { + Ok(()) => size_of::() as u32, + Err(e) => { + error!("Bad guest memory address: {e}"); + 0 + } + } } Err(e) => { error!("Failed to parse available descriptor chain: {e:?}"); From 7b07c3a1949cf5af6356694e55d825492bfdf860 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 18 Apr 2026 11:21:20 +0100 Subject: [PATCH 1332/1893] 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 --- virtio-devices/src/rng.rs | 67 ++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 8d11a3d7b6..9d86cf7d8b 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -12,7 +12,7 @@ use std::{io, result}; use anyhow::anyhow; use event_monitor::event; -use log::{error, info}; +use log::{error, info, warn}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -39,12 +39,6 @@ const QUEUE_AVAIL_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1; #[derive(Error, Debug)] enum Error { - #[error("Descriptor chain too short")] - DescriptorChainTooShort, - #[error("Invalid descriptor")] - InvalidDescriptor, - #[error("Failed to write to guest memory")] - GuestMemoryWrite(#[source] vm_memory::guest_memory::Error), #[error("Failed adding used index")] QueueAddUsed(#[source] virtio_queue::Error), } @@ -66,29 +60,50 @@ impl RngEpollHandler { let mut used_descs = false; while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) { - let desc = desc_chain.next().ok_or(Error::DescriptorChainTooShort)?; - - // The descriptor must be write-only and non-zero length - if !(desc.is_write_only() && desc.len() > 0) { - return Err(Error::InvalidDescriptor); - } - - // Fill the read with data from the random device on the host. - let len = desc_chain - .memory() - .read_volatile_from( - desc.addr() - .translate_gva(self.access_platform.as_deref(), desc.len() as usize) - .map_err(|e| { - Error::GuestMemoryWrite(vm_memory::GuestMemoryError::IOError(e)) - })?, + // virtio-rng has no status byte; on any error along the chain + // report a used length of 0 to indicate failure. + let mut total_len: usize = 0; + while let Some(desc) = desc_chain.next() { + if !desc.is_write_only() || desc.len() == 0 { + warn!( + "Skipping descriptor with write_only={} len={}", + desc.is_write_only(), + desc.len() + ); + total_len = 0; + break; + } + let addr = match desc + .addr() + .translate_gva(self.access_platform.as_deref(), desc.len() as usize) + { + Ok(a) => a, + Err(e) => { + warn!("Failed to translate descriptor address: {e}"); + total_len = 0; + break; + } + }; + match desc_chain.memory().read_volatile_from( + addr, &mut self.random_file, desc.len() as usize, - ) - .map_err(Error::GuestMemoryWrite)?; + ) { + Ok(written) => total_len += written, + Err(e) => { + warn!("Failed to read entropy into descriptor: {e}"); + total_len = 0; + break; + } + } + } queue - .add_used(desc_chain.memory(), desc_chain.head_index(), len as u32) + .add_used( + desc_chain.memory(), + desc_chain.head_index(), + total_len as u32, + ) .map_err(Error::QueueAddUsed)?; used_descs = true; } From 334b900fcdb31e85e01804e2b84e2e94af5214fa Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 20 Apr 2026 10:11:10 +0100 Subject: [PATCH 1333/1893] 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 --- vhost_user_block/src/lib.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 9b0e429ee4..25c10fa500 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -20,7 +20,7 @@ use std::time::Instant; use std::{convert, io, process, result}; use block::qcow::{self, ImageType, QcowFile}; -use block::{Request, VirtioBlockConfig, build_serial}; +use block::{Request, RequestType, VirtioBlockConfig, build_serial}; use libc::EFD_NONBLOCK; use log::{debug, error, info, warn}; use option_parser::{OptionParser, OptionParserError, Toggle}; @@ -128,36 +128,33 @@ impl VhostUserBlkThread { .pop_descriptor_chain(self.mem.memory()) { debug!("got an element in the queue"); - let len; - match Request::parse(&mut desc_chain, None) { + let len = match Request::parse(&mut desc_chain, None) { Ok(mut request) => { debug!("element is a valid request"); request.set_writeback(self.writeback.load(Ordering::Acquire)); - let status = match request.execute( + let (status, len) = match request.execute( &mut self.disk_image.lock().unwrap().deref_mut(), self.disk_nsectors, desc_chain.memory(), &self.serial, ) { - Ok(l) => { - len = l; - VIRTIO_BLK_S_OK as u8 - } - Err(e) => { - len = 1; - e.status() + Ok(_) if request.request_type == RequestType::GetDeviceId => { + (VIRTIO_BLK_S_OK as u8, self.serial.len() as u32 + 1) } + Ok(l) => (VIRTIO_BLK_S_OK as u8, l + 1), + Err(e) => (e.status(), 1), }; desc_chain .memory() .write_obj(status, request.status_addr) .unwrap(); + len } Err(err) => { error!("failed to parse available descriptor chain: {err:?}"); - len = 0; + 0 } - } + }; vring .get_queue_mut() From 9a14fdb7f80f0adf6f3bea9e55c3392c1ba13273 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 20 Apr 2026 10:15:36 +0100 Subject: [PATCH 1334/1893] 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 --- virtio-devices/src/block.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 2e28c60da5..8e96431e8f 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -268,7 +268,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese // If no asynchronous operation has been submitted, we can // simply return the used descriptor. queue - .add_used(desc_chain.memory(), desc_chain.head_index(), 0) + .add_used(desc_chain.memory(), desc_chain.head_index(), 1) .map_err(Error::QueueAddUsed)?; queue .enable_notification(self.mem.memory().deref()) @@ -349,10 +349,17 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese .write_obj(status as u8, request.status_addr) .map_err(Error::RequestStatus)?; + let len = if status == VIRTIO_BLK_S_OK + && request.request_type == RequestType::GetDeviceId + { + self.serial.len() as u32 + 1 + } else { + 1 + }; // If no asynchronous operation has been submitted, we can // simply return the used descriptor. queue - .add_used(desc_chain.memory(), desc_chain.head_index(), 0) + .add_used(desc_chain.memory(), desc_chain.head_index(), len) .map_err(Error::QueueAddUsed)?; queue .enable_notification(self.mem.memory().deref()) @@ -373,7 +380,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese mem.write_obj(VIRTIO_BLK_S_IOERR as u8, request.status_addr) .map_err(Error::RequestStatus)?; queue - .add_used(mem.deref(), desc_index, 0) + .add_used(mem.deref(), desc_index, 1) .map_err(Error::QueueAddUsed)?; queue .enable_notification(mem.deref()) @@ -528,14 +535,19 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese .write_latency_avg .store(write_avg, Ordering::Relaxed); - (VIRTIO_BLK_S_OK as u8, result as u32) + let len = if request.request_type == RequestType::In { + result as u32 + 1 + } else { + 1 + }; + (VIRTIO_BLK_S_OK as u8, len) } else { warn!( "Request failed: {:x?} {:?}", request, io::Error::from_raw_os_error(-result) ); - (VIRTIO_BLK_S_IOERR as u8, 0) + (VIRTIO_BLK_S_IOERR as u8, 1) }; mem.write_obj(status, request.status_addr) From 3ab2c60489198ef9e2caf2d58587c7ffedec1a20 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 19 Apr 2026 20:37:59 +0200 Subject: [PATCH 1335/1893] 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 --- test_infra/src/lib.rs | 101 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 42acca1905..e73f16531d 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::undocumented_unsafe_blocks)] +use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Display; use std::fs::OpenOptions; @@ -12,9 +13,11 @@ use std::io::{Read, Seek, SeekFrom, Write}; use std::net::{TcpListener, TcpStream}; use std::os::unix::fs::PermissionsExt; use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process::{Child, Command, ExitStatus, Output, Stdio}; use std::str::FromStr; +use std::sync::{LazyLock, Mutex}; use std::time::{Duration, Instant}; use std::{env, fmt, fs, io, thread}; @@ -25,6 +28,101 @@ use thiserror::Error; use vmm_sys_util::tempdir::TempDir; use wait_timeout::ChildExt; +// --------------------------------------------------------------------------- +// Process group registry, one group per test. +// +// Every child process spawned during a test is placed into a shared +// process group. The first child creates the group via setpgid(0, 0) +// and subsequent children join via setpgid(0, pgid). A single +// killpg(pgid, SIGKILL) tears down all processes when the test ends. +// +// The registry maps test name to the group PID, protected by a mutex +// so that concurrent tests each get their own independent group. +// +// Any Command::spawn() in test helpers should go through +// ProcessRegistry::spawn so the child is automatically tracked. +// --------------------------------------------------------------------------- + +static PROCESS_REGISTRY: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + +pub struct ProcessRegistry; + +impl ProcessRegistry { + /// Spawn `cmd` in the process group for `test_name`. + /// + /// The first spawn creates a new group (group PID = child PID). + /// Subsequent spawns join the existing group. + pub fn spawn(test_name: &str, cmd: &mut Command) -> io::Result { + let pgid = { + let mut reg = PROCESS_REGISTRY.lock().unwrap(); + let stored = reg.get(test_name).copied(); + // If the stored group no longer has any live processes, + // discard it so the next child creates a fresh group. + if let Some(id) = stored { + let probe = unsafe { libc::killpg(id as i32, 0) }; + if probe == -1 && io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH) { + reg.remove(test_name); + None + } else { + stored + } + } else { + None + } + }; + + unsafe { + cmd.pre_exec(move || { + let target = pgid.unwrap_or(0) as libc::pid_t; + // Best effort: may fail with EPERM in containers. + let _ = libc::setpgid(0, target); + Ok(()) + }); + } + + let child = cmd.spawn()?; + + if pgid.is_none() { + let actual = unsafe { libc::getpgid(child.id() as i32) }; + if actual == child.id() as i32 { + PROCESS_REGISTRY + .lock() + .unwrap() + .insert(test_name.to_string(), child.id()); + } + } + + Ok(child) + } + + /// Kill all processes in the group for `test_name` and remove + /// the entry. + pub fn cleanup(test_name: &str) -> bool { + let pgid = PROCESS_REGISTRY.lock().unwrap().remove(test_name); + + if let Some(pgid) = pgid { + let ret = unsafe { libc::killpg(pgid as i32, libc::SIGKILL) }; + if ret == 0 { + eprintln!( + "[cleanup] Sent SIGKILL to process group {pgid} \ + (test '{test_name}')" + ); + return true; + } + let err = io::Error::last_os_error(); + // ESRCH: all processes already exited. + if err.raw_os_error() != Some(libc::ESRCH) { + eprintln!( + "[cleanup] Failed to kill process group {pgid} \ + (test '{test_name}'): {err}" + ); + } + } + false + } +} + #[derive(Error, Debug)] pub enum WaitTimeoutError { #[error("timeout")] @@ -1046,6 +1144,8 @@ pub struct Guest { pub num_cpu: u32, pub nested: bool, pub mem_size_str: String, + /// Test name, set from the current thread name at construction. + pub test_name: Option, } // Return the next id that can be used for this guest. This is stored in a @@ -1118,6 +1218,7 @@ impl Guest { num_cpu: 1u32, nested: true, mem_size_str: "512M".to_string(), + test_name: thread::current().name().map(String::from), } } From 18513ead0c194a7793fb723844c3ebbeeb1a1c36 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 19 Apr 2026 20:40:02 +0200 Subject: [PATCH 1336/1893] 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 --- test_infra/src/lib.rs | 45 +++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index e73f16531d..00b153bda9 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1904,15 +1904,16 @@ impl<'a> GuestCommand<'a> { } if self.capture_output { + self.command.stderr(Stdio::piped()).stdout(Stdio::piped()); + // The caller should call .wait() on the returned child #[allow(unknown_lints)] #[allow(clippy::zombie_processes)] - let child = self - .command - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); + let child = if let Some(name) = &self.guest.test_name { + ProcessRegistry::spawn(name, &mut self.command)? + } else { + self.command.spawn().unwrap() + }; let fd = child.stdout.as_ref().unwrap().as_raw_fd(); let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; @@ -1936,7 +1937,11 @@ impl<'a> GuestCommand<'a> { // The caller should call .wait() on the returned child #[allow(unknown_lints)] #[allow(clippy::zombie_processes)] - self.command.spawn() + if let Some(name) = &self.guest.test_name { + ProcessRegistry::spawn(name, &mut self.command) + } else { + self.command.spawn() + } } } @@ -2326,11 +2331,12 @@ pub fn measure_virtio_net_throughput( if !bandwidth { cmd.args(["-u", "-b", "1T"]); } - let client = cmd - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .map_err(Error::Spawn)?; + cmd.stderr(Stdio::piped()).stdout(Stdio::piped()); + let client = if let Some(name) = &guest.test_name { + ProcessRegistry::spawn(name, &mut cmd).map_err(Error::Spawn)? + } else { + cmd.spawn().map_err(Error::Spawn)? + }; clients.push(client); } @@ -2425,8 +2431,9 @@ pub fn measure_virtio_net_latency(guest: &Guest, test_timeout: u32) -> Result Result Date: Sun, 19 Apr 2026 20:44:46 +0200 Subject: [PATCH 1337/1893] 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 --- performance-metrics/src/main.rs | 56 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 5192a4406b..0844838c74 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -17,7 +17,7 @@ use std::{env, fmt, thread}; use clap::{Arg, ArgAction, Command as ClapCommand}; use performance_tests::*; use serde::{Deserialize, Serialize}; -use test_infra::FioOps; +use test_infra::{FioOps, ProcessRegistry}; use thiserror::Error; #[derive(Error, Debug)] @@ -1719,45 +1719,44 @@ fn run_test_with_timeout( let test_iterations = overrides.test_iterations; let test_timeout = overrides.test_timeout; let overrides = overrides.clone(); - thread::spawn(move || { - println!( - "Test '{}' running .. (control: {}, overrides: {})", - test.name, test.control, overrides - ); + thread::Builder::new() + .name(test.name.into()) + .spawn(move || { + println!( + "Test '{}' running .. (control: {}, overrides: {})", + test.name, test.control, overrides + ); - let output = match std::panic::catch_unwind(|| test.run(&overrides)) { - Ok(test_result) => { - println!( - "Test '{}' .. ok: mean = {}, std_dev = {}", - test_result.name, test_result.mean, test_result.std_dev - ); - Ok(test_result) - } - Err(_) => Err(Error::TestFailed), - }; + let output = match std::panic::catch_unwind(|| test.run(&overrides)) { + Ok(test_result) => { + println!( + "Test '{}' .. ok: mean = {}, std_dev = {}", + test_result.name, test_result.mean, test_result.std_dev + ); + Ok(test_result) + } + Err(_) => Err(Error::TestFailed), + }; - let _ = sender.send(output); - }); + let _ = sender.send(output); + }) + .unwrap(); let test_timeout = test.calc_timeout(&test_iterations, &test_timeout); - receiver + let result = receiver .recv_timeout(Duration::from_secs(test_timeout)) .map_err(|_| { eprintln!( "[Error] Test '{}' time-out after {} seconds", test.name, test_timeout ); - cleanup_stale_processes(); Error::TestTimeout - })? -} + }) + .and_then(|r| r); -fn cleanup_stale_processes() { - // "cloud-hyperviso" - process name truncated to 15 chars by the kernel - for proc in &["cloud-hyperviso", "iperf3", "ethr"] { - let _ = Command::new("pkill").args(["-9", proc]).status(); - } - thread::sleep(Duration::from_secs(2)); + ProcessRegistry::cleanup(test.name); + + result } fn settle_host() { @@ -1914,7 +1913,6 @@ fn main() { metrics_report .results .push(PerformanceTestResult::failed(test.name)); - cleanup_stale_processes(); } else { eprintln!("Aborting test due to error: '{e:?}'"); std::process::exit(1); From 6453a9a91abec0986a7eace4f5e1ec5554e2e667 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 19 Apr 2026 21:45:14 +0200 Subject: [PATCH 1338/1893] 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 --- test_infra/src/lib.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 00b153bda9..7b2cafb424 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -2604,3 +2604,26 @@ pub mod aarch64 { #[cfg(target_arch = "aarch64")] pub use aarch64::*; + +#[cfg(test)] +mod unit_tests { + use super::*; + + fn is_alive(pid: u32) -> bool { + unsafe { libc::kill(pid as i32, 0) == 0 } + } + + #[test] + fn process_registry_spawn_and_cleanup() { + let name = "test_spawn_and_cleanup"; + let mut cmd = Command::new("sleep"); + cmd.arg("60"); + let mut child = ProcessRegistry::spawn(name, &mut cmd).unwrap(); + let pid = child.id(); + + assert!(is_alive(pid)); + assert!(ProcessRegistry::cleanup(name)); + let _ = child.wait(); + assert!(!is_alive(pid)); + } +} From 6e8c2bd2b3cd0edc6a5194ba575f6ee57e2f3a37 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 19 Apr 2026 22:27:55 +0200 Subject: [PATCH 1339/1893] 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 --- test_infra/src/lib.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 7b2cafb424..1ce436ba19 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -2626,4 +2626,30 @@ mod unit_tests { let _ = child.wait(); assert!(!is_alive(pid)); } + + #[test] + fn process_registry_shared_group() { + let name = "test_shared_group"; + + let mut cmd1 = Command::new("sleep"); + cmd1.arg("60"); + let mut child1 = ProcessRegistry::spawn(name, &mut cmd1).unwrap(); + let pid1 = child1.id(); + + let mut cmd2 = Command::new("sleep"); + cmd2.arg("60"); + let mut child2 = ProcessRegistry::spawn(name, &mut cmd2).unwrap(); + let pid2 = child2.id(); + + let pgid1 = unsafe { libc::getpgid(pid1 as i32) }; + let pgid2 = unsafe { libc::getpgid(pid2 as i32) }; + assert_eq!(pgid1, pgid2); + assert_eq!(pgid1, pid1 as i32); + + assert!(ProcessRegistry::cleanup(name)); + let _ = child1.wait(); + let _ = child2.wait(); + assert!(!is_alive(pid1)); + assert!(!is_alive(pid2)); + } } From 0a2d078479c96b2bbe7547d556cbff99ad6e145a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sun, 19 Apr 2026 22:28:47 +0200 Subject: [PATCH 1340/1893] test_infra: Test cleanup of unknown name returns false Signed-off-by: Anatol Belski --- test_infra/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 1ce436ba19..6fa77302a3 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -2652,4 +2652,9 @@ mod unit_tests { assert!(!is_alive(pid1)); assert!(!is_alive(pid2)); } + + #[test] + fn process_registry_cleanup_unknown() { + assert!(!ProcessRegistry::cleanup("nonexistent")); + } } From 78a05ab0c103975c3403a56645de4facdb2a2021 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 20 Apr 2026 06:51:30 +0200 Subject: [PATCH 1341/1893] 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 --- test_infra/src/lib.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 6fa77302a3..bfdccf0c68 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -2657,4 +2657,29 @@ mod unit_tests { fn process_registry_cleanup_unknown() { assert!(!ProcessRegistry::cleanup("nonexistent")); } + + #[test] + fn process_registry_stale_group_replaced() { + let name = "test_stale_group"; + + let mut cmd1 = Command::new("sleep"); + cmd1.arg("0"); + let mut child1 = ProcessRegistry::spawn(name, &mut cmd1).unwrap(); + let pid1 = child1.id(); + let _ = child1.wait(); + + // Group is now stale. Next spawn should create a fresh group. + let mut cmd2 = Command::new("sleep"); + cmd2.arg("60"); + let mut child2 = ProcessRegistry::spawn(name, &mut cmd2).unwrap(); + let pid2 = child2.id(); + + assert_ne!(pid1, pid2); + let pgid2 = unsafe { libc::getpgid(pid2 as i32) }; + assert_eq!(pgid2, pid2 as i32); + + assert!(ProcessRegistry::cleanup(name)); + let _ = child2.wait(); + assert!(!is_alive(pid2)); + } } From d9b188c1be4457c75438bb86af8756c9178f565c Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 17 Apr 2026 19:50:03 +0200 Subject: [PATCH 1342/1893] 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 --- block/src/qcow/mod.rs | 2 +- block/src/qcow_common.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 4fc4916f30..8c0abb5dab 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause pub(crate) mod backing; -mod decoder; +pub(crate) mod decoder; mod header; pub(crate) mod metadata; pub(crate) mod qcow_raw_file; diff --git a/block/src/qcow_common.rs b/block/src/qcow_common.rs index 08a1a9ca3e..eeea8bf512 100644 --- a/block/src/qcow_common.rs +++ b/block/src/qcow_common.rs @@ -14,6 +14,8 @@ use std::cmp::min; use std::os::fd::RawFd; use std::{io, ptr, slice}; +use crate::qcow::decoder::Decoder; + // -- Position independent I/O helpers -- // // Duplicated file descriptors share the kernel file description and thus the @@ -44,6 +46,33 @@ pub fn pread_exact(fd: RawFd, buf: &mut [u8], offset: u64) -> io::Result<()> { Ok(()) } +/// Allocate a buffer and pread exactly `len` bytes at `offset`. +pub fn pread_alloc(fd: RawFd, offset: u64, len: usize) -> io::Result> { + let mut buf = vec![0u8; len]; + pread_exact(fd, &mut buf, offset)?; + Ok(buf) +} + +/// 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 fn decompress_cluster( + compressed: &[u8], + cluster_size: usize, + decoder: &dyn Decoder, +) -> io::Result> { + 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) +} + /// Write all bytes to fd at offset, looping on short writes. pub fn pwrite_all(fd: RawFd, buf: &[u8], offset: u64) -> io::Result<()> { let mut total = 0usize; From 659f7c17e534c9df070a6139d106bd1ed003f2d1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 17 Apr 2026 20:53:54 +0200 Subject: [PATCH 1343/1893] 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 --- block/src/qcow/metadata.rs | 5 ----- block/src/qcow_async.rs | 10 +++++++--- block/src/qcow_sync.rs | 13 +++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/block/src/qcow/metadata.rs b/block/src/qcow/metadata.rs index b4b64cabd0..37bb7847b6 100644 --- a/block/src/qcow/metadata.rs +++ b/block/src/qcow/metadata.rs @@ -333,11 +333,6 @@ impl QcowMetadata { pub fn cluster_size(&self) -> u64 { self.inner.read().unwrap().raw_file.cluster_size() } - - /// Returns the intra cluster byte offset for a given guest address. - pub fn cluster_offset(&self, address: u64) -> u64 { - self.inner.read().unwrap().raw_file.cluster_offset(address) - } } impl QcowState { diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 308c2e27d0..a0f4706749 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -177,6 +177,7 @@ pub struct QcowAsync { alignment: usize, /// I/O alignment for the AsyncIo trait (at least SECTOR_SIZE). io_alignment: u64, + cluster_size: u64, io_uring: IoUring, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, @@ -197,6 +198,7 @@ impl QcowAsync { io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?; Ok(QcowAsync { + cluster_size: metadata.cluster_size(), metadata, data_file, backing_file, @@ -296,6 +298,7 @@ impl AsyncIo for QcowAsync { &self.data_file, &self.backing_file, self.alignment, + self.cluster_size, )?; let total_len: usize = iovecs.iter().map(|v| v.iov_len).sum(); @@ -325,7 +328,7 @@ impl AsyncIo for QcowAsync { fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { let virtual_size = self.metadata.virtual_size(); - let cluster_size = self.metadata.cluster_size(); + let cluster_size = self.cluster_size; let result = self .metadata @@ -425,6 +428,7 @@ impl AsyncIo for QcowAsync { &self.data_file, &self.backing_file, self.alignment, + self.cluster_size, )?; sync_completions.push((req.user_data, total_len as i32)); } @@ -571,14 +575,14 @@ impl QcowAsync { data_file: &QcowRawFile, backing_file: &Option>, alignment: usize, + cluster_size: u64, ) -> AsyncIoResult<()> { let total_len: usize = iovecs.iter().map(|v| v.iov_len).sum(); - let cluster_size = metadata.cluster_size(); let mut buf_offset = 0usize; while buf_offset < total_len { let curr_addr = address + buf_offset as u64; - let intra_offset = metadata.cluster_offset(curr_addr); + let intra_offset = curr_addr & (cluster_size - 1); let remaining_in_cluster = (cluster_size - intra_offset) as usize; let count = min(total_len - buf_offset, remaining_in_cluster); diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 44b8efaf5a..fd2c611432 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -156,6 +156,7 @@ pub struct QcowSync { sparse: bool, /// O_DIRECT alignment requirement (0 = no alignment needed). alignment: usize, + cluster_size: u64, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, } @@ -169,6 +170,7 @@ impl QcowSync { ) -> Self { let alignment = data_file.file().alignment(); QcowSync { + cluster_size: metadata.cluster_size(), metadata, data_file, backing_file, @@ -278,9 +280,8 @@ impl AsyncIo for QcowSync { while buf_offset < total_len { let curr_addr = address + buf_offset as u64; - let cluster_size = self.metadata.cluster_size(); - let intra_offset = self.metadata.cluster_offset(curr_addr); - let remaining_in_cluster = (cluster_size - intra_offset) as usize; + let intra_offset = curr_addr & (self.cluster_size - 1); + let remaining_in_cluster = (self.cluster_size - intra_offset) as usize; let count = min(total_len - buf_offset, remaining_in_cluster); // Read backing data for COW if this is a partial cluster @@ -288,10 +289,10 @@ impl AsyncIo for QcowSync { let backing_data = if let Some(backing) = self .backing_file .as_ref() - .filter(|_| intra_offset != 0 || count < cluster_size as usize) + .filter(|_| intra_offset != 0 || count < self.cluster_size as usize) { let cluster_begin = curr_addr - intra_offset; - let mut data = vec![0u8; cluster_size as usize]; + let mut data = vec![0u8; self.cluster_size as usize]; backing .read_at(cluster_begin, &mut data) .map_err(AsyncIoError::WriteVectored)?; @@ -357,7 +358,7 @@ impl AsyncIo for QcowSync { fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> { let virtual_size = self.metadata.virtual_size(); - let cluster_size = self.metadata.cluster_size(); + let cluster_size = self.cluster_size; let result = self .metadata From 5504ad753a45ee604a49b309aa0347bd966ef926 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 17 Apr 2026 20:57:05 +0200 Subject: [PATCH 1344/1893] 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 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 --- block/src/qcow/backing.rs | 30 +++++++++++++++---- block/src/qcow/decoder.rs | 2 +- block/src/qcow/metadata.rs | 59 ++++++++++++++++++++++++-------------- block/src/qcow/mod.rs | 23 +++++++++++++-- block/src/qcow_async.rs | 50 +++++++++++++++++++++++++++----- block/src/qcow_sync.rs | 32 +++++++++++++++++---- 6 files changed, 153 insertions(+), 43 deletions(-) diff --git a/block/src/qcow/backing.rs b/block/src/qcow/backing.rs index 754618f132..6b8448861f 100644 --- a/block/src/qcow/backing.rs +++ b/block/src/qcow/backing.rs @@ -11,9 +11,10 @@ use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}; use std::sync::Arc; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; +use crate::qcow::decoder::Decoder; use crate::qcow::metadata::{BackingRead, ClusterReadMapping, QcowMetadata}; use crate::qcow::{BackingFile, BackingKind, Error as QcowError}; -use crate::qcow_common::pread_exact; +use crate::qcow_common::{decompress_cluster, pread_alloc, pread_exact}; /// Raw backing file using pread64 on a duplicated fd. pub(crate) struct RawBacking { @@ -52,6 +53,8 @@ pub(crate) struct Qcow2Backing { pub(crate) metadata: Arc, pub(crate) data_fd: OwnedFd, pub(crate) backing_file: Option>, + pub(crate) cluster_size: u64, + pub(crate) decoder: Arc, } // SAFETY: All reads go through QcowMetadata which uses RwLock @@ -104,10 +107,22 @@ impl Qcow2Backing { )?; buf_offset += length as usize; } - ClusterReadMapping::Compressed { data } => { - let len = data.len(); - buf[buf_offset..buf_offset + len].copy_from_slice(&data); - buf_offset += len; + ClusterReadMapping::Compressed { + host_offset, + compressed_size, + cluster_offset, + length, + } => { + let compressed = + pread_alloc(self.data_fd.as_raw_fd(), host_offset, compressed_size)?; + 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, @@ -152,8 +167,11 @@ pub fn shared_backing_from(bf: BackingFile) -> BlockResult> } BackingKind::Qcow { inner, backing } => { let data_fd = dup_fd(inner.raw_file.as_fd())?; + let metadata = Arc::new(QcowMetadata::new(*inner)); Ok(Arc::new(Qcow2Backing { - metadata: Arc::new(QcowMetadata::new(*inner)), + cluster_size: metadata.cluster_size(), + decoder: metadata.decoder(), + metadata, data_fd, backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?, })) diff --git a/block/src/qcow/decoder.rs b/block/src/qcow/decoder.rs index f1237aae7d..f9510baf93 100644 --- a/block/src/qcow/decoder.rs +++ b/block/src/qcow/decoder.rs @@ -19,7 +19,7 @@ pub enum Error { pub type Result = std::result::Result; /// Generic trait for decoding zlib/zstd formats -pub trait Decoder { +pub trait Decoder: Send + Sync { fn decode(&self, input: &[u8], output: &mut [u8]) -> Result; } diff --git a/block/src/qcow/metadata.rs b/block/src/qcow/metadata.rs index 37bb7847b6..d7c38c9fc5 100644 --- a/block/src/qcow/metadata.rs +++ b/block/src/qcow/metadata.rs @@ -19,10 +19,11 @@ use std::cmp::min; use std::io::{self, Seek}; use std::mem; -use std::sync::RwLock; +use std::sync::{Arc, RwLock}; use libc::{EINVAL, EIO}; +use super::decoder::Decoder; use super::qcow_raw_file::QcowRawFile; use super::refcount::RefCount; use super::util::{ @@ -51,13 +52,16 @@ pub enum ClusterReadMapping { /// bounded by cluster boundary and guest request. Allocated { offset: u64, length: u64 }, - /// The cluster is compressed. The decompressed data is returned inline - /// because decompression is a CPU only operation that was done under the - /// write lock to access the raw compressed bytes from disk. - /// - /// The data field contains exactly the bytes the guest requested, already - /// sliced from the decompressed cluster. - Compressed { data: Vec }, + /// The cluster is compressed. The host file offset and compressed byte + /// count are extracted from the L2 entry under the read lock. The caller + /// reads the compressed data with pread on its own fd, decompresses + /// into a cluster sized buffer, then slices the requested range. + Compressed { + host_offset: u64, + compressed_size: usize, + cluster_offset: usize, + length: usize, + }, /// The cluster is not allocated in this layer but may exist in a backing /// file. The caller should delegate to the backing file at the given @@ -112,6 +116,7 @@ pub enum DeallocAction { /// write lock, so contention stays low and queues scale. pub struct QcowMetadata { inner: RwLock, + decoder: Arc, } /// The actual metadata state, accessible only through the RwLock. @@ -132,6 +137,7 @@ pub(crate) struct QcowState { impl QcowMetadata { pub(crate) fn new(inner: QcowState) -> Self { QcowMetadata { + decoder: Arc::from(inner.header.get_decoder()), inner: RwLock::new(inner), } } @@ -333,6 +339,11 @@ impl QcowMetadata { pub fn cluster_size(&self) -> u64 { self.inner.read().unwrap().raw_file.cluster_size() } + + /// Returns the shared decoder matching the image compression type. + pub fn decoder(&self) -> Arc { + Arc::clone(&self.decoder) + } } impl QcowState { @@ -373,10 +384,19 @@ impl QcowState { let l2_index = self.l2_table_index(address) as usize; let l2_entry = l2_table[l2_index]; - // Compressed entries require disk I/O for decompression - can't do - // that under a read lock. Fall through to the write lock path. + // Compressed entries: extract layout from L2 entry under read lock. + // The caller reads and decompresses on its own fd without holding + // the metadata lock. if l2_entry_is_compressed(l2_entry) { - return Ok(None); + let (host_offset, compressed_size) = + l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); + let cluster_offset = self.raw_file.cluster_offset(address) as usize; + return Ok(Some(ClusterReadMapping::Compressed { + host_offset, + compressed_size, + cluster_offset, + length: count, + })); } if l2_entry_is_empty(l2_entry) { @@ -439,17 +459,14 @@ impl QcowState { if l2_entry_is_empty(l2_entry) { Ok(self.unallocated_read_mapping(address, count, has_backing_file)) } else if l2_entry_is_compressed(l2_entry) { - // Under write lock we can do I/O for decompression - let decompressed = self.decompress_l2_cluster(l2_entry)?; - let start = self.raw_file.cluster_offset(address) as usize; - let end = start - .checked_add(count) - .ok_or_else(|| io::Error::from_raw_os_error(EINVAL))?; - if end > decompressed.len() { - return Err(io::Error::from_raw_os_error(EINVAL)); - } + let (host_offset, compressed_size) = + l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); + let cluster_offset = self.raw_file.cluster_offset(address) as usize; Ok(ClusterReadMapping::Compressed { - data: decompressed[start..end].to_vec(), + host_offset, + compressed_size, + cluster_offset, + length: count, }) } else if l2_entry_is_zero(l2_entry) { // Match original QcowFile::file_read semantics where zero flagged diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 8c0abb5dab..3e629bf570 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -57,6 +57,7 @@ use crate::qcow::qcow_raw_file::{BeUint, QcowRawFile}; pub use crate::qcow::raw_file::RawFile; use crate::qcow::refcount::RefCount; use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache}; +use crate::qcow_common::decompress_cluster; #[sorted] #[derive(Debug, Error)] @@ -322,8 +323,26 @@ impl BackingFile { .file_mut() .read_exact(&mut buf[pos..pos + length as usize])?; } - ClusterReadMapping::Compressed { data } => { - buf[pos..pos + data.len()].copy_from_slice(&data); + ClusterReadMapping::Compressed { + host_offset, + compressed_size, + cluster_offset, + length, + } => { + let mut compressed = vec![0u8; compressed_size]; + inner + .raw_file + .file_mut() + .seek(SeekFrom::Start(host_offset))?; + inner.raw_file.file_mut().read_exact(&mut compressed)?; + let decompressed = decompress_cluster( + &compressed, + cluster_size as usize, + &*inner.header.get_decoder(), + )?; + buf[pos..pos + length].copy_from_slice( + &decompressed[cluster_offset..cluster_offset + length], + ); } ClusterReadMapping::Backing { offset: backing_off, diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index a0f4706749..cdcd548f31 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -21,14 +21,15 @@ use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; +use crate::qcow::decoder::Decoder; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, }; use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; use crate::qcow_common::{ - AlignedBuf, aligned_pread, aligned_pwrite, gather_from_iovecs_into, pread_exact, pwrite_all, - scatter_to_iovecs, zero_fill_iovecs, + AlignedBuf, aligned_pread, aligned_pwrite, decompress_cluster, gather_from_iovecs_into, + pread_alloc, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; use crate::{BatchRequest, RequestType, SECTOR_SIZE, disk_file}; @@ -178,6 +179,7 @@ pub struct QcowAsync { /// I/O alignment for the AsyncIo trait (at least SECTOR_SIZE). io_alignment: u64, cluster_size: u64, + decoder: Arc, io_uring: IoUring, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, @@ -199,6 +201,7 @@ impl QcowAsync { Ok(QcowAsync { cluster_size: metadata.cluster_size(), + decoder: metadata.decoder(), metadata, data_file, backing_file, @@ -253,6 +256,8 @@ impl AsyncIo for QcowAsync { iovecs, total_len, self.alignment, + self.cluster_size, + &*self.decoder, )? { let fd = self.data_file.as_raw_fd(); let (submitter, mut sq, _) = self.io_uring.split(); @@ -396,6 +401,8 @@ impl AsyncIo for QcowAsync { &req.iovecs, total_len, self.alignment, + self.cluster_size, + &*self.decoder, )? { let fd = self.data_file.as_raw_fd(); // SAFETY: fd is valid and iovecs point to valid guest memory. @@ -462,6 +469,7 @@ impl QcowAsync { /// Returns `Some(host_offset)` if the entire read falls within a single /// allocated cluster (fast path). Otherwise handles the read /// synchronously via `scatter_read_sync` and returns `None`. + #[allow(clippy::too_many_arguments)] fn resolve_read( metadata: &QcowMetadata, data_file: &QcowRawFile, @@ -470,6 +478,8 @@ impl QcowAsync { iovecs: &[libc::iovec], total_len: usize, alignment: usize, + cluster_size: u64, + decoder: &dyn Decoder, ) -> AsyncIoResult> { let has_backing = backing_file.is_some(); let mappings = metadata @@ -494,7 +504,15 @@ impl QcowAsync { return Ok(Some(*host_offset)); } - Self::scatter_read_sync(mappings, iovecs, data_file, backing_file, alignment)?; + Self::scatter_read_sync( + mappings, + iovecs, + data_file, + backing_file, + alignment, + cluster_size, + decoder, + )?; Ok(None) } @@ -505,6 +523,8 @@ impl QcowAsync { data_file: &QcowRawFile, backing_file: &Option>, alignment: usize, + cluster_size: u64, + decoder: &dyn Decoder, ) -> AsyncIoResult<()> { let mut buf_offset = 0usize; for mapping in mappings { @@ -542,11 +562,27 @@ impl QcowAsync { } buf_offset += len; } - ClusterReadMapping::Compressed { data } => { - let len = data.len(); + ClusterReadMapping::Compressed { + host_offset, + compressed_size, + cluster_offset, + length, + } => { + let compressed = + pread_alloc(data_file.as_raw_fd(), host_offset, compressed_size) + .map_err(AsyncIoError::ReadVectored)?; + let decompressed = + decompress_cluster(&compressed, cluster_size as usize, decoder) + .map_err(AsyncIoError::ReadVectored)?; // SAFETY: iovecs point to valid guest memory buffers. - unsafe { scatter_to_iovecs(iovecs, buf_offset, &data) }; - buf_offset += len; + unsafe { + scatter_to_iovecs( + iovecs, + buf_offset, + &decompressed[cluster_offset..cluster_offset + length], + ); + } + buf_offset += length; } ClusterReadMapping::Backing { offset: backing_offset, diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index fd2c611432..d33a2fd6a5 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -16,14 +16,16 @@ use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, Disk use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::qcow::backing::shared_backing_from; +use crate::qcow::decoder::Decoder; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, }; use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; use crate::qcow_common::{ - AlignedBuf, aligned_pread, aligned_pwrite, gather_from_iovecs, gather_from_iovecs_into, - pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, + AlignedBuf, aligned_pread, aligned_pwrite, decompress_cluster, gather_from_iovecs, + gather_from_iovecs_into, pread_alloc, pread_exact, pwrite_all, scatter_to_iovecs, + zero_fill_iovecs, }; pub struct QcowDiskSync { @@ -157,6 +159,7 @@ pub struct QcowSync { /// O_DIRECT alignment requirement (0 = no alignment needed). alignment: usize, cluster_size: u64, + decoder: Arc, eventfd: EventFd, completion_list: VecDeque<(u64, i32)>, } @@ -171,6 +174,7 @@ impl QcowSync { let alignment = data_file.file().alignment(); QcowSync { cluster_size: metadata.cluster_size(), + decoder: metadata.decoder(), metadata, data_file, backing_file, @@ -239,11 +243,27 @@ impl AsyncIo for QcowSync { } buf_offset += len; } - ClusterReadMapping::Compressed { data } => { - let len = data.len(); + ClusterReadMapping::Compressed { + host_offset, + compressed_size, + cluster_offset, + length, + } => { + let compressed = + pread_alloc(self.data_file.as_raw_fd(), host_offset, compressed_size) + .map_err(AsyncIoError::ReadVectored)?; + let decompressed = + decompress_cluster(&compressed, self.cluster_size as usize, &*self.decoder) + .map_err(AsyncIoError::ReadVectored)?; // SAFETY: iovecs point to valid guest memory buffers - unsafe { scatter_to_iovecs(iovecs, buf_offset, &data) }; - buf_offset += len; + unsafe { + scatter_to_iovecs( + iovecs, + buf_offset, + &decompressed[cluster_offset..cluster_offset + length], + ); + } + buf_offset += length; } ClusterReadMapping::Backing { offset: backing_offset, From 158882e6ddfb12fb3bc200ca45a9531d07e4c181 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 18 Apr 2026 00:18:13 +0200 Subject: [PATCH 1345/1893] 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 --- block/src/qcow_common.rs | 111 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/block/src/qcow_common.rs b/block/src/qcow_common.rs index eeea8bf512..195fb48aa1 100644 --- a/block/src/qcow_common.rs +++ b/block/src/qcow_common.rs @@ -291,3 +291,114 @@ pub unsafe fn gather_from_iovecs(iovecs: &[libc::iovec], start: usize, len: usiz unsafe { gather_from_iovecs_into(iovecs, start, &mut result) }; result } + +#[cfg(test)] +pub(crate) mod unit_tests { + use std::fs::File; + use std::io::{Read, Seek, SeekFrom, Write}; + + use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + use flate2::write::DeflateEncoder; + use flate2::Compression; + + 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. + /// + /// Walks L1 -> L2 tables, compresses each standard cluster with raw + /// deflate, appends the compressed payload at the end of the file, + /// and rewrites the L2 entry with the compressed layout. + pub fn compress_allocated_clusters(file: &mut File) { + file.seek(SeekFrom::Start(HEADER_CLUSTER_BITS_OFFSET)) + .unwrap(); + let cluster_bits = file.read_u32::().unwrap(); + let cluster_size = 1u64 << cluster_bits; + + file.seek(SeekFrom::Start(HEADER_L1_SIZE_OFFSET)).unwrap(); + let l1_size = file.read_u32::().unwrap(); + + file.seek(SeekFrom::Start(HEADER_L1_TABLE_OFFSET)).unwrap(); + let l1_table_offset = file.read_u64::().unwrap(); + + let entries_per_l2 = cluster_size / 8; + + let mut append_offset = file.seek(SeekFrom::End(0)).unwrap(); + 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.seek(SeekFrom::Start(l1_entry_offset)).unwrap(); + let l1_entry = file.read_u64::().unwrap(); + + 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.seek(SeekFrom::Start(l2_entry_offset)).unwrap(); + let l2_entry = file.read_u64::().unwrap(); + + 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.seek(SeekFrom::Start(host_cluster_addr)).unwrap(); + file.read_exact(&mut cluster_data).unwrap(); + + let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(&cluster_data).unwrap(); + let compressed = encoder.finish().unwrap(); + + file.seek(SeekFrom::Start(append_offset)).unwrap(); + file.write_all(&compressed).unwrap(); + + // The L2 entry encodes the compressed size in units of + // 512 byte sectors. The reader decodes the sector count + // back and computes: nsectors * 512 - (addr & 511). + // Because addr is 512 aligned, this yields nsectors * 512 + // which rounds up to the next sector boundary. The file + // must contain enough bytes for that rounded up pread. + let padded_len = (compressed.len() + 511) & !511; + if padded_len > compressed.len() { + let padding = vec![0u8; padded_len - compressed.len()]; + file.write_all(&padding).unwrap(); + } + + let new_entry = + make_compressed_l2_entry(append_offset, compressed.len(), cluster_bits); + file.seek(SeekFrom::Start(l2_entry_offset)).unwrap(); + file.write_u64::(new_entry).unwrap(); + + append_offset += padded_len as u64; + } + } + + file.flush().unwrap(); + } +} From a502354619a2d6b6897ef4fcd49242b1cb82cbc9 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 17 Apr 2026 23:58:02 +0200 Subject: [PATCH 1346/1893] 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 --- block/src/qcow_common.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_common.rs b/block/src/qcow_common.rs index 195fb48aa1..817436a589 100644 --- a/block/src/qcow_common.rs +++ b/block/src/qcow_common.rs @@ -296,10 +296,15 @@ pub unsafe fn gather_from_iovecs(iovecs: &[libc::iovec], start: usize, len: usiz pub(crate) mod unit_tests { use std::fs::File; use std::io::{Read, Seek, SeekFrom, Write}; + use std::os::unix::fs::FileExt; + use std::os::unix::io::AsRawFd; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; - use flate2::write::DeflateEncoder; use flate2::Compression; + use flate2::write::DeflateEncoder; + use vmm_sys_util::tempfile::TempFile; + + use super::pread_alloc; const COMPRESSED_FLAG: u64 = 1 << 62; const CLUSTER_USED_FLAG: u64 = 1 << 63; @@ -401,4 +406,20 @@ pub(crate) mod unit_tests { file.flush().unwrap(); } + + #[test] + fn test_pread_alloc() { + let temp = TempFile::new().unwrap(); + let file = temp.as_file(); + let data: Vec = (0..=255).cycle().take(4096).collect(); + file.write_all_at(&data, 0).unwrap(); + + let buf = pread_alloc(file.as_raw_fd(), 0, 4096).unwrap(); + assert_eq!(buf, data); + + let buf = pread_alloc(file.as_raw_fd(), 100, 200).unwrap(); + assert_eq!(buf, &data[100..300]); + + pread_alloc(file.as_raw_fd(), 4000, 200).unwrap_err(); + } } From ed9154b39d61b056f5319b8722cb5938aeb0a529 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 18 Apr 2026 00:01:19 +0200 Subject: [PATCH 1347/1893] 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 --- block/src/qcow_common.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_common.rs b/block/src/qcow_common.rs index 817436a589..e9b1626d0e 100644 --- a/block/src/qcow_common.rs +++ b/block/src/qcow_common.rs @@ -304,7 +304,8 @@ pub(crate) mod unit_tests { use flate2::write::DeflateEncoder; use vmm_sys_util::tempfile::TempFile; - use super::pread_alloc; + use super::{decompress_cluster, pread_alloc}; + use crate::qcow::decoder::ZlibDecoder; const COMPRESSED_FLAG: u64 = 1 << 62; const CLUSTER_USED_FLAG: u64 = 1 << 63; @@ -422,4 +423,17 @@ pub(crate) mod unit_tests { pread_alloc(file.as_raw_fd(), 4000, 200).unwrap_err(); } + + #[test] + fn test_decompress_cluster() { + let cluster_size = 65536; + let original: Vec = (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); + } } From 79c6ce43ef666c9212d1da5376d97cfc16c7c568 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 18 Apr 2026 00:06:26 +0200 Subject: [PATCH 1348/1893] 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 --- block/src/qcow_common.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/block/src/qcow_common.rs b/block/src/qcow_common.rs index e9b1626d0e..9b8354d776 100644 --- a/block/src/qcow_common.rs +++ b/block/src/qcow_common.rs @@ -436,4 +436,11 @@ pub(crate) mod unit_tests { 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)); + } } From 16abb28b85c131c331f84e20ff666bb5b7b24453 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 18 Apr 2026 00:20:34 +0200 Subject: [PATCH 1349/1893] 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 --- block/src/qcow_sync.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index d33a2fd6a5..4d80349d0b 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -448,6 +448,7 @@ mod unit_tests { use super::*; use crate::disk_file::{AsyncDiskFile, DiskSize, Resizable}; use crate::qcow::{BackingFileConfig, ImageType, QcowFile, RawFile}; + use crate::qcow_common::unit_tests::compress_allocated_clusters; fn create_disk_with_data( file_size: u64, @@ -1933,4 +1934,20 @@ mod unit_tests { let abuf = AlignedBuf::new(513, 512).unwrap(); assert_eq!(abuf.layout().size(), 1024); } + + #[test] + fn test_compressed_read() { + let cluster_size = 65536usize; + let data: Vec = (0..=255).cycle().take(cluster_size).collect(); + let (temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, 0, false, false); + drop(disk); + + compress_allocated_clusters(&mut temp.as_file().try_clone().unwrap()); + + let disk = + QcowDiskSync::new(temp.as_file().try_clone().unwrap(), false, false, false).unwrap(); + + let buf = async_read(&disk, 0, cluster_size); + assert_eq!(buf, data); + } } From 365ed236ccea37fef256fa949ee1d7e6499ad9d8 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 18 Apr 2026 00:29:27 +0200 Subject: [PATCH 1350/1893] 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 sharing. Signed-off-by: Anatol Belski --- block/src/qcow_async.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index cdcd548f31..9651e3c2a3 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -680,12 +680,15 @@ impl QcowAsync { #[cfg(test)] mod unit_tests { use std::io::{Seek, SeekFrom, Write}; + use std::sync::Arc; + use std::thread; use vmm_sys_util::tempfile::TempFile; use super::*; use crate::disk_file::AsyncDiskFile; use crate::qcow::{QcowFile, RawFile}; + use crate::qcow_common::unit_tests::compress_allocated_clusters; use crate::{BatchRequest, RequestType, SECTOR_SIZE}; fn create_disk_with_data( @@ -1127,4 +1130,41 @@ mod unit_tests { let buf = async_read(&disk, 0, pattern.len()); assert_eq!(buf, pattern, "O_DIRECT roundtrip should match"); } + + #[test] + fn test_compressed_read_multi_queue() { + let cluster_size = 65536usize; + let data: Vec = (0..=255).cycle().take(cluster_size).collect(); + let (temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, 0, false); + drop(disk); + + compress_allocated_clusters(&mut temp.as_file().try_clone().unwrap()); + + let disk = Arc::new( + QcowDiskAsync::new(temp.as_file().try_clone().unwrap(), false, false, false).unwrap(), + ); + + let handles: Vec<_> = (0..4) + .map(|_| { + let disk = Arc::clone(&disk); + let expected = data.clone(); + thread::spawn(move || { + let mut async_io = disk.new_async_io(1).unwrap(); + let mut buf = vec![0xFFu8; cluster_size]; + let iovec = libc::iovec { + iov_base: buf.as_mut_ptr() as *mut libc::c_void, + iov_len: buf.len(), + }; + async_io.read_vectored(0, &[iovec], 1).unwrap(); + let (_, result) = wait_for_completion(async_io.as_mut()); + assert_eq!(result as usize, cluster_size); + assert_eq!(buf, expected); + }) + }) + .collect(); + + for h in handles { + h.join().unwrap(); + } + } } From 519acda794aee81f6d4a4a61beff12de91d1af9b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 18 Apr 2026 00:36:23 +0200 Subject: [PATCH 1351/1893] 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 --- block/src/qcow/mod.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 3e629bf570..32c2b06487 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -2353,6 +2353,7 @@ mod unit_tests { use super::util::{COMPRESSED_FLAG, ZERO_FLAG}; use super::*; + use crate::qcow_common::unit_tests::compress_allocated_clusters; fn valid_header_v3() -> Vec { vec![ @@ -4590,4 +4591,27 @@ mod unit_tests { assert_eq!(qcow.header.version, 2); }); } + + #[test] + fn test_compressed_read() { + let cluster_size = 65536usize; + let data: Vec = (0..=255).cycle().take(cluster_size).collect(); + let temp = TempFile::new().unwrap(); + { + let raw_file = RawFile::new(temp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw_file, 3, 100 * 1024 * 1024, false).unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + qcow.write_all(&data).unwrap(); + qcow.flush().unwrap(); + } + + compress_allocated_clusters(&mut temp.as_file().try_clone().unwrap()); + + let raw_file = RawFile::new(temp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::from(raw_file).unwrap(); + qcow.seek(SeekFrom::Start(0)).unwrap(); + let mut buf = vec![0u8; cluster_size]; + qcow.read_exact(&mut buf).unwrap(); + assert_eq!(buf, data); + } } From a8d339c9e77f410aa97c08bf1fdb241cfd0fceaf Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 16 Apr 2026 10:07:36 +0200 Subject: [PATCH 1352/1893] 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 --- block/src/fixed_vhd_async.rs | 2 +- block/src/fixed_vhd_sync.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index 699fb2a494..d546703158 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -36,7 +36,7 @@ impl disk_file::PhysicalSize for FixedVhdDiskAsync { crate::Error::GetFileMetadata(io) => { BlockError::new(BlockErrorKind::Io, crate::Error::GetFileMetadata(io)) } - _ => BlockError::new(BlockErrorKind::Io, e), + _ => unreachable!("unexpected error from FixedVhd::physical_size(): {e}"), }) } } diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index 14685522b3..877b17c1de 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -36,7 +36,7 @@ impl disk_file::PhysicalSize for FixedVhdDiskSync { crate::Error::GetFileMetadata(io) => { BlockError::new(BlockErrorKind::Io, crate::Error::GetFileMetadata(io)) } - _ => BlockError::new(BlockErrorKind::Io, e), + _ => unreachable!("unexpected error from FixedVhd::physical_size(): {e}"), }) } } From da64acb7754a6e324da93729fa79b7232a72b6b6 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 16 Apr 2026 11:18:19 +0200 Subject: [PATCH 1353/1893] 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 --- block/src/fixed_vhd_async.rs | 15 ++++++++------- block/src/fixed_vhd_sync.rs | 10 ++++++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index d546703158..e8b28c44c1 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -26,7 +26,9 @@ impl FixedVhdDiskAsync { impl disk_file::DiskSize for FixedVhdDiskAsync { fn logical_size(&self) -> BlockResult { - Ok(self.0.logical_size().unwrap()) + self.0 + .logical_size() + .map_err(|e| BlockError::new(BlockErrorKind::Io, e)) } } @@ -69,13 +71,12 @@ impl disk_file::AsyncDiskFile for FixedVhdDiskAsync { } fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + let size = self + .0 + .logical_size() + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; Ok(Box::new( - FixedVhdAsync::new( - self.0.as_raw_fd(), - ring_depth, - self.0.logical_size().unwrap(), - ) - .map_err(|e| { + FixedVhdAsync::new(self.0.as_raw_fd(), ring_depth, size).map_err(|e| { BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)) .with_op(ErrorOp::Open) })?, diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index 877b17c1de..d7a578393f 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -26,7 +26,9 @@ impl FixedVhdDiskSync { impl disk_file::DiskSize for FixedVhdDiskSync { fn logical_size(&self) -> BlockResult { - Ok(self.0.logical_size().unwrap()) + self.0 + .logical_size() + .map_err(|e| BlockError::new(BlockErrorKind::Io, e)) } } @@ -69,8 +71,12 @@ impl disk_file::AsyncDiskFile for FixedVhdDiskSync { } fn new_async_io(&self, _ring_depth: u32) -> BlockResult> { + let size = self + .0 + .logical_size() + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; Ok(Box::new( - FixedVhdSync::new(self.0.as_raw_fd(), self.0.logical_size().unwrap()).map_err(|e| { + FixedVhdSync::new(self.0.as_raw_fd(), size).map_err(|e| { BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)) .with_op(ErrorOp::Open) })?, From 4416a582c8cec5c8570309d63939bf6eabf1559a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 17 Apr 2026 09:48:19 +0200 Subject: [PATCH 1354/1893] 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 --- block/src/vhdx_sync.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 0405554c1b..7d4f28e33f 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -53,7 +53,7 @@ impl disk_file::PhysicalSize for VhdxDiskSync { Error::GetFileMetadata(io) => { BlockError::new(BlockErrorKind::Io, Error::GetFileMetadata(io)) } - _ => BlockError::new(BlockErrorKind::Io, e), + _ => unreachable!("unexpected error from Vhdx::physical_size(): {e}"), }) } } From cd9fc52472b02edd1a7ce6c8ef517d366d241a15 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 17 Apr 2026 10:06:21 +0200 Subject: [PATCH 1355/1893] 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 --- block/src/vhdx_sync.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 7d4f28e33f..01ae4f346d 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -11,7 +11,7 @@ use vmm_sys_util::eventfd::EventFd; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; -use crate::vhdx::Vhdx; +use crate::vhdx::{Vhdx, VhdxError}; use crate::{AsyncAdaptor, BlockBackend, Error, disk_file}; #[derive(Debug)] @@ -31,7 +31,15 @@ impl VhdxDiskSync { pub fn new(f: File) -> BlockResult { Ok(VhdxDiskSync { vhdx_file: Arc::new(Mutex::new(Vhdx::new(f).map_err(|e| { - BlockError::new(BlockErrorKind::Io, e).with_op(ErrorOp::Open) + 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) })?)), }) } From 7294ca99cfd8a4626212b35eed3b988bff30a274 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 17 Apr 2026 11:07:21 +0200 Subject: [PATCH 1356/1893] 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 --- block/src/fixed_vhd_async.rs | 13 ++++++------- block/src/raw_async.rs | 16 ++++++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index e8b28c44c1..f1494b4b99 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -75,12 +75,11 @@ impl disk_file::AsyncDiskFile for FixedVhdDiskAsync { .0 .logical_size() .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; - Ok(Box::new( - FixedVhdAsync::new(self.0.as_raw_fd(), ring_depth, size).map_err(|e| { - BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)) - .with_op(ErrorOp::Open) - })?, - )) + Ok(Box::new(FixedVhdAsync::new( + self.0.as_raw_fd(), + ring_depth, + size, + )?)) } } @@ -90,7 +89,7 @@ pub struct FixedVhdAsync { } impl FixedVhdAsync { - pub fn new(fd: RawFd, ring_depth: u32, size: u64) -> std::io::Result { + pub fn new(fd: RawFd, ring_depth: u32, size: u64) -> BlockResult { let raw_file_async = RawFileAsync::new(fd, ring_depth)?; Ok(FixedVhdAsync { diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 7fa3208f42..b2dbaa5652 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -108,8 +108,7 @@ impl disk_file::AsyncDiskFile for RawFileDisk { } fn new_async_io(&self, ring_depth: u32) -> BlockResult> { - let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth) - .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)))?; + let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth)?; raw.alignment = DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); Ok(Box::new(raw) as Box) @@ -124,13 +123,18 @@ pub struct RawFileAsync { } impl RawFileAsync { - pub fn new(fd: RawFd, ring_depth: u32) -> std::io::Result { - let io_uring = IoUring::new(ring_depth)?; - let eventfd = EventFd::new(libc::EFD_NONBLOCK)?; + pub fn new(fd: RawFd, ring_depth: u32) -> BlockResult { + let io_uring = + IoUring::new(ring_depth).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; + let eventfd = + EventFd::new(libc::EFD_NONBLOCK).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; // Register the io_uring eventfd that will notify when something in // the completion queue is ready. - io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?; + io_uring + .submitter() + .register_eventfd(eventfd.as_raw_fd()) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; Ok(RawFileAsync { fd, From 5a14d0e2e0e0a71b133a7be54bb86c481070cd79 Mon Sep 17 00:00:00 2001 From: Saravanan D Date: Sat, 18 Apr 2026 15:40:01 -0700 Subject: [PATCH 1357/1893] vmm: clear VFIO MMIO regions in DeviceManager::drop DeviceManager and VfioPciDevice both hold Arc 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 --- vmm/src/device_manager.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 0a263e7c42..e892391324 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4958,7 +4958,7 @@ impl DeviceManager { // rather than MmioRegion start addresses because move_bar() // updates the device's region addresses but not the // DeviceManager's cloned copies. - let device_regions = vfio_pci_device.lock().unwrap().mmio_regions().clone(); + let device_regions = vfio_pci_device.lock().unwrap().mmio_regions(); let mut mmio_regions = self.mmio_regions.lock().unwrap(); for device_region in &device_regions { mmio_regions.retain(|x| !x.has_matching_slots(device_region)); @@ -5865,6 +5865,11 @@ impl BusDevice for DeviceManager { impl Drop for DeviceManager { fn drop(&mut self) { + // Explicitly clear the regions owned by this device to ensure + // that they are dropped and unmapped before the container is cleared. + // See eject_device() for the device hot-unplug equivalent. + self.mmio_regions.lock().unwrap().clear(); + // Wake up the DeviceManager threads (mainly virtio device workers), // to avoid deadlock on waiting for paused/parked worker threads. if let Err(e) = self.resume() { From 21cd13df0166b55c47309ad71d6409785422f17e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Mon, 20 Apr 2026 23:23:12 +0200 Subject: [PATCH 1358/1893] 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 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 --- block/src/async_io.rs | 2 +- block/src/disk_file.rs | 10 ++-- block/src/fixed_vhd_async.rs | 2 +- block/src/fixed_vhd_sync.rs | 2 +- block/src/qcow_async.rs | 22 ++++---- block/src/qcow_sync.rs | 46 ++++++++-------- block/src/raw_async.rs | 2 +- block/src/raw_async_aio.rs | 2 +- block/src/raw_sync.rs | 2 +- block/src/vhdx_sync.rs | 2 +- performance-metrics/src/micro_bench_block.rs | 56 ++++++++++---------- virtio-devices/src/block.rs | 2 +- 12 files changed, 75 insertions(+), 75 deletions(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index 2d8ea37878..f81056797a 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -72,7 +72,7 @@ pub trait DiskFile: Send { fn logical_size(&mut self) -> DiskFileResult; /// Returns the physical size of the underlying file. fn physical_size(&mut self) -> DiskFileResult; - fn new_async_io(&self, ring_depth: u32) -> DiskFileResult>; + fn create_async_io(&self, ring_depth: u32) -> DiskFileResult>; fn topology(&mut self) -> DiskTopology { DiskTopology::default() } diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index fea7243abb..372410aa2c 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -24,7 +24,7 @@ //! / \ //! FullDiskFile: AsyncDiskFile: //! DiskFile + PhysicalSize + DiskFile + Unpin -//! DiskFd + SparseCapable + try_clone, new_async_io +//! DiskFd + SparseCapable + try_clone, create_async_io //! Resizable //! \ / //! AsyncFullDiskFile: FullDiskFile + AsyncDiskFile @@ -139,7 +139,7 @@ pub trait AsyncDiskFile: DiskFile + Unpin { /// 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 new_async_io(&self, ring_depth: u32) -> BlockResult>; + fn create_async_io(&self, ring_depth: u32) -> BlockResult>; } /// Full capability async disk file trait. @@ -215,12 +215,12 @@ impl DiskBackend { } } - pub fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + pub fn create_async_io(&self, ring_depth: u32) -> BlockResult> { match self { Self::Legacy(d) => d - .new_async_io(ring_depth) + .create_async_io(ring_depth) .map_err(|e| BlockError::new(BlockErrorKind::Io, io::Error::other(e))), - Self::Next(d) => d.new_async_io(ring_depth), + Self::Next(d) => d.create_async_io(ring_depth), } } diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index f1494b4b99..bdc5202843 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -70,7 +70,7 @@ impl disk_file::AsyncDiskFile for FixedVhdDiskAsync { Ok(Box::new(FixedVhdDiskAsync(self.0.clone()))) } - fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + fn create_async_io(&self, ring_depth: u32) -> BlockResult> { let size = self .0 .logical_size() diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index d7a578393f..fd74e0123b 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -70,7 +70,7 @@ impl disk_file::AsyncDiskFile for FixedVhdDiskSync { Ok(Box::new(FixedVhdDiskSync(self.0.clone()))) } - fn new_async_io(&self, _ring_depth: u32) -> BlockResult> { + fn create_async_io(&self, _ring_depth: u32) -> BlockResult> { let size = self .0 .logical_size() diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 9651e3c2a3..8f58b4ba1d 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -146,7 +146,7 @@ impl disk_file::AsyncDiskFile for QcowDiskAsync { })) } - fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + fn create_async_io(&self, ring_depth: u32) -> BlockResult> { Ok(Box::new( QcowAsync::new( Arc::clone(&self.metadata), @@ -731,7 +731,7 @@ mod unit_tests { } fn async_write(disk: &QcowDiskAsync, offset: u64, data: &[u8]) { - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); let iovec = libc::iovec { iov_base: data.as_ptr() as *mut libc::c_void, iov_len: data.len(), @@ -749,7 +749,7 @@ mod unit_tests { } fn async_read(disk: &QcowDiskAsync, offset: u64, len: usize) -> Vec { - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); let mut buf = vec![0xFFu8; len]; let iovec = libc::iovec { iov_base: buf.as_mut_ptr() as *mut libc::c_void, @@ -770,7 +770,7 @@ mod unit_tests { let offset = 0u64; let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.punch_hole(offset, data.len() as u64, 100).unwrap(); let (user_data, result) = async_io.next_completed_request().unwrap(); assert_eq!(user_data, 100); @@ -790,7 +790,7 @@ mod unit_tests { let offset = 0u64; let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true); - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io .write_zeroes(offset, data.len() as u64, 200) .unwrap(); @@ -862,7 +862,7 @@ mod unit_tests { let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) .unwrap(); - let mut async_io = disk.new_async_io(8).unwrap(); + let mut async_io = disk.create_async_io(8).unwrap(); // Prepare write data for two regions. let write_a = vec![0xAA; 4096]; @@ -917,7 +917,7 @@ mod unit_tests { iov_len: read_b.len(), }; - let mut async_io = disk.new_async_io(8).unwrap(); + let mut async_io = disk.create_async_io(8).unwrap(); let read_batch = vec![ BatchRequest { offset: offset_a as libc::off_t, @@ -1012,7 +1012,7 @@ mod unit_tests { let buf = async_read(&disk, offset, data.len()); assert!(buf.iter().all(|&b| b == 0xAA)); - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.punch_hole(offset, data.len() as u64, 10).unwrap(); let (_, result) = wait_for_completion(async_io.as_mut()); assert_eq!(result, 0); @@ -1065,7 +1065,7 @@ mod unit_tests { } let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) .unwrap(); - let async_io = disk.new_async_io(1).unwrap(); + let async_io = disk.create_async_io(1).unwrap(); assert_eq!(async_io.alignment(), SECTOR_SIZE); } @@ -1088,7 +1088,7 @@ mod unit_tests { return; } }; - let async_io = disk.new_async_io(1).unwrap(); + let async_io = disk.create_async_io(1).unwrap(); assert!(async_io.alignment() >= SECTOR_SIZE); } @@ -1149,7 +1149,7 @@ mod unit_tests { let disk = Arc::clone(&disk); let expected = data.clone(); thread::spawn(move || { - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); let mut buf = vec![0xFFu8; cluster_size]; let iovec = libc::iovec { iov_base: buf.as_mut_ptr() as *mut libc::c_void, diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 4d80349d0b..4d722728ca 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -140,7 +140,7 @@ impl disk_file::AsyncDiskFile for QcowDiskSync { // ring_depth is unused - this sync backend performs blocking I/O // instead of submitting to an async ring. - fn new_async_io(&self, _ring_depth: u32) -> BlockResult> { + fn create_async_io(&self, _ring_depth: u32) -> BlockResult> { Ok(Box::new(QcowSync::new( Arc::clone(&self.metadata), self.data_raw_file.clone(), @@ -476,7 +476,7 @@ mod unit_tests { } fn async_read(disk: &QcowDiskSync, offset: u64, len: usize) -> Vec { - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); let mut buf = vec![0xFFu8; len]; let iovec = libc::iovec { iov_base: buf.as_mut_ptr() as *mut libc::c_void, @@ -492,7 +492,7 @@ mod unit_tests { } fn async_write(disk: &QcowDiskSync, offset: u64, data: &[u8]) { - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); let iovec = libc::iovec { iov_base: data.as_ptr() as *mut libc::c_void, iov_len: data.len(), @@ -511,7 +511,7 @@ mod unit_tests { let offset = 0u64; let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true, false); - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.punch_hole(offset, data.len() as u64, 100).unwrap(); let (user_data, result) = async_io.next_completed_request().unwrap(); assert_eq!(user_data, 100); @@ -531,7 +531,7 @@ mod unit_tests { let offset = 64 * 1024u64; let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true, false); - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io .write_zeroes(offset, data.len() as u64, 200) .unwrap(); @@ -568,7 +568,7 @@ mod unit_tests { let disk = QcowDiskSync::new(_temp.as_file().try_clone().unwrap(), false, false, true).unwrap(); - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.punch_hole(0, 64 * 1024, 1).unwrap(); async_io.punch_hole(128 * 1024, 64 * 1024, 2).unwrap(); @@ -593,7 +593,7 @@ mod unit_tests { let offset = 0u64; let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true, false); - let mut async_io1 = disk.new_async_io(1).unwrap(); + let mut async_io1 = disk.create_async_io(1).unwrap(); async_io1 .punch_hole(offset, data.len() as u64, 100) .unwrap(); @@ -611,14 +611,14 @@ mod unit_tests { } #[test] - fn test_qcow_disk_sync_punch_hole_with_new_async_io() { + fn test_qcow_disk_sync_punch_hole_with_create_async_io() { // Simulates the real usage pattern of write data, punch hole, then read back. let data = vec![0xCD; 64 * 1024]; // one cluster let offset = 1024 * 1024u64; // 1MB offset let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true, false); // Punch hole to simulate DISCARD - let mut async_io1 = disk.new_async_io(1).unwrap(); + let mut async_io1 = disk.create_async_io(1).unwrap(); async_io1.punch_hole(offset, data.len() as u64, 1).unwrap(); let (user_data, result) = async_io1.next_completed_request().unwrap(); assert_eq!(user_data, 1); @@ -629,7 +629,7 @@ mod unit_tests { let read_buf = async_read(&disk, offset, data.len()); assert!( read_buf.iter().all(|&b| b == 0), - "After punch_hole via new_async_io, read should return zeros" + "After punch_hole via create_async_io, read should return zeros" ); } @@ -641,7 +641,7 @@ mod unit_tests { async_write(&disk, offset, &data); - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.fsync(Some(10)).unwrap(); let (ud, res) = async_io.next_completed_request().unwrap(); assert_eq!(ud, 10); @@ -691,7 +691,7 @@ mod unit_tests { async_write(&disk, offset, &data); - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.fsync(Some(99)).unwrap(); drop(async_io); @@ -825,7 +825,7 @@ mod unit_tests { let new_data = vec![0xAB; cluster_size as usize]; async_write(&disk, 0, &new_data); { - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.fsync(Some(99)).unwrap(); } @@ -1092,7 +1092,7 @@ mod unit_tests { async_write(&disk, idx * cluster_size, &written); } { - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.fsync(Some(99)).unwrap(); } @@ -1383,7 +1383,7 @@ mod unit_tests { let written = vec![0xFFu8; cluster_size as usize]; async_write(&disk, 0, &written); { - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.fsync(Some(99)).unwrap(); } @@ -1392,7 +1392,7 @@ mod unit_tests { // Punch hole on cluster 0 - should deallocate and fall through to backing { - let mut async_io = disk.new_async_io(1).unwrap(); + let mut async_io = disk.create_async_io(1).unwrap(); async_io.punch_hole(0, cluster_size, 42).unwrap(); let (ud, res) = async_io.next_completed_request().unwrap(); assert_eq!(ud, 42); @@ -1435,7 +1435,7 @@ mod unit_tests { let data1 = vec![0xAAu8; cluster_size as usize]; async_write(&disk, 0, &data1); { - let mut aio = disk.new_async_io(1).unwrap(); + let mut aio = disk.create_async_io(1).unwrap(); aio.fsync(Some(1)).unwrap(); } let buf = async_read(&disk, 0, cluster_size as usize); @@ -1444,7 +1444,7 @@ mod unit_tests { let data2 = vec![0xBBu8; cluster_size as usize]; async_write(&disk, 0, &data2); { - let mut aio = disk.new_async_io(1).unwrap(); + let mut aio = disk.create_async_io(1).unwrap(); aio.fsync(Some(2)).unwrap(); } let buf = async_read(&disk, 0, cluster_size as usize); @@ -1496,7 +1496,7 @@ mod unit_tests { let write_data = vec![0xEEu8; write_len]; async_write(&disk, write_offset, &write_data); { - let mut aio = disk.new_async_io(1).unwrap(); + let mut aio = disk.create_async_io(1).unwrap(); aio.fsync(Some(1)).unwrap(); } @@ -1550,7 +1550,7 @@ mod unit_tests { let punch_offset = cluster_size - 4096; let punch_len = 8192u64; { - let mut aio = disk.new_async_io(1).unwrap(); + let mut aio = disk.create_async_io(1).unwrap(); aio.punch_hole(punch_offset, punch_len, 10).unwrap(); let (ud, res) = aio.next_completed_request().unwrap(); assert_eq!(ud, 10); @@ -1614,7 +1614,7 @@ mod unit_tests { let new_data = vec![0xBB; cluster_size as usize]; async_write(&disk, initial_size, &new_data); { - let mut aio = disk.new_async_io(1).unwrap(); + let mut aio = disk.create_async_io(1).unwrap(); aio.fsync(Some(1)).unwrap(); } let buf = async_read(&disk, initial_size, cluster_size as usize); @@ -1684,7 +1684,7 @@ mod unit_tests { ]; let total = a.len() + b.len() + c.len(); - let mut aio = disk.new_async_io(1).unwrap(); + let mut aio = disk.create_async_io(1).unwrap(); aio.write_vectored(0, &iovecs_w, 1).unwrap(); let (ud, res) = aio.next_completed_request().unwrap(); assert_eq!(ud, 1); @@ -1711,7 +1711,7 @@ mod unit_tests { }, ]; - let mut aio = disk.new_async_io(1).unwrap(); + let mut aio = disk.create_async_io(1).unwrap(); aio.read_vectored(0, &iovecs_r, 10).unwrap(); let (ud, res) = aio.next_completed_request().unwrap(); assert_eq!(ud, 10); diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index b2dbaa5652..2b8de99e59 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -107,7 +107,7 @@ impl disk_file::AsyncDiskFile for RawFileDisk { Ok(Box::new(RawFileDisk { file })) } - fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + fn create_async_io(&self, ring_depth: u32) -> BlockResult> { let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth)?; raw.alignment = DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 980f8d13a7..2b63aa24c3 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -85,7 +85,7 @@ impl disk_file::AsyncDiskFile for RawFileDiskAio { Ok(Box::new(RawFileDiskAio { file })) } - fn new_async_io(&self, ring_depth: u32) -> BlockResult> { + fn create_async_io(&self, ring_depth: u32) -> BlockResult> { let mut raw = RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)?; raw.alignment = DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 491ef0563d..1b83b6707f 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -81,7 +81,7 @@ impl disk_file::AsyncDiskFile for RawFileDiskSync { Ok(Box::new(RawFileDiskSync { file })) } - fn new_async_io(&self, _ring_depth: u32) -> BlockResult> { + fn create_async_io(&self, _ring_depth: u32) -> BlockResult> { let mut raw = RawFileSync::new(self.file.as_raw_fd()); raw.alignment = DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); diff --git a/block/src/vhdx_sync.rs b/block/src/vhdx_sync.rs index 01ae4f346d..da9b3e1fb8 100644 --- a/block/src/vhdx_sync.rs +++ b/block/src/vhdx_sync.rs @@ -95,7 +95,7 @@ impl disk_file::AsyncDiskFile for VhdxDiskSync { })) } - fn new_async_io(&self, _ring_depth: u32) -> BlockResult> { + fn create_async_io(&self, _ring_depth: u32) -> BlockResult> { Ok(Box::new(VhdxSync::new(Arc::clone(&self.vhdx_file)))) } } diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index aab20308c1..4e7cb52a34 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -68,7 +68,7 @@ pub fn micro_bench_aio_drain(control: &PerformanceTestControl) -> f64 { pub fn micro_bench_qcow_read(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::qcow_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; let iovec = read_iovec(&mut buf); @@ -93,7 +93,7 @@ pub fn micro_bench_qcow_read(control: &PerformanceTestControl) -> f64 { pub fn micro_bench_qcow_random_read(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::qcow_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); let indices = deterministic_permutation(num_ops); @@ -128,7 +128,7 @@ pub fn micro_bench_qcow_random_read(control: &PerformanceTestControl) -> f64 { pub fn micro_bench_qcow_write(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::empty_qcow_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; let iovec = write_iovec(&buf); @@ -153,7 +153,7 @@ pub fn micro_bench_qcow_write(control: &PerformanceTestControl) -> f64 { pub fn micro_bench_qcow_punch_hole(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::qcow_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); let start = Instant::now(); for i in 0..num_ops { @@ -180,7 +180,7 @@ pub fn micro_bench_qcow_punch_hole(control: &PerformanceTestControl) -> f64 { pub fn micro_bench_qcow_fsync(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::empty_qcow_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); // Write num_ops clusters to dirty L2 and refcount metadata. let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; @@ -210,7 +210,7 @@ pub fn micro_bench_qcow_fsync(control: &PerformanceTestControl) -> f64 { pub fn micro_bench_qcow_backing_read(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_backing, _overlay, disk) = util::qcow_overlay_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; let iovec = read_iovec(&mut buf); @@ -236,7 +236,7 @@ pub fn micro_bench_qcow_backing_read(control: &PerformanceTestControl) -> f64 { pub fn micro_bench_qcow_cow_write(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_backing, _overlay, disk) = util::qcow_overlay_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); let buf = vec![0xBBu8; QCOW_CLUSTER_SIZE as usize]; let iovec = write_iovec(&buf); @@ -260,7 +260,7 @@ pub fn micro_bench_qcow_cow_write(control: &PerformanceTestControl) -> f64 { pub fn micro_bench_qcow_compressed_read(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::compressed_qcow_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; let iovec = read_iovec(&mut buf); @@ -288,7 +288,7 @@ pub fn micro_bench_qcow_multi_cluster_read(control: &PerformanceTestControl) -> let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::qcow_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); let read_size = CLUSTERS_PER_READ * QCOW_CLUSTER_SIZE as usize; let mut buf = vec![0u8; read_size]; @@ -316,7 +316,7 @@ pub fn micro_bench_qcow_multi_cluster_read(control: &PerformanceTestControl) -> pub fn micro_bench_qcow_l2_cache_miss(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::sparse_qcow_tempfile(num_ops); - let mut async_io = disk.new_async_io(1).expect("new_async_io failed"); + let mut async_io = disk.create_async_io(1).expect("create_async_io failed"); let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; let iovec = read_iovec(&mut buf); @@ -343,8 +343,8 @@ pub fn micro_bench_qcow_async_read(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::qcow_async_tempfile(num_ops); let mut async_io = disk - .new_async_io(num_ops as u32) - .expect("new_async_io failed"); + .create_async_io(num_ops as u32) + .expect("create_async_io failed"); let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; let iovec = read_iovec(&mut buf); @@ -366,8 +366,8 @@ pub fn micro_bench_qcow_batch_read(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::qcow_async_tempfile(num_ops); let mut async_io = disk - .new_async_io(num_ops as u32) - .expect("new_async_io failed"); + .create_async_io(num_ops as u32) + .expect("create_async_io failed"); let mut buf = vec![0u8; num_ops * QCOW_CLUSTER_SIZE as usize]; @@ -406,8 +406,8 @@ pub fn micro_bench_qcow_async_random_read(control: &PerformanceTestControl) -> f let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::qcow_async_tempfile(num_ops); let mut async_io = disk - .new_async_io(num_ops as u32) - .expect("new_async_io failed"); + .create_async_io(num_ops as u32) + .expect("create_async_io failed"); let indices = deterministic_permutation(num_ops); @@ -442,8 +442,8 @@ pub fn micro_bench_qcow_async_multi_cluster_read(control: &PerformanceTestContro let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::qcow_async_tempfile(num_ops); let mut async_io = disk - .new_async_io(num_ops as u32) - .expect("new_async_io failed"); + .create_async_io(num_ops as u32) + .expect("create_async_io failed"); let read_size = CLUSTERS_PER_READ * QCOW_CLUSTER_SIZE as usize; let mut buf = vec![0u8; read_size]; @@ -468,8 +468,8 @@ pub fn micro_bench_qcow_async_backing_read(control: &PerformanceTestControl) -> let num_ops = control.num_ops.expect("num_ops required") as usize; let (_backing, _overlay, disk) = util::qcow_async_overlay_tempfile(num_ops); let mut async_io = disk - .new_async_io(num_ops as u32) - .expect("new_async_io failed"); + .create_async_io(num_ops as u32) + .expect("create_async_io failed"); let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; let iovec = read_iovec(&mut buf); @@ -490,8 +490,8 @@ pub fn micro_bench_qcow_async_compressed_read(control: &PerformanceTestControl) let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::compressed_qcow_async_tempfile(num_ops); let mut async_io = disk - .new_async_io(num_ops as u32) - .expect("new_async_io failed"); + .create_async_io(num_ops as u32) + .expect("create_async_io failed"); let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; let iovec = read_iovec(&mut buf); @@ -515,8 +515,8 @@ pub fn micro_bench_qcow_async_write(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::empty_qcow_async_tempfile(num_ops); let mut async_io = disk - .new_async_io(num_ops as u32) - .expect("new_async_io failed"); + .create_async_io(num_ops as u32) + .expect("create_async_io failed"); let buf = vec![0xA5u8; QCOW_CLUSTER_SIZE as usize]; let iovec = write_iovec(&buf); @@ -536,8 +536,8 @@ pub fn micro_bench_qcow_async_l2_cache_miss(control: &PerformanceTestControl) -> let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::sparse_qcow_async_tempfile(num_ops); let mut async_io = disk - .new_async_io(num_ops as u32) - .expect("new_async_io failed"); + .create_async_io(num_ops as u32) + .expect("create_async_io failed"); let mut buf = vec![0u8; QCOW_CLUSTER_SIZE as usize]; let iovec = read_iovec(&mut buf); @@ -562,8 +562,8 @@ pub fn micro_bench_qcow_batch_write(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let (_tmp, disk) = util::empty_qcow_async_tempfile(num_ops); let mut async_io = disk - .new_async_io(num_ops as u32) - .expect("new_async_io failed"); + .create_async_io(num_ops as u32) + .expect("create_async_io failed"); let buf = vec![0xA5u8; num_ops * QCOW_CLUSTER_SIZE as usize]; diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 8e96431e8f..5ecb0d9fe2 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -1129,7 +1129,7 @@ impl VirtioDevice for Block { mem: mem.clone(), disk_image: self .disk_image - .new_async_io(queue_size as u32) + .create_async_io(queue_size as u32) .map_err(|e| { error!("failed to create new AsyncIo: {e}"); ActivateError::BadActivate From 8f972567d07b8898d61696f5bb38bb8562eccbe7 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 21 Apr 2026 18:39:59 +0000 Subject: [PATCH 1359/1893] docs: Standardize the format for disclosing LLM-assisted changes This is adopted from the Linux kernel development process. Signed-off-by: Wei Liu --- CONTRIBUTING.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 943f53c39d..3d9f6b06f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -226,6 +226,26 @@ 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. From 7c44f492936a468b1e62c21412d6237639aa621b Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Mon, 6 Apr 2026 20:22:27 -0400 Subject: [PATCH 1360/1893] 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 --- block/src/lib.rs | 566 +--------------------------------- block/src/request.rs | 598 ++++++++++++++++++++++++++++++++++++ vhost_user_block/src/lib.rs | 6 +- virtio-devices/src/block.rs | 34 +- 4 files changed, 628 insertions(+), 576 deletions(-) create mode 100644 block/src/request.rs diff --git a/block/src/lib.rs b/block/src/lib.rs index 7a55cc4498..da836aaaad 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -31,11 +31,12 @@ pub mod raw_async_aio; #[cfg(test)] mod raw_async_io_tests; pub mod raw_sync; +mod request; pub mod vhd; pub mod vhdx; pub mod vhdx_sync; -use std::alloc::{Layout, alloc_zeroed, dealloc}; +use std::alloc::{Layout, alloc_zeroed}; use std::collections::VecDeque; use std::fmt::{self, Debug}; use std::fs::{File, OpenOptions}; @@ -45,7 +46,6 @@ use std::os::unix::fs::FileTypeExt; use std::os::unix::io::AsRawFd; use std::path::Path; use std::str::FromStr; -use std::time::Instant; use std::{cmp, mem, result}; #[cfg(feature = "io_uring")] @@ -53,39 +53,25 @@ use io_uring::{IoUring, Probe, opcode}; use libc::{ FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE, S_IFBLK, S_IFMT, ioctl, }; -use log::{debug, error, info, warn}; +use log::{debug, info, warn}; +pub use request::{ + AlignedOperation, BatchRequest, ExecuteAsync, MAX_DISCARD_WRITE_ZEROES_SEG, Request, + RequestType, +}; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use thiserror::Error; use virtio_bindings::virtio_blk::*; -use virtio_queue::DescriptorChain; use vm_memory::bitmap::Bitmap; -use vm_memory::{ - Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard, -}; -use vm_virtio::{AccessPlatform, Translatable}; +use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError}; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::{aio, ioctl_io_nr, ioctl_ior_nr}; -use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; +use crate::async_io::{AsyncIoError, AsyncIoResult}; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; +use crate::request::{DEFAULT_DESCRIPTOR_VEC_SIZE, SECTOR_SIZE}; use crate::vhdx::VhdxError; -const SECTOR_SHIFT: u8 = 9; -pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT; - -/// Maximum number of segments per DISCARD or WRITE_ZEROES request. -pub const MAX_DISCARD_WRITE_ZEROES_SEG: u32 = 1; - -/// Size and field offsets within `struct virtio_blk_discard_write_zeroes`. -const DISCARD_WZ_SEG_SIZE: u32 = mem::size_of::() as u32; -const DISCARD_WZ_MAX_PAYLOAD: u32 = DISCARD_WZ_SEG_SIZE * MAX_DISCARD_WRITE_ZEROES_SEG; -const DISCARD_WZ_SECTOR_OFFSET: u64 = - mem::offset_of!(virtio_blk_discard_write_zeroes, sector) as u64; -const DISCARD_WZ_NUM_SECTORS_OFFSET: u64 = - mem::offset_of!(virtio_blk_discard_write_zeroes, num_sectors) as u64; -const DISCARD_WZ_FLAGS_OFFSET: u64 = mem::offset_of!(virtio_blk_discard_write_zeroes, flags) as u64; - #[derive(Error, Debug)] pub enum Error { #[error("Guest gave us bad memory addresses")] @@ -218,17 +204,6 @@ impl ExecuteError { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum RequestType { - In, - Out, - Flush, - GetDeviceId, - Discard, - WriteZeroes, - Unsupported(u32), -} - pub fn request_type( mem: &vm_memory::GuestMemoryMmap, desc_addr: GuestAddress, @@ -258,527 +233,6 @@ fn sector( mem.read_obj(addr).map_err(Error::GuestMemory) } -const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32; - -#[derive(Debug)] -pub struct AlignedOperation { - origin_ptr: u64, - aligned_ptr: u64, - size: usize, - layout: Layout, -} - -pub struct BatchRequest { - pub offset: libc::off_t, - pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>, - pub user_data: u64, - pub request_type: RequestType, -} - -pub struct ExecuteAsync { - // `true` if the execution will complete asynchronously - pub async_complete: bool, - // request need to be batched for submission if any - pub batch_request: Option, -} - -#[derive(Debug)] -pub struct Request { - pub request_type: RequestType, - pub sector: u64, - pub data_descriptors: SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]>, - pub status_addr: GuestAddress, - pub writeback: bool, - pub aligned_operations: SmallVec<[AlignedOperation; DEFAULT_DESCRIPTOR_VEC_SIZE]>, - pub start: Instant, -} - -impl Request { - pub fn parse( - desc_chain: &mut DescriptorChain>>, - access_platform: Option<&dyn AccessPlatform>, - ) -> result::Result { - let hdr_desc = desc_chain - .next() - .ok_or(Error::DescriptorChainTooShort) - .inspect_err(|_| { - error!("Missing head descriptor"); - })?; - - // The head contains the request type which MUST be readable. - if hdr_desc.is_write_only() { - return Err(Error::UnexpectedWriteOnlyDescriptor); - } - - let hdr_desc_addr = hdr_desc - .addr() - .translate_gva(access_platform, hdr_desc.len() as usize) - .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?; - - let mut req = Request { - request_type: request_type(desc_chain.memory(), hdr_desc_addr)?, - sector: sector(desc_chain.memory(), hdr_desc_addr)?, - data_descriptors: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE), - status_addr: GuestAddress(0), - writeback: true, - aligned_operations: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE), - start: Instant::now(), - }; - - let status_desc; - let mut desc = desc_chain - .next() - .ok_or(Error::DescriptorChainTooShort) - .inspect_err(|_| { - error!("Only head descriptor present: request = {req:?}"); - })?; - - if desc.has_next() { - req.data_descriptors.reserve_exact(1); - while desc.has_next() { - if desc.is_write_only() && req.request_type == RequestType::Out { - return Err(Error::UnexpectedWriteOnlyDescriptor); - } - if desc.is_write_only() && req.request_type == RequestType::Discard { - return Err(Error::UnexpectedWriteOnlyDescriptor); - } - if desc.is_write_only() && req.request_type == RequestType::WriteZeroes { - return Err(Error::UnexpectedWriteOnlyDescriptor); - } - if !desc.is_write_only() && req.request_type == RequestType::In { - return Err(Error::UnexpectedReadOnlyDescriptor); - } - if !desc.is_write_only() && req.request_type == RequestType::GetDeviceId { - return Err(Error::UnexpectedReadOnlyDescriptor); - } - - req.data_descriptors.push(( - desc.addr() - .translate_gva(access_platform, desc.len() as usize) - .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?, - desc.len(), - )); - desc = desc_chain - .next() - .ok_or(Error::DescriptorChainTooShort) - .inspect_err(|_| { - error!("DescriptorChain corrupted: request = {req:?}"); - })?; - } - status_desc = desc; - } else { - status_desc = desc; - // Only flush requests are allowed to skip the data descriptor. - if req.request_type != RequestType::Flush { - error!("Need a data descriptor: request = {req:?}"); - return Err(Error::DescriptorChainTooShort); - } - } - - // The status MUST always be writable. - if !status_desc.is_write_only() { - return Err(Error::UnexpectedReadOnlyDescriptor); - } - - if status_desc.len() < 1 { - return Err(Error::DescriptorLengthTooSmall); - } - - req.status_addr = status_desc - .addr() - .translate_gva(access_platform, status_desc.len() as usize) - .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?; - - Ok(req) - } - - pub fn execute( - &self, - disk: &mut T, - disk_nsectors: u64, - mem: &vm_memory::GuestMemoryMmap, - serial: &[u8], - ) -> result::Result { - disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT)) - .map_err(ExecuteError::Seek)?; - let mut len = 0; - for (data_addr, data_len) in &self.data_descriptors { - let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE; - if u64::from(*data_len) % SECTOR_SIZE != 0 { - top += 1; - } - top = top - .checked_add(self.sector) - .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; - if top > disk_nsectors { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - - match self.request_type { - RequestType::In => { - let mut buf = vec![0u8; *data_len as usize]; - disk.read_exact(&mut buf).map_err(ExecuteError::ReadExact)?; - mem.read_exact_volatile_from( - *data_addr, - &mut buf.as_slice(), - *data_len as usize, - ) - .map_err(ExecuteError::Read)?; - len += data_len; - } - RequestType::Out => { - let mut buf: Vec = Vec::new(); - mem.write_all_volatile_to(*data_addr, &mut buf, *data_len as usize) - .map_err(ExecuteError::Write)?; - disk.write_all(&buf).map_err(ExecuteError::WriteAll)?; - if !self.writeback { - disk.flush().map_err(ExecuteError::Flush)?; - } - } - RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?, - RequestType::GetDeviceId => { - if (*data_len as usize) < serial.len() { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - mem.write_slice(serial, *data_addr) - .map_err(ExecuteError::Write)?; - } - RequestType::Discard => { - return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_DISCARD)); - } - RequestType::WriteZeroes => { - return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_WRITE_ZEROES)); - } - RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), - } - } - Ok(len) - } - - pub fn execute_async( - &mut self, - mem: &vm_memory::GuestMemoryMmap, - disk_nsectors: u64, - disk_image: &mut dyn AsyncIo, - serial: &[u8], - disable_sector0_writes: bool, - user_data: u64, - ) -> result::Result { - let sector = self.sector; - let request_type = self.request_type; - let offset = (sector << SECTOR_SHIFT) as libc::off_t; - let alignment = disk_image.alignment(); - - let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> = - SmallVec::with_capacity(self.data_descriptors.len()); - for &(data_addr, data_len) in &self.data_descriptors { - let _: u32 = data_len; // compiler-checked documentation - const _: () = assert!( - core::mem::size_of::() <= core::mem::size_of::(), - "unsupported platform" - ); - if data_len == 0 { - continue; - } - let mut top: u64 = u64::from(data_len) / SECTOR_SIZE; - if u64::from(data_len) % SECTOR_SIZE != 0 { - top += 1; - } - let data_len = data_len as usize; - top = top - .checked_add(sector) - .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; - if top > disk_nsectors { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - - let origin_ptr = mem - .get_slice(data_addr, data_len) - .map_err(ExecuteError::GetHostAddress)?; - assert!(origin_ptr.len() >= data_len); - let origin_ptr = origin_ptr.ptr_guard(); - - // O_DIRECT requires buffer addresses to be aligned to the - // backend device's logical block size. In case it's not properly - // aligned, an intermediate buffer is created with the correct - // alignment, and a copy from/to the origin buffer is performed, - // depending on the type of operation. - let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(alignment) { - origin_ptr.as_ptr() as *mut libc::c_void - } else { - let layout = Layout::from_size_align(data_len, alignment as usize).unwrap(); - // SAFETY: layout has non-zero size - let aligned_ptr = unsafe { alloc_zeroed(layout) }; - if aligned_ptr.is_null() { - return Err(ExecuteError::TemporaryBufferAllocation( - io::Error::last_os_error(), - )); - } - - // We need to perform the copy beforehand in case we're writing - // data out. - if request_type == RequestType::Out { - // SAFETY: destination buffer has been allocated with - // the proper size. - unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, data_len) }; - } - - // Store both origin and aligned pointers for complete_async() - // to process them. - self.aligned_operations.push(AlignedOperation { - origin_ptr: origin_ptr.as_ptr() as u64, - aligned_ptr: aligned_ptr as u64, - size: data_len, - layout, - }); - - aligned_ptr as *mut libc::c_void - }; - - let iovec = libc::iovec { - iov_base, - iov_len: data_len as libc::size_t, - }; - iovecs.push(iovec); - } - - let mut ret = ExecuteAsync { - async_complete: true, - batch_request: None, - }; - // Queue operations expected to be submitted. - match request_type { - RequestType::In => { - for (data_addr, data_len) in &self.data_descriptors { - mem.get_slice(*data_addr, *data_len as usize) - .map_err(ExecuteError::GetHostAddress)? - .bitmap() - .mark_dirty(0, *data_len as usize); - } - if disk_image.batch_requests_enabled() { - ret.batch_request = Some(BatchRequest { - offset, - iovecs, - user_data, - request_type, - }); - } else { - disk_image - .read_vectored(offset, &iovecs, user_data) - .map_err(ExecuteError::AsyncRead)?; - } - } - RequestType::Out => { - if disk_image.batch_requests_enabled() { - ret.batch_request = Some(BatchRequest { - offset, - iovecs, - user_data, - request_type, - }); - } else { - disk_image - .write_vectored(offset, &iovecs, user_data) - .map_err(ExecuteError::AsyncWrite)?; - } - } - RequestType::Flush => { - disk_image - .fsync(Some(user_data)) - .map_err(ExecuteError::AsyncFlush)?; - } - RequestType::GetDeviceId => { - let (data_addr, data_len) = if self.data_descriptors.len() == 1 { - (self.data_descriptors[0].0, self.data_descriptors[0].1) - } else { - return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); - }; - if (data_len as usize) < serial.len() { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - mem.write_slice(serial, data_addr) - .map_err(ExecuteError::Write)?; - ret.async_complete = false; - return Ok(ret); - } - RequestType::Discard => { - let (data_addr, data_len) = if self.data_descriptors.len() == 1 { - (self.data_descriptors[0].0, self.data_descriptors[0].1) - } else { - return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); - }; - - if data_len < DISCARD_WZ_SEG_SIZE { - return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); - } - if data_len > DISCARD_WZ_MAX_PAYLOAD { - return Err(ExecuteError::BadRequest(Error::TooManySegments( - data_len.div_ceil(DISCARD_WZ_SEG_SIZE), - ))); - } - - let mut discard_sector = [0u8; 8]; - let mut discard_num_sectors = [0u8; 4]; - let mut discard_flags = [0u8; 4]; - - let sector_addr = data_addr.checked_add(DISCARD_WZ_SECTOR_OFFSET).unwrap(); - mem.read_slice(&mut discard_sector, sector_addr) - .map_err(ExecuteError::Read)?; - - let num_sectors_addr = data_addr - .checked_add(DISCARD_WZ_NUM_SECTORS_OFFSET) - .unwrap(); - mem.read_slice(&mut discard_num_sectors, num_sectors_addr) - .map_err(ExecuteError::Read)?; - - let flags_addr = data_addr.checked_add(DISCARD_WZ_FLAGS_OFFSET).unwrap(); - mem.read_slice(&mut discard_flags, flags_addr) - .map_err(ExecuteError::Read)?; - - let discard_flags = u32::from_le_bytes(discard_flags); - // Per virtio spec v1.2 reject discard if any flag is set, including unmap. - if discard_flags != 0 { - warn!("Unsupported flags {discard_flags:#x} in discard request"); - return Err(ExecuteError::UnsupportedFlags { - request_type: VIRTIO_BLK_T_DISCARD, - flags: discard_flags, - }); - } - - let discard_sector = u64::from_le_bytes(discard_sector); - - if discard_sector == 0 && disable_sector0_writes { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - - let discard_num_sectors = u32::from_le_bytes(discard_num_sectors); - - let top = discard_sector - .checked_add(discard_num_sectors as u64) - .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; - if top > disk_nsectors { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - - let discard_offset = discard_sector * SECTOR_SIZE; - let discard_length = (discard_num_sectors as u64) * SECTOR_SIZE; - - disk_image - .punch_hole(discard_offset, discard_length, user_data) - .map_err(ExecuteError::AsyncPunchHole)?; - } - RequestType::WriteZeroes => { - let (data_addr, data_len) = if self.data_descriptors.len() == 1 { - (self.data_descriptors[0].0, self.data_descriptors[0].1) - } else { - return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); - }; - - if data_len < DISCARD_WZ_SEG_SIZE { - return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); - } - if data_len > DISCARD_WZ_MAX_PAYLOAD { - return Err(ExecuteError::BadRequest(Error::TooManySegments( - data_len.div_ceil(DISCARD_WZ_SEG_SIZE), - ))); - } - - let mut wz_sector = [0u8; 8]; - let mut wz_num_sectors = [0u8; 4]; - let mut wz_flags = [0u8; 4]; - - let sector_addr = data_addr.checked_add(DISCARD_WZ_SECTOR_OFFSET).unwrap(); - mem.read_slice(&mut wz_sector, sector_addr) - .map_err(ExecuteError::Read)?; - - let num_sectors_addr = data_addr - .checked_add(DISCARD_WZ_NUM_SECTORS_OFFSET) - .unwrap(); - mem.read_slice(&mut wz_num_sectors, num_sectors_addr) - .map_err(ExecuteError::Read)?; - - let flags_addr = data_addr.checked_add(DISCARD_WZ_FLAGS_OFFSET).unwrap(); - mem.read_slice(&mut wz_flags, flags_addr) - .map_err(ExecuteError::Read)?; - - let wz_sector = u64::from_le_bytes(wz_sector); - let wz_num_sectors = u32::from_le_bytes(wz_num_sectors); - - let wz_flags = u32::from_le_bytes(wz_flags); - // Per virtio spec v1.2 reject write zeroes if any unknown flag is set. - if (wz_flags & !VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP) != 0 { - warn!("Unsupported flags {wz_flags:#x} in write zeroes request"); - return Err(ExecuteError::UnsupportedFlags { - request_type: VIRTIO_BLK_T_WRITE_ZEROES, - flags: wz_flags, - }); - } - - let wz_offset = wz_sector * SECTOR_SIZE; - if wz_offset == 0 && disable_sector0_writes { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - - let top = wz_sector - .checked_add(wz_num_sectors as u64) - .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; - if top > disk_nsectors { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - - let wz_length = (wz_num_sectors as u64) * SECTOR_SIZE; - - if wz_flags & VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP != 0 { - disk_image - .punch_hole(wz_offset, wz_length, user_data) - .map_err(ExecuteError::AsyncPunchHole)?; - } else { - disk_image - .write_zeroes(wz_offset, wz_length, user_data) - .map_err(ExecuteError::AsyncWriteZeroes)?; - } - } - RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), - } - - Ok(ret) - } - - pub fn complete_async(&mut self) -> result::Result<(), Error> { - for aligned_operation in self.aligned_operations.drain(..) { - // We need to perform the copy after the data has been read inside - // the aligned buffer in case we're reading data in. - if self.request_type == RequestType::In { - // SAFETY: origin buffer has been allocated with the - // proper size. - unsafe { - std::ptr::copy( - aligned_operation.aligned_ptr as *const u8, - aligned_operation.origin_ptr as *mut u8, - aligned_operation.size, - ); - }; - } - - // Free the temporary aligned buffer. - // SAFETY: aligned_ptr was allocated by alloc_zeroed with the same - // layout - unsafe { - dealloc( - aligned_operation.aligned_ptr as *mut u8, - aligned_operation.layout, - ); - }; - } - - Ok(()) - } - - pub fn set_writeback(&mut self, writeback: bool) { - self.writeback = writeback; - } -} - #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] #[repr(C, packed)] pub struct VirtioBlockConfig { diff --git a/block/src/request.rs b/block/src/request.rs new file mode 100644 index 0000000000..721bc8e29d --- /dev/null +++ b/block/src/request.rs @@ -0,0 +1,598 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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. +// +// Copyright © 2020 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +use std::alloc::{Layout, alloc_zeroed, dealloc}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::mem; +use std::time::Instant; + +use log::{error, warn}; +use smallvec::SmallVec; +use virtio_bindings::virtio_blk::{ + VIRTIO_BLK_T_DISCARD, VIRTIO_BLK_T_WRITE_ZEROES, VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP, + virtio_blk_discard_write_zeroes, +}; +use virtio_queue::DescriptorChain; +use vm_memory::bitmap::Bitmap; +use vm_memory::{ + Address as _, Bytes as _, GuestAddress, GuestMemory as _, GuestMemoryError, + GuestMemoryLoadGuard, +}; +use vm_virtio::{AccessPlatform, Translatable as _}; + +use crate::async_io::AsyncIo; +use crate::{Error, ExecuteError, request_type, sector}; + +const SECTOR_SHIFT: u8 = 9; +pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT; + +/// Maximum number of segments per DISCARD or WRITE_ZEROES request. +pub const MAX_DISCARD_WRITE_ZEROES_SEG: u32 = 1; +/// Size and field offsets within `struct virtio_blk_discard_write_zeroes`. +const DISCARD_WZ_SEG_SIZE: u32 = mem::size_of::() as u32; +const DISCARD_WZ_MAX_PAYLOAD: u32 = DISCARD_WZ_SEG_SIZE * MAX_DISCARD_WRITE_ZEROES_SEG; +const DISCARD_WZ_SECTOR_OFFSET: u64 = + mem::offset_of!(virtio_blk_discard_write_zeroes, sector) as u64; +const DISCARD_WZ_NUM_SECTORS_OFFSET: u64 = + mem::offset_of!(virtio_blk_discard_write_zeroes, num_sectors) as u64; +const DISCARD_WZ_FLAGS_OFFSET: u64 = mem::offset_of!(virtio_blk_discard_write_zeroes, flags) as u64; +#[derive(Debug)] +pub struct AlignedOperation { + origin_ptr: u64, + aligned_ptr: u64, + size: usize, + layout: Layout, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RequestType { + In, + Out, + Flush, + GetDeviceId, + Discard, + WriteZeroes, + Unsupported(u32), +} + +pub const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32; +pub struct BatchRequest { + pub offset: libc::off_t, + pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>, + pub user_data: u64, + pub request_type: RequestType, +} + +pub struct ExecuteAsync { + // `true` if the execution will complete asynchronously + pub async_complete: bool, + // request need to be batched for submission if any + pub batch_request: Option, +} + +#[derive(Debug)] +pub struct Request { + request_type: RequestType, + sector: u64, + data_descriptors: SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]>, + status_addr: GuestAddress, + pub writeback: bool, + aligned_operations: SmallVec<[AlignedOperation; DEFAULT_DESCRIPTOR_VEC_SIZE]>, + start: Instant, +} + +impl Request { + pub fn parse( + desc_chain: &mut DescriptorChain>>, + access_platform: Option<&dyn AccessPlatform>, + ) -> Result { + let hdr_desc = desc_chain + .next() + .ok_or(Error::DescriptorChainTooShort) + .inspect_err(|_| { + error!("Missing head descriptor"); + })?; + + // The head contains the request type which MUST be readable. + if hdr_desc.is_write_only() { + return Err(Error::UnexpectedWriteOnlyDescriptor); + } + + let hdr_desc_addr = hdr_desc + .addr() + .translate_gva(access_platform, hdr_desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?; + + let mut req = Request { + request_type: request_type(desc_chain.memory(), hdr_desc_addr)?, + sector: sector(desc_chain.memory(), hdr_desc_addr)?, + data_descriptors: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE), + status_addr: GuestAddress(0), + writeback: true, + aligned_operations: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE), + start: Instant::now(), + }; + + let status_desc; + let mut desc = desc_chain + .next() + .ok_or(Error::DescriptorChainTooShort) + .inspect_err(|_| { + error!("Only head descriptor present: request = {req:?}"); + })?; + + if desc.has_next() { + req.data_descriptors.reserve_exact(1); + while desc.has_next() { + if desc.is_write_only() && req.request_type == RequestType::Out { + return Err(Error::UnexpectedWriteOnlyDescriptor); + } + if desc.is_write_only() && req.request_type == RequestType::Discard { + return Err(Error::UnexpectedWriteOnlyDescriptor); + } + if desc.is_write_only() && req.request_type == RequestType::WriteZeroes { + return Err(Error::UnexpectedWriteOnlyDescriptor); + } + if !desc.is_write_only() && req.request_type == RequestType::In { + return Err(Error::UnexpectedReadOnlyDescriptor); + } + if !desc.is_write_only() && req.request_type == RequestType::GetDeviceId { + return Err(Error::UnexpectedReadOnlyDescriptor); + } + + req.data_descriptors.push(( + desc.addr() + .translate_gva(access_platform, desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?, + desc.len(), + )); + desc = desc_chain + .next() + .ok_or(Error::DescriptorChainTooShort) + .inspect_err(|_| { + error!("DescriptorChain corrupted: request = {req:?}"); + })?; + } + status_desc = desc; + } else { + status_desc = desc; + // Only flush requests are allowed to skip the data descriptor. + if req.request_type != RequestType::Flush { + error!("Need a data descriptor: request = {req:?}"); + return Err(Error::DescriptorChainTooShort); + } + } + + // The status MUST always be writable. + if !status_desc.is_write_only() { + return Err(Error::UnexpectedReadOnlyDescriptor); + } + + if status_desc.len() < 1 { + return Err(Error::DescriptorLengthTooSmall); + } + + req.status_addr = status_desc + .addr() + .translate_gva(access_platform, status_desc.len() as usize) + .map_err(|e| Error::GuestMemory(GuestMemoryError::IOError(e)))?; + + Ok(req) + } + + pub fn execute( + &self, + disk: &mut T, + disk_nsectors: u64, + mem: &vm_memory::GuestMemoryMmap, + serial: &[u8], + ) -> Result { + disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT)) + .map_err(ExecuteError::Seek)?; + let mut len = 0; + for (data_addr, data_len) in &self.data_descriptors { + let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE; + if u64::from(*data_len) % SECTOR_SIZE != 0 { + top += 1; + } + top = top + .checked_add(self.sector) + .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; + if top > disk_nsectors { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + + match self.request_type { + RequestType::In => { + let mut buf = vec![0u8; *data_len as usize]; + disk.read_exact(&mut buf).map_err(ExecuteError::ReadExact)?; + mem.read_exact_volatile_from( + *data_addr, + &mut buf.as_slice(), + *data_len as usize, + ) + .map_err(ExecuteError::Read)?; + len += data_len; + } + RequestType::Out => { + let mut buf: Vec = Vec::new(); + mem.write_all_volatile_to(*data_addr, &mut buf, *data_len as usize) + .map_err(ExecuteError::Write)?; + disk.write_all(&buf).map_err(ExecuteError::WriteAll)?; + if !self.writeback { + disk.flush().map_err(ExecuteError::Flush)?; + } + } + RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?, + RequestType::GetDeviceId => { + if (*data_len as usize) < serial.len() { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + mem.write_slice(serial, *data_addr) + .map_err(ExecuteError::Write)?; + } + RequestType::Discard => { + return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_DISCARD)); + } + RequestType::WriteZeroes => { + return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_WRITE_ZEROES)); + } + RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), + } + } + Ok(len) + } + + pub fn execute_async( + &mut self, + mem: &vm_memory::GuestMemoryMmap, + disk_nsectors: u64, + disk_image: &mut dyn AsyncIo, + serial: &[u8], + disable_sector0_writes: bool, + user_data: u64, + ) -> Result { + let sector = self.sector; + let request_type = self.request_type; + let offset = (sector << SECTOR_SHIFT) as libc::off_t; + let alignment = disk_image.alignment(); + + let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> = + SmallVec::with_capacity(self.data_descriptors.len()); + for &(data_addr, data_len) in &self.data_descriptors { + let _: u32 = data_len; // compiler-checked documentation + const _: () = assert!( + core::mem::size_of::() <= core::mem::size_of::(), + "unsupported platform" + ); + if data_len == 0 { + continue; + } + let mut top: u64 = u64::from(data_len) / SECTOR_SIZE; + if u64::from(data_len) % SECTOR_SIZE != 0 { + top += 1; + } + let data_len = data_len as usize; + top = top + .checked_add(sector) + .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; + if top > disk_nsectors { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + + let origin_ptr = mem + .get_slice(data_addr, data_len) + .map_err(ExecuteError::GetHostAddress)?; + assert!(origin_ptr.len() >= data_len); + let origin_ptr = origin_ptr.ptr_guard(); + + // O_DIRECT requires buffer addresses to be aligned to the + // backend device's logical block size. In case it's not properly + // aligned, an intermediate buffer is created with the correct + // alignment, and a copy from/to the origin buffer is performed, + // depending on the type of operation. + let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(alignment) { + origin_ptr.as_ptr() as *mut libc::c_void + } else { + let layout = Layout::from_size_align(data_len, alignment as usize).unwrap(); + // SAFETY: layout has non-zero size + let aligned_ptr = unsafe { alloc_zeroed(layout) }; + if aligned_ptr.is_null() { + return Err(ExecuteError::TemporaryBufferAllocation( + std::io::Error::last_os_error(), + )); + } + + // We need to perform the copy beforehand in case we're writing + // data out. + if request_type == RequestType::Out { + // SAFETY: destination buffer has been allocated with + // the proper size. + unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, data_len) }; + } + + // Store both origin and aligned pointers for complete_async() + // to process them. + self.aligned_operations.push(AlignedOperation { + origin_ptr: origin_ptr.as_ptr() as u64, + aligned_ptr: aligned_ptr as u64, + size: data_len, + layout, + }); + + aligned_ptr as *mut libc::c_void + }; + + let iovec = libc::iovec { + iov_base, + iov_len: data_len as libc::size_t, + }; + iovecs.push(iovec); + } + + let mut ret = ExecuteAsync { + async_complete: true, + batch_request: None, + }; + // Queue operations expected to be submitted. + match request_type { + RequestType::In => { + for (data_addr, data_len) in &self.data_descriptors { + mem.get_slice(*data_addr, *data_len as usize) + .map_err(ExecuteError::GetHostAddress)? + .bitmap() + .mark_dirty(0, *data_len as usize); + } + if disk_image.batch_requests_enabled() { + ret.batch_request = Some(BatchRequest { + offset, + iovecs, + user_data, + request_type, + }); + } else { + disk_image + .read_vectored(offset, &iovecs, user_data) + .map_err(ExecuteError::AsyncRead)?; + } + } + RequestType::Out => { + if disk_image.batch_requests_enabled() { + ret.batch_request = Some(BatchRequest { + offset, + iovecs, + user_data, + request_type, + }); + } else { + disk_image + .write_vectored(offset, &iovecs, user_data) + .map_err(ExecuteError::AsyncWrite)?; + } + } + RequestType::Flush => { + disk_image + .fsync(Some(user_data)) + .map_err(ExecuteError::AsyncFlush)?; + } + RequestType::GetDeviceId => { + let (data_addr, data_len) = if self.data_descriptors.len() == 1 { + (self.data_descriptors[0].0, self.data_descriptors[0].1) + } else { + return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); + }; + if (data_len as usize) < serial.len() { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + mem.write_slice(serial, data_addr) + .map_err(ExecuteError::Write)?; + ret.async_complete = false; + return Ok(ret); + } + RequestType::Discard => { + let (data_addr, data_len) = if self.data_descriptors.len() == 1 { + (self.data_descriptors[0].0, self.data_descriptors[0].1) + } else { + return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); + }; + + if data_len < DISCARD_WZ_SEG_SIZE { + return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); + } + if data_len > DISCARD_WZ_MAX_PAYLOAD { + return Err(ExecuteError::BadRequest(Error::TooManySegments( + data_len.div_ceil(DISCARD_WZ_SEG_SIZE), + ))); + } + + let mut discard_sector = [0u8; 8]; + let mut discard_num_sectors = [0u8; 4]; + let mut discard_flags = [0u8; 4]; + + let sector_addr = data_addr.checked_add(DISCARD_WZ_SECTOR_OFFSET).unwrap(); + mem.read_slice(&mut discard_sector, sector_addr) + .map_err(ExecuteError::Read)?; + + let num_sectors_addr = data_addr + .checked_add(DISCARD_WZ_NUM_SECTORS_OFFSET) + .unwrap(); + mem.read_slice(&mut discard_num_sectors, num_sectors_addr) + .map_err(ExecuteError::Read)?; + + let flags_addr = data_addr.checked_add(DISCARD_WZ_FLAGS_OFFSET).unwrap(); + mem.read_slice(&mut discard_flags, flags_addr) + .map_err(ExecuteError::Read)?; + + let discard_flags = u32::from_le_bytes(discard_flags); + // Per virtio spec v1.2 reject discard if any flag is set, including unmap. + if discard_flags != 0 { + warn!("Unsupported flags {discard_flags:#x} in discard request"); + return Err(ExecuteError::UnsupportedFlags { + request_type: VIRTIO_BLK_T_DISCARD, + flags: discard_flags, + }); + } + + let discard_sector = u64::from_le_bytes(discard_sector); + + if discard_sector == 0 && disable_sector0_writes { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + + let discard_num_sectors = u32::from_le_bytes(discard_num_sectors); + + let top = discard_sector + .checked_add(discard_num_sectors as u64) + .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; + if top > disk_nsectors { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + + let discard_offset = discard_sector * SECTOR_SIZE; + let discard_length = (discard_num_sectors as u64) * SECTOR_SIZE; + + disk_image + .punch_hole(discard_offset, discard_length, user_data) + .map_err(ExecuteError::AsyncPunchHole)?; + } + RequestType::WriteZeroes => { + let (data_addr, data_len) = if self.data_descriptors.len() == 1 { + (self.data_descriptors[0].0, self.data_descriptors[0].1) + } else { + return Err(ExecuteError::BadRequest(Error::TooManyDescriptors)); + }; + + if data_len < DISCARD_WZ_SEG_SIZE { + return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall)); + } + if data_len > DISCARD_WZ_MAX_PAYLOAD { + return Err(ExecuteError::BadRequest(Error::TooManySegments( + data_len.div_ceil(DISCARD_WZ_SEG_SIZE), + ))); + } + + let mut wz_sector = [0u8; 8]; + let mut wz_num_sectors = [0u8; 4]; + let mut wz_flags = [0u8; 4]; + + let sector_addr = data_addr.checked_add(DISCARD_WZ_SECTOR_OFFSET).unwrap(); + mem.read_slice(&mut wz_sector, sector_addr) + .map_err(ExecuteError::Read)?; + + let num_sectors_addr = data_addr + .checked_add(DISCARD_WZ_NUM_SECTORS_OFFSET) + .unwrap(); + mem.read_slice(&mut wz_num_sectors, num_sectors_addr) + .map_err(ExecuteError::Read)?; + + let flags_addr = data_addr.checked_add(DISCARD_WZ_FLAGS_OFFSET).unwrap(); + mem.read_slice(&mut wz_flags, flags_addr) + .map_err(ExecuteError::Read)?; + + let wz_sector = u64::from_le_bytes(wz_sector); + let wz_num_sectors = u32::from_le_bytes(wz_num_sectors); + + let wz_flags = u32::from_le_bytes(wz_flags); + // Per virtio spec v1.2 reject write zeroes if any unknown flag is set. + if (wz_flags & !VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP) != 0 { + warn!("Unsupported flags {wz_flags:#x} in write zeroes request"); + return Err(ExecuteError::UnsupportedFlags { + request_type: VIRTIO_BLK_T_WRITE_ZEROES, + flags: wz_flags, + }); + } + + let wz_offset = wz_sector * SECTOR_SIZE; + if wz_offset == 0 && disable_sector0_writes { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + + let top = wz_sector + .checked_add(wz_num_sectors as u64) + .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; + if top > disk_nsectors { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + + let wz_length = (wz_num_sectors as u64) * SECTOR_SIZE; + + if wz_flags & VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP != 0 { + disk_image + .punch_hole(wz_offset, wz_length, user_data) + .map_err(ExecuteError::AsyncPunchHole)?; + } else { + disk_image + .write_zeroes(wz_offset, wz_length, user_data) + .map_err(ExecuteError::AsyncWriteZeroes)?; + } + } + RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), + } + + Ok(ret) + } + + pub fn complete_async(&mut self) -> Result<(), Error> { + for aligned_operation in self.aligned_operations.drain(..) { + // We need to perform the copy after the data has been read inside + // the aligned buffer in case we're reading data in. + if self.request_type == RequestType::In { + // SAFETY: origin buffer has been allocated with the + // proper size. + unsafe { + std::ptr::copy( + aligned_operation.aligned_ptr as *const u8, + aligned_operation.origin_ptr as *mut u8, + aligned_operation.size, + ); + }; + } + + // Free the temporary aligned buffer. + // SAFETY: aligned_ptr was allocated by alloc_zeroed with the same + // layout + unsafe { + dealloc( + aligned_operation.aligned_ptr as *mut u8, + aligned_operation.layout, + ); + }; + } + + Ok(()) + } + + #[inline] + pub fn data_descriptors( + &self, + ) -> &SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]> { + &self.data_descriptors + } + + #[inline] + pub fn status_addr(&self) -> GuestAddress { + self.status_addr + } + + #[inline] + pub fn start(&self) -> Instant { + self.start + } + + #[inline] + pub fn sector(&self) -> u64 { + self.sector + } + + #[inline] + pub fn request_type(&self) -> RequestType { + self.request_type + } +} diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 25c10fa500..cf009f05fc 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -131,14 +131,14 @@ impl VhostUserBlkThread { let len = match Request::parse(&mut desc_chain, None) { Ok(mut request) => { debug!("element is a valid request"); - request.set_writeback(self.writeback.load(Ordering::Acquire)); + request.writeback = self.writeback.load(Ordering::Acquire); let (status, len) = match request.execute( &mut self.disk_image.lock().unwrap().deref_mut(), self.disk_nsectors, desc_chain.memory(), &self.serial, ) { - Ok(_) if request.request_type == RequestType::GetDeviceId => { + Ok(_) if request.request_type() == RequestType::GetDeviceId => { (VIRTIO_BLK_S_OK as u8, self.serial.len() as u32 + 1) } Ok(l) => (VIRTIO_BLK_S_OK as u8, l + 1), @@ -146,7 +146,7 @@ impl VhostUserBlkThread { }; desc_chain .memory() - .write_obj(status, request.status_addr) + .write_obj(status, request.status_addr()) .unwrap(); len } diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 5ecb0d9fe2..8f4dd2eb87 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -182,7 +182,7 @@ impl BlockEpollHandler { request: &Request, disable_sector0_writes: bool, ) -> result::Result<(), ExecuteError> { - let request_type = request.request_type; + let request_type = request.request_type(); if (has_feature(features, VIRTIO_BLK_F_RO.into())) && !(request_type == RequestType::In || request_type == RequestType::GetDeviceId @@ -197,7 +197,7 @@ impl BlockEpollHandler { return Err(ExecuteError::ReadOnly); } - if request_type == RequestType::Out && disable_sector0_writes && request.sector == 0 { + if request_type == RequestType::Out && disable_sector0_writes && request.sector() == 0 { warn!( "Attempting to write to sector 0 on a raw disk without specifying image_type=raw" ); @@ -262,7 +262,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese warn!("Request check failed: {request:x?} {e:?}"); desc_chain .memory() - .write_obj(VIRTIO_BLK_S_IOERR, request.status_addr) + .write_obj(VIRTIO_BLK_S_IOERR, request.status_addr()) .map_err(Error::RequestStatus)?; // If no asynchronous operation has been submitted, we can @@ -286,11 +286,11 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese break; } // Exercise the rate limiter only if this request is of data transfer type. - if request.request_type == RequestType::In - || request.request_type == RequestType::Out + if request.request_type() == RequestType::In + || request.request_type() == RequestType::Out { let mut bytes = Wrapping(0); - for (_, data_len) in &request.data_descriptors { + for (_, data_len) in request.data_descriptors() { bytes += Wrapping(*data_len as u64); } @@ -307,7 +307,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese } } - request.set_writeback(self.writeback.load(Ordering::Acquire)); + request.writeback = self.writeback.load(Ordering::Acquire); let result = request.execute_async( desc_chain.memory(), @@ -329,7 +329,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese _ => { unreachable!( "Unexpected batch request type: {:?}", - request.request_type + request.request_type() ) } } @@ -346,11 +346,11 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese desc_chain .memory() - .write_obj(status as u8, request.status_addr) + .write_obj(status as u8, request.status_addr()) .map_err(Error::RequestStatus)?; let len = if status == VIRTIO_BLK_S_OK - && request.request_type == RequestType::GetDeviceId + && request.request_type() == RequestType::GetDeviceId { self.serial.len() as u32 + 1 } else { @@ -377,7 +377,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese warn!("Request failed with batch submission: {request:x?} {e:?}"); let desc_index = user_data; let mem = self.mem.memory(); - mem.write_obj(VIRTIO_BLK_S_IOERR as u8, request.status_addr) + mem.write_obj(VIRTIO_BLK_S_IOERR as u8, request.status_addr()) .map_err(Error::RequestStatus)?; queue .add_used(mem.deref(), desc_index, 1) @@ -454,7 +454,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese request.complete_async().map_err(Error::RequestCompleting)?; - let latency = request.start.elapsed().as_micros() as u64; + let latency = request.start().elapsed().as_micros() as u64; let read_ops_last = self.counters.read_ops.load(Ordering::Relaxed); let write_ops_last = self.counters.write_ops.load(Ordering::Relaxed); let read_max = self.counters.read_latency_max.load(Ordering::Relaxed); @@ -462,9 +462,9 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese let mut read_avg = self.counters.read_latency_avg.load(Ordering::Relaxed); let mut write_avg = self.counters.write_latency_avg.load(Ordering::Relaxed); let (status, len) = if result >= 0 { - match request.request_type { + match request.request_type() { RequestType::In => { - for (_, data_len) in &request.data_descriptors { + for (_, data_len) in request.data_descriptors() { read_bytes += Wrapping(*data_len as u64); } read_ops += Wrapping(1); @@ -496,7 +496,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese if !request.writeback { self.disk_image.fsync(None).map_err(Error::Fsync)?; } - for (_, data_len) in &request.data_descriptors { + for (_, data_len) in request.data_descriptors() { write_bytes += Wrapping(*data_len as u64); } write_ops += Wrapping(1); @@ -535,7 +535,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese .write_latency_avg .store(write_avg, Ordering::Relaxed); - let len = if request.request_type == RequestType::In { + let len = if request.request_type() == RequestType::In { result as u32 + 1 } else { 1 @@ -550,7 +550,7 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese (VIRTIO_BLK_S_IOERR as u8, 1) }; - mem.write_obj(status, request.status_addr) + mem.write_obj(status, request.status_addr()) .map_err(Error::RequestStatus)?; let queue = &mut self.queue; From d56253196bfda3c472abfbdd71300b81715a3f5a Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 21 Apr 2026 18:58:15 +0000 Subject: [PATCH 1361/1893] 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 --- cloud-hypervisor/tests/integration.rs | 1 + vmm/src/config.rs | 47 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index f8915ec4a1..d0a7b49c88 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -66,6 +66,7 @@ mod common_parallel { } #[test] + #[cfg_attr(target_arch = "x86_64", should_panic)] fn test_cpu_topology_421() { test_cpu_topology(4, 2, 1, false); } diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 7efda7c05a..3faa7ff75f 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -250,6 +250,9 @@ pub enum ValidationError { /// CPU topology count doesn't match max #[error("Product of CPU topology parts does not match maximum vCPU")] CpuTopologyCount, + /// CPU topology uses too many threads per core + #[error("CPU topology supports at most 2 threads per core")] + CpuTopologyThreadsPerCore, /// One part of the CPU topology was zero #[error("No part of the CPU topology can be zero")] CpuTopologyZeroPart, @@ -2982,6 +2985,11 @@ impl VmConfig { return Err(ValidationError::CpuTopologyZeroPart); } + #[cfg(target_arch = "x86_64")] + if t.threads_per_core > 2 { + return Err(ValidationError::CpuTopologyThreadsPerCore); + } + // The setting of dies doesn't apply on AArch64. // Only '1' value is accepted, so its impact on the vcpu topology // setting can be ignored. @@ -5043,6 +5051,45 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" Err(ValidationError::CpuTopologyCount) ); + let mut still_valid_config = valid_config.clone(); + still_valid_config.cpus.max_vcpus = 8; + still_valid_config.cpus.boot_vcpus = 8; + still_valid_config.cpus.topology = Some(CpuTopology { + threads_per_core: 1, + cores_per_die: 8, + dies_per_package: 1, + packages: 1, + }); + still_valid_config.validate().unwrap(); + + let mut still_valid_config = valid_config.clone(); + still_valid_config.cpus.max_vcpus = 8; + still_valid_config.cpus.boot_vcpus = 8; + still_valid_config.cpus.topology = Some(CpuTopology { + threads_per_core: 2, + cores_per_die: 4, + dies_per_package: 1, + packages: 1, + }); + still_valid_config.validate().unwrap(); + + #[cfg(target_arch = "x86_64")] + { + let mut invalid_config = valid_config.clone(); + invalid_config.cpus.max_vcpus = 6; + invalid_config.cpus.boot_vcpus = 6; + invalid_config.cpus.topology = Some(CpuTopology { + threads_per_core: 3, + cores_per_die: 2, + dies_per_package: 1, + packages: 1, + }); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::CpuTopologyThreadsPerCore) + ); + } + let mut invalid_config = valid_config.clone(); invalid_config.disks = Some(vec![DiskConfig { vhost_socket: Some("/path/to/sock".to_owned()), From cba2b7f773e41070e7eb026213f62a881f885979 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 21 Apr 2026 20:59:20 +0200 Subject: [PATCH 1362/1893] 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 --- block/src/factory.rs | 197 +++++++++++++++++++++++++++++++++++++++++++ block/src/lib.rs | 1 + 2 files changed, 198 insertions(+) create mode 100644 block/src/factory.rs diff --git a/block/src/factory.rs b/block/src/factory.rs new file mode 100644 index 0000000000..9925dc162d --- /dev/null +++ b/block/src/factory.rs @@ -0,0 +1,197 @@ +// 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; + +use crate::disk_file::AsyncFullDiskFile; +use crate::error::{BlockError, BlockErrorKind, BlockResult}; +#[cfg(feature = "io_uring")] +use crate::fixed_vhd_async::FixedVhdDiskAsync; +use crate::fixed_vhd_sync::FixedVhdDiskSync; +#[cfg(feature = "io_uring")] +use crate::qcow_async::QcowDiskAsync; +use crate::qcow_sync::QcowDiskSync; +use crate::raw_async_aio::RawFileDiskAio; +use crate::raw_sync::RawFileDiskSync; +use crate::vhdx_sync::VhdxDiskSync; +use crate::{ + ImageType, block_aio_is_supported, detect_image_type, open_disk_image, preallocate_disk, +}; +#[cfg(feature = "io_uring")] +use crate::{block_io_uring_is_supported, raw_async::RawFileDisk}; + +/// 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, +} + +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 = 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 = 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 { + 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 = 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> { + info!("Opening VHDX disk file with synchronous backend"); + Ok(Box::new( + VhdxDiskSync::new(file).map_err(|e| e.with_path(options.path))?, + )) +} + +fn open_fixed_vhd( + file: fs::File, + options: &DiskOpenOptions<'_>, +) -> BlockResult> { + #[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( + FixedVhdDiskAsync::new(file).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( + FixedVhdDiskSync::new(file).map_err(|e| e.with_path(options.path))?, + )) +} + +fn open_raw( + file: fs::File, + options: &DiskOpenOptions<'_>, +) -> BlockResult> { + 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(RawFileDisk::new(file))); + } + 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(RawFileDiskAio::new(file))); + } + info!("AIO runtime probe failed for RAW, using synchronous backend"); + } + + info!("Opening RAW disk file with synchronous backend"); + Ok(Box::new(RawFileDiskSync::new(file))) +} + +fn open_qcow2( + file: fs::File, + options: &DiskOpenOptions<'_>, +) -> BlockResult> { + #[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( + QcowDiskAsync::new(file, options.direct, options.backing_files, options.sparse) + .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( + QcowDiskSync::new(file, options.direct, options.backing_files, options.sparse) + .map_err(|e| e.with_path(options.path))?, + )) +} diff --git a/block/src/lib.rs b/block/src/lib.rs index da836aaaad..c7385e6f25 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -11,6 +11,7 @@ pub mod async_io; pub mod disk_file; pub mod error; +pub mod factory; pub mod fcntl; pub mod fixed_vhd; #[cfg(feature = "io_uring")] From 9ada7a9afd0b085a91ca5ffa2c746492eb66e903 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 21 Apr 2026 21:11:10 +0200 Subject: [PATCH 1363/1893] 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 --- block/src/factory.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/block/src/factory.rs b/block/src/factory.rs index 9925dc162d..b3514b279b 100644 --- a/block/src/factory.rs +++ b/block/src/factory.rs @@ -195,3 +195,32 @@ fn open_qcow2( .map_err(|e| e.with_path(options.path))?, )) } + +#[cfg(test)] +mod unit_tests { + use std::path::Path; + + use super::*; + + 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"), + } + } +} From 01c4e0512f3fc25ba8977314de2dc17d88dcfe61 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 21 Apr 2026 21:15:18 +0200 Subject: [PATCH 1364/1893] 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 --- block/src/factory.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/block/src/factory.rs b/block/src/factory.rs index b3514b279b..58d2255e0c 100644 --- a/block/src/factory.rs +++ b/block/src/factory.rs @@ -200,6 +200,8 @@ fn open_qcow2( mod unit_tests { use std::path::Path; + use vmm_sys_util::tempfile::TempFile; + use super::*; fn default_options(path: &Path) -> DiskOpenOptions<'_> { @@ -223,4 +225,14 @@ mod unit_tests { 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); + } } From 78784c2a3f62f6554d236e374965918ca7a8fa70 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 21 Apr 2026 21:34:12 +0200 Subject: [PATCH 1365/1893] 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 --- block/src/factory.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/block/src/factory.rs b/block/src/factory.rs index 58d2255e0c..0a3ddc9746 100644 --- a/block/src/factory.rs +++ b/block/src/factory.rs @@ -198,11 +198,13 @@ fn open_qcow2( #[cfg(test)] mod unit_tests { + use std::io::Write; use std::path::Path; use vmm_sys_util::tempfile::TempFile; use super::*; + use crate::qcow::{QcowFile, RawFile}; fn default_options(path: &Path) -> DiskOpenOptions<'_> { DiskOpenOptions { @@ -235,4 +237,18 @@ mod unit_tests { let opened = open_disk(&options).unwrap(); assert_eq!(opened.image_type, ImageType::Raw); } + + #[test] + fn detect_qcow2_image() { + let tmp = TempFile::new().unwrap(); + { + let raw = RawFile::new(tmp.as_file().try_clone().unwrap(), false); + let mut qcow = QcowFile::new(raw, 3, 100 * 1024 * 1024, true).unwrap(); + qcow.flush().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::Qcow2); + } } From 37693aa1415ff7a1a02aa089b905e4677821c5c4 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 21 Apr 2026 21:35:25 +0200 Subject: [PATCH 1366/1893] block: factory: Add test for readonly open Verify that open_disk() succeeds with readonly=true on a RAW image. Signed-off-by: Anatol Belski --- block/src/factory.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/block/src/factory.rs b/block/src/factory.rs index 0a3ddc9746..296b9c70cc 100644 --- a/block/src/factory.rs +++ b/block/src/factory.rs @@ -251,4 +251,15 @@ mod unit_tests { 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); + } } From 03a5c29c4849815e897db12a0e2f29d78e585f33 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 21 Apr 2026 21:39:29 +0200 Subject: [PATCH 1367/1893] 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 --- block/src/factory.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/block/src/factory.rs b/block/src/factory.rs index 296b9c70cc..7bfef67295 100644 --- a/block/src/factory.rs +++ b/block/src/factory.rs @@ -262,4 +262,24 @@ mod unit_tests { 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); + } } From 6b6150ebfcfd538cb54ec5e0e9b1d40f7b8f475f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 21 Apr 2026 22:17:06 +0200 Subject: [PATCH 1368/1893] 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 --- vmm/src/device_manager.rs | 205 +++----------------------------------- 1 file changed, 15 insertions(+), 190 deletions(-) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index e892391324..fbbfde4b31 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -32,21 +32,10 @@ use arch::layout::{APIC_START, IOAPIC_SIZE, IOAPIC_START}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use arch::{DeviceType, MmioDeviceInfo}; use arch::{NumaNodes, layout}; +use block::ImageType; use block::disk_file::DiskBackend; use block::error::BlockError; -use block::fixed_vhd_sync::FixedVhdDiskSync; -#[cfg(feature = "io_uring")] -use block::qcow_async::QcowDiskAsync; -use block::qcow_sync::QcowDiskSync; -use block::raw_async_aio::RawFileDiskAio; -use block::raw_sync::RawFileDiskSync; -use block::vhdx_sync::VhdxDiskSync; -use block::{ - ImageType, block_aio_is_supported, block_io_uring_is_supported, detect_image_type, - open_disk_image, preallocate_disk, -}; -#[cfg(feature = "io_uring")] -use block::{fixed_vhd_async::FixedVhdDiskAsync, raw_async::RawFileDisk}; +use block::factory::{DiskOpenOptions, open_disk}; #[cfg(target_arch = "riscv64")] use devices::aia; #[cfg(target_arch = "x86_64")] @@ -270,10 +259,6 @@ pub enum DeviceManagerError { #[error("Cannot create virtio-watchdog device")] CreateVirtioWatchdog(#[source] io::Error), - /// Failed to parse disk image format - #[error("Failed to parse disk image format")] - DetectImageType(#[source] BlockError), - /// Cannot create serial manager #[error("Cannot create serial manager")] CreateSerialManager(#[source] SerialManagerError), @@ -580,27 +565,6 @@ pub enum DeviceManagerError { /// Failed to set O_DIRECT flag to file descriptor #[error("Failed to set O_DIRECT flag to file descriptor")] SetDirectIo, - - /// Failed to create FixedVhdDiskAsync - #[error("Failed to create FixedVhdDiskAsync")] - CreateFixedVhdDiskAsync(#[source] BlockError), - - /// Failed to create FixedVhdDiskSync - #[error("Failed to create FixedVhdDiskSync")] - CreateFixedVhdDiskSync(#[source] BlockError), - - /// Failed to create QcowDiskSync - #[error("Failed to create QcowDiskSync")] - CreateQcowDiskSync(#[source] BlockError), - - /// Failed to create QcowDiskAsync - #[error("Failed to create QcowDiskAsync")] - CreateQcowDiskAsync(#[source] BlockError), - - /// Failed to create FixedVhdxDiskSync - #[error("Failed to create FixedVhdxDiskSync")] - CreateFixedVhdxDiskSync(#[source] BlockError), - /// Failed to add DMA mapping handler to virtio-mem device. #[error("Failed to add DMA mapping handler to virtio-mem device")] AddDmaMappingHandlerVirtioMem(#[source] virtio_devices::mem::Error), @@ -1128,13 +1092,6 @@ pub struct DeviceManager { // Force VIRTIO_F_ACCESS_PLATFORM on all virtio devices (e.g. for TDX/SEV-SNP) force_access_platform: bool, - - // io_uring availability if detected - io_uring_supported: Option, - - // aio availability if detected - aio_supported: Option, - // List of unique identifiers provided at boot through the configuration. boot_id_list: BTreeSet, @@ -1438,8 +1395,6 @@ impl DeviceManager { pvmemcontrol_devices: None, pvpanic_device: None, force_access_platform, - io_uring_supported: None, - aio_supported: None, boot_id_list, #[cfg(not(target_arch = "riscv64"))] timestamp, @@ -2652,29 +2607,6 @@ impl DeviceManager { Ok(()) } - - // Cache whether aio is supported to avoid checking for very block device - fn aio_is_supported(&mut self) -> bool { - if let Some(supported) = self.aio_supported { - return supported; - } - - let supported = block_aio_is_supported(); - self.aio_supported = Some(supported); - supported - } - - // Cache whether io_uring is supported to avoid probing for very block device - fn io_uring_is_supported(&mut self) -> bool { - if let Some(supported) = self.io_uring_supported { - return supported; - } - - let supported = block_io_uring_is_supported(); - self.io_uring_supported = Some(supported); - supported - } - /// Creates a [`MetaVirtioDevice`] from the provided [`DiskConfig`]. /// /// Depending on the config, this is a [`vhost_user::Blk`] device or a [`virtio_devices::Block`] @@ -2735,22 +2667,23 @@ impl DeviceManager { vhost_user_block as Arc>, ) } else { - let mut options = OpenOptions::new(); - options.read(true); - options.write(!disk_cfg.readonly); - if disk_cfg.direct { - options.custom_flags(libc::O_DIRECT); - } - // Open block device path let disk_path = disk_cfg .path .as_ref() .ok_or(DeviceManagerError::NoDiskPath)?; - let mut file: File = - open_disk_image(disk_path, &options).map_err(DeviceManagerError::Disk)?; - let detected_image_type = - detect_image_type(&mut file).map_err(DeviceManagerError::DetectImageType)?; + let opened = open_disk(&DiskOpenOptions { + path: disk_path, + readonly: disk_cfg.readonly, + direct: disk_cfg.direct, + sparse: disk_cfg.sparse, + backing_files: disk_cfg.backing_files, + disable_io_uring: disk_cfg.disable_io_uring, + disable_aio: disk_cfg.disable_aio, + }) + .map_err(DeviceManagerError::Disk)?; + + let detected_image_type = opened.image_type; let mut disable_sector0_writes = false; if disk_cfg.image_type == ImageType::Unknown { @@ -2786,115 +2719,7 @@ impl DeviceManager { warn!("Enabling backing_files option only applies for QCOW2 files"); } - let image = match disk_cfg.image_type { - ImageType::FixedVhd => { - // Use asynchronous backend relying on io_uring if the - // syscalls are supported. - if cfg!(feature = "io_uring") - && !disk_cfg.disable_io_uring - && self.io_uring_is_supported() - { - info!("Using asynchronous fixed VHD disk file (io_uring)"); - - #[cfg(not(feature = "io_uring"))] - unreachable!("Checked in if statement above"); - #[cfg(feature = "io_uring")] - { - DiskBackend::Next(Box::new( - FixedVhdDiskAsync::new(file) - .map_err(DeviceManagerError::CreateFixedVhdDiskAsync)?, - )) - } - } else { - info!("Using synchronous fixed VHD disk file"); - DiskBackend::Next(Box::new( - FixedVhdDiskSync::new(file) - .map_err(DeviceManagerError::CreateFixedVhdDiskSync)?, - )) - } - } - ImageType::Raw => { - // For non-sparse RAW disks, preallocate disk space - if !disk_cfg.readonly - && !disk_cfg.sparse - && let Some(path) = &disk_cfg.path - { - preallocate_disk(&file, path); - } - - // Use asynchronous backend relying on io_uring if the - // syscalls are supported. - if cfg!(feature = "io_uring") - && !disk_cfg.disable_io_uring - && self.io_uring_is_supported() - { - info!("Using asynchronous RAW disk file (io_uring)"); - - #[cfg(not(feature = "io_uring"))] - unreachable!("Checked in if statement above"); - #[cfg(feature = "io_uring")] - { - DiskBackend::Next(Box::new(RawFileDisk::new(file))) - } - } else if !disk_cfg.disable_aio && self.aio_is_supported() { - info!("Using asynchronous RAW disk file (aio)"); - DiskBackend::Next(Box::new(RawFileDiskAio::new(file))) - } else { - info!("Using synchronous RAW disk file"); - DiskBackend::Next(Box::new(RawFileDiskSync::new(file))) - } - } - ImageType::Qcow2 => { - if cfg!(feature = "io_uring") - && !disk_cfg.disable_io_uring - && self.io_uring_is_supported() - { - info!("Using asynchronous QCOW2 disk file (io_uring)"); - - #[cfg(not(feature = "io_uring"))] - unreachable!("Checked in if statement above"); - #[cfg(feature = "io_uring")] - { - DiskBackend::Next(Box::new( - QcowDiskAsync::new( - file, - disk_cfg.direct, - disk_cfg.backing_files, - disk_cfg.sparse, - ) - .map_err(|e| match &disk_cfg.path { - Some(p) => e.with_path(p), - None => e, - }) - .map_err(DeviceManagerError::CreateQcowDiskAsync)?, - )) - } - } else { - info!("Using synchronous QCOW2 disk file"); - DiskBackend::Next(Box::new( - QcowDiskSync::new( - file, - disk_cfg.direct, - disk_cfg.backing_files, - disk_cfg.sparse, - ) - .map_err(|e| match &disk_cfg.path { - Some(p) => e.with_path(p), - None => e, - }) - .map_err(DeviceManagerError::CreateQcowDiskSync)?, - )) - } - } - ImageType::Vhdx => { - info!("Using synchronous VHDX disk file"); - DiskBackend::Next(Box::new( - VhdxDiskSync::new(file) - .map_err(DeviceManagerError::CreateFixedVhdxDiskSync)?, - )) - } - ImageType::Unknown => unreachable!(), - }; + let image = DiskBackend::Next(opened.disk); let rate_limit_group = if let Some(rate_limiter_cfg) = disk_cfg.rate_limiter_config.as_ref() { From 835caf94135fc10083992bb9b3b4c13eaa8feb99 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 21 Apr 2026 22:32:58 +0200 Subject: [PATCH 1369/1893] 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 in the Block struct and its constructor. Remove the DiskBackend::Next wrapping in device_manager and the fuzz target. Signed-off-by: Anatol Belski --- block/src/disk_file.rs | 83 +------------------------------------ fuzz/fuzz_targets/block.rs | 3 +- virtio-devices/src/block.rs | 6 +-- vmm/src/device_manager.rs | 5 +-- 4 files changed, 6 insertions(+), 91 deletions(-) diff --git a/block/src/disk_file.rs b/block/src/disk_file.rs index 372410aa2c..7f044ea7e3 100644 --- a/block/src/disk_file.rs +++ b/block/src/disk_file.rs @@ -34,10 +34,8 @@ //! `&mut self`. Errors are returned as [`BlockResult`]. use std::fmt::Debug; -use std::io; -use crate::async_io::{self, AsyncIo, BorrowedDiskFd}; -use crate::error::{BlockError, BlockErrorKind}; +use crate::async_io::{AsyncIo, BorrowedDiskFd}; use crate::{BlockResult, DiskTopology}; /// Reported capacity of a disk image. @@ -158,82 +156,3 @@ pub trait AsyncFullDiskFile: FullDiskFile + AsyncDiskFile {} /// Blanket implementation: any type implementing both [`FullDiskFile`] /// and [`AsyncDiskFile`] automatically satisfies [`AsyncFullDiskFile`]. impl AsyncFullDiskFile for T {} - -/// A disk backend that dispatches to either the existing [`async_io::DiskFile`] -/// trait or the next-generation [`AsyncFullDiskFile`] trait. -pub enum DiskBackend { - /// Existing disk file backend (raw, vhd, vhdx, etc.). - Legacy(Box), - /// Next-generation disk file backend (qcow2, and more formats as they migrate). - Next(Box), -} - -impl DiskBackend { - pub fn logical_size(&mut self) -> BlockResult { - match self { - Self::Legacy(d) => d - .logical_size() - .map_err(|e| BlockError::new(BlockErrorKind::Io, io::Error::other(e))), - Self::Next(d) => d.logical_size(), - } - } - - pub fn physical_size(&mut self) -> BlockResult { - match self { - Self::Legacy(d) => d - .physical_size() - .map_err(|e| BlockError::new(BlockErrorKind::Io, io::Error::other(e))), - Self::Next(d) => d.physical_size(), - } - } - - pub fn topology(&mut self) -> DiskTopology { - match self { - Self::Legacy(d) => d.topology(), - Self::Next(d) => d.topology(), - } - } - - pub fn supports_sparse_operations(&self) -> bool { - match self { - Self::Legacy(d) => d.supports_sparse_operations(), - Self::Next(d) => d.supports_sparse_operations(), - } - } - - pub fn supports_zero_flag(&self) -> bool { - match self { - Self::Legacy(d) => d.supports_zero_flag(), - Self::Next(d) => d.supports_zero_flag(), - } - } - - pub fn fd(&mut self) -> BorrowedDiskFd<'_> { - match self { - Self::Legacy(d) => d.fd(), - Self::Next(d) => d.fd(), - } - } - - pub fn create_async_io(&self, ring_depth: u32) -> BlockResult> { - match self { - Self::Legacy(d) => d - .create_async_io(ring_depth) - .map_err(|e| BlockError::new(BlockErrorKind::Io, io::Error::other(e))), - Self::Next(d) => d.create_async_io(ring_depth), - } - } - - pub fn resize(&mut self, new_size: u64) -> BlockResult<()> { - match self { - Self::Legacy(d) => d.resize(new_size).map_err(|e| match e { - async_io::DiskFileError::Unsupported => BlockError::new( - BlockErrorKind::UnsupportedFeature, - io::Error::other("resize not supported"), - ), - _ => BlockError::new(BlockErrorKind::Io, io::Error::other(e)), - }), - Self::Next(d) => d.resize(new_size), - } - } -} diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index abddc27b41..20717b2cff 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -15,7 +15,6 @@ use std::path::PathBuf; use std::sync::Arc; use std::{ffi, io}; -use block::disk_file::DiskBackend; use block::fcntl::LockGranularityChoice; use block::raw_sync::RawFileDiskSync; use libfuzzer_sys::{fuzz_target, Corpus}; @@ -55,7 +54,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { let queue_affinity = BTreeMap::new(); let mut block = Block::new( "tmp".to_owned(), - DiskBackend::Next(Box::new(RawFileDiskSync::new(disk_file))), + Box::new(RawFileDiskSync::new(disk_file)), PathBuf::from(""), false, false, diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 8f4dd2eb87..5765d5a398 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -20,7 +20,7 @@ use std::{io, result}; use anyhow::anyhow; use block::async_io::{AsyncIo, AsyncIoError}; -use block::disk_file::DiskBackend; +use block::disk_file::AsyncFullDiskFile; use block::error::BlockError; use block::fcntl::{LockError, LockGranularity, LockGranularityChoice, LockType, get_lock_state}; use block::{ @@ -713,7 +713,7 @@ impl EpollHelperHandler for BlockEpollHandler { pub struct Block { common: VirtioCommon, id: String, - disk_image: DiskBackend, + disk_image: Box, disk_path: PathBuf, disk_nsectors: Arc, config: VirtioBlockConfig, @@ -743,7 +743,7 @@ impl Block { #[allow(clippy::too_many_arguments)] pub fn new( id: String, - mut disk_image: DiskBackend, + disk_image: Box, disk_path: PathBuf, read_only: bool, access_platform_enabled: bool, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index fbbfde4b31..8a693c08ff 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -33,7 +33,6 @@ use arch::layout::{APIC_START, IOAPIC_SIZE, IOAPIC_START}; use arch::{DeviceType, MmioDeviceInfo}; use arch::{NumaNodes, layout}; use block::ImageType; -use block::disk_file::DiskBackend; use block::error::BlockError; use block::factory::{DiskOpenOptions, open_disk}; #[cfg(target_arch = "riscv64")] @@ -2719,8 +2718,6 @@ impl DeviceManager { warn!("Enabling backing_files option only applies for QCOW2 files"); } - let image = DiskBackend::Next(opened.disk); - let rate_limit_group = if let Some(rate_limiter_cfg) = disk_cfg.rate_limiter_config.as_ref() { // Create an anonymous RateLimiterGroup that is dropped when the Disk @@ -2764,7 +2761,7 @@ impl DeviceManager { let mut virtio_block = virtio_devices::Block::new( id.clone(), - image, + opened.disk, disk_cfg .path .as_ref() From 814e8edc0ca30c9539ce215266b8a976ed067f72 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 21 Apr 2026 22:37:27 +0200 Subject: [PATCH 1370/1893] 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 --- block/src/async_io.rs | 47 +++---------------------------------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/block/src/async_io.rs b/block/src/async_io.rs index f81056797a..bbdd77779d 100644 --- a/block/src/async_io.rs +++ b/block/src/async_io.rs @@ -8,7 +8,7 @@ use std::os::fd::{AsRawFd, OwnedFd, RawFd}; use thiserror::Error; use vmm_sys_util::eventfd::EventFd; -use crate::{BatchRequest, DiskTopology, SECTOR_SIZE}; +use crate::{BatchRequest, SECTOR_SIZE}; #[derive(Error, Debug)] pub enum DiskFileError { @@ -30,10 +30,10 @@ pub enum DiskFileError { pub type DiskFileResult = std::result::Result; -/// A wrapper for [`RawFd`] capturing the lifetime of a corresponding [`DiskFile`]. +/// A wrapper for [`RawFd`] capturing the lifetime of a corresponding disk file. /// /// This fulfills the same role as [`BorrowedFd`] but is tailored to the limitations -/// by some implementations of [`DiskFile`], which wrap the effective [`File`] +/// by some disk implementations, which wrap the effective [`File`] /// in an `Arc>`, making the use of [`BorrowedFd`] impossible. /// /// [`BorrowedFd`]: std::os::fd::BorrowedFd @@ -58,47 +58,6 @@ impl AsRawFd for BorrowedDiskFd<'_> { } } -/// 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; - /// Returns the physical size of the underlying file. - fn physical_size(&mut self) -> DiskFileResult; - fn create_async_io(&self, ring_depth: u32) -> DiskFileResult>; - fn topology(&mut self) -> DiskTopology { - DiskTopology::default() - } - fn resize(&mut self, _size: u64) -> DiskFileResult<()> { - Err(DiskFileError::Unsupported) - } - - /// Indicates support for sparse operations (punch hole, write zeroes, discard). - /// Override to return true when supported. - fn supports_sparse_operations(&self) -> bool { - false - } - - /// Indicates support for zero flag optimization in WRITE_ZEROES. Override - /// to return true when supported. - fn supports_zero_flag(&self) -> bool { - false - } - - /// 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. From 0a7c613da025f1dcd5dae856e1a9b49eb3e037b1 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 23 Apr 2026 00:35:42 -0400 Subject: [PATCH 1371/1893] vm-allocator: Remove a redundant check for zero alignment.is_power_of_two() already implies alignment != 0. Signed-off-by: Demi Marie Obenour --- vm-allocator/src/address.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm-allocator/src/address.rs b/vm-allocator/src/address.rs index 2eb99670e8..b36935f654 100644 --- a/vm-allocator/src/address.rs +++ b/vm-allocator/src/address.rs @@ -174,7 +174,7 @@ impl AddressAllocator { } let alignment = align_size.unwrap_or(4); - if !alignment.is_power_of_two() || alignment == 0 { + if !alignment.is_power_of_two() { return None; } From f84a940c43171d9dcb2e969641ba4e1eb77e89c5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 00:47:44 +0200 Subject: [PATCH 1372/1893] 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 --- block/src/lib.rs | 1 + block/src/raw_disk.rs | 79 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 block/src/raw_disk.rs diff --git a/block/src/lib.rs b/block/src/lib.rs index c7385e6f25..77fb503158 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -31,6 +31,7 @@ pub mod raw_async; pub mod raw_async_aio; #[cfg(test)] mod raw_async_io_tests; +pub mod raw_disk; pub mod raw_sync; mod request; pub mod vhd; diff --git a/block/src/raw_disk.rs b/block/src/raw_disk.rs new file mode 100644 index 0000000000..216b5d5bc9 --- /dev/null +++ b/block/src/raw_disk.rs @@ -0,0 +1,79 @@ +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +use std::fs::File; +use std::os::unix::io::AsRawFd; + +use log::warn; + +use crate::async_io::{BorrowedDiskFd, DiskFileError}; +use crate::error::{BlockError, BlockErrorKind, BlockResult}; +use crate::{DiskTopology, disk_file, probe_sparse_support, query_device_size}; + +/// Selects which async I/O backend a `RawDisk` uses. +#[derive(Clone, Copy, Debug)] +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, +} + +impl RawDisk { + pub fn new(file: File, backend: RawBackend) -> Self { + Self { file, backend } + } +} + +impl disk_file::DiskSize for RawDisk { + fn logical_size(&self) -> BlockResult { + 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 { + 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) + } +} From 0955a40060a28f6f3af59b1cba60ef8470125660 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 01:00:06 +0200 Subject: [PATCH 1373/1893] 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 --- block/src/raw_disk.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/block/src/raw_disk.rs b/block/src/raw_disk.rs index 216b5d5bc9..b12b0a6ac8 100644 --- a/block/src/raw_disk.rs +++ b/block/src/raw_disk.rs @@ -3,6 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause use std::fs::File; +use std::io; +use std::os::unix::fs::FileTypeExt; use std::os::unix::io::AsRawFd; use log::warn; @@ -77,3 +79,32 @@ impl disk_file::SparseCapable for RawDisk { 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))) + } + } +} From 10d44476974c03e9907af5158a4c14a1ce51e2d1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 10:31:37 +0200 Subject: [PATCH 1374/1893] 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 --- block/src/raw_disk.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/block/src/raw_disk.rs b/block/src/raw_disk.rs index b12b0a6ac8..78c1ad6fc9 100644 --- a/block/src/raw_disk.rs +++ b/block/src/raw_disk.rs @@ -9,8 +9,12 @@ use std::os::unix::io::AsRawFd; use log::warn; -use crate::async_io::{BorrowedDiskFd, DiskFileError}; +use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError}; use crate::error::{BlockError, BlockErrorKind, BlockResult}; +#[cfg(feature = "io_uring")] +use crate::raw_async::RawFileAsync; +use crate::raw_async_aio::RawFileAsyncAio; +use crate::raw_sync::RawFileSync; use crate::{DiskTopology, disk_file, probe_sparse_support, query_device_size}; /// Selects which async I/O backend a `RawDisk` uses. @@ -108,3 +112,33 @@ impl disk_file::Resizable for RawDisk { } } } + +impl disk_file::DiskFile for RawDisk {} + +impl disk_file::AsyncDiskFile for RawDisk { + fn try_clone(&self) -> BlockResult> { + let file = self + .file + .try_clone() + .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?; + Ok(Box::new(RawDisk { + file, + backend: self.backend, + })) + } + + fn create_async_io(&self, ring_depth: u32) -> BlockResult> { + match self.backend { + RawBackend::Sync => Ok(Box::new(RawFileSync::new(self.file.as_raw_fd()))), + #[cfg(feature = "io_uring")] + RawBackend::IoUring => Ok(Box::new(RawFileAsync::new( + self.file.as_raw_fd(), + ring_depth, + )?)), + RawBackend::Aio => Ok(Box::new(RawFileAsyncAio::new( + self.file.as_raw_fd(), + ring_depth, + )?)), + } + } +} From 4d0aa530f286c7336cde9f6bd680bea39da13ef5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 15:52:56 +0200 Subject: [PATCH 1375/1893] 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 --- fuzz/fuzz_targets/block.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index 20717b2cff..0d131b9351 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use std::{ffi, io}; use block::fcntl::LockGranularityChoice; -use block::raw_sync::RawFileDiskSync; +use block::raw_disk::{RawBackend, RawDisk}; use libfuzzer_sys::{fuzz_target, Corpus}; use seccompiler::SeccompAction; use virtio_devices::{Block, VirtioDevice, VirtioInterrupt, VirtioInterruptType}; @@ -54,7 +54,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { let queue_affinity = BTreeMap::new(); let mut block = Block::new( "tmp".to_owned(), - Box::new(RawFileDiskSync::new(disk_file)), + Box::new(RawDisk::new(disk_file, RawBackend::Sync)), PathBuf::from(""), false, false, From 556cb6be46bb4b65a53aa64db2173e9b0a7b5502 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 15:50:53 +0200 Subject: [PATCH 1376/1893] 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 --- performance-metrics/src/micro_bench_block.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 4e7cb52a34..6dbdee92fb 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -7,12 +7,10 @@ //! These run without booting a VM and measure hot path operations //! (e.g. AIO completion draining) at the syscall level. -use std::os::unix::io::AsRawFd; use std::time::Instant; -use block::async_io::AsyncIo; use block::disk_file::AsyncDiskFile; -use block::raw_async_aio::RawFileAsyncAio; +use block::raw_disk::{RawBackend, RawDisk}; use block::{BatchRequest, RequestType}; use crate::PerformanceTestControl; @@ -29,8 +27,10 @@ use crate::util::{ pub fn micro_bench_aio_drain(control: &PerformanceTestControl) -> f64 { let num_ops = control.num_ops.expect("num_ops required") as usize; let tmp = util::sized_tempfile(num_ops); - let fd = tmp.as_file().as_raw_fd(); - let mut aio = RawFileAsyncAio::new(fd, num_ops as u32).expect("failed to create AIO context"); + let disk = RawDisk::new(tmp.as_file().try_clone().unwrap(), RawBackend::Aio); + let mut aio = disk + .create_async_io(num_ops as u32) + .expect("failed to create AIO context"); let buf = vec![0xA5u8; BLOCK_SIZE as usize]; From 701767e5bef174b26458f0912fc10e21aef6979a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 13:15:52 +0200 Subject: [PATCH 1377/1893] 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 --- block/src/factory.rs | 13 +++-- block/src/lib.rs | 9 ++-- block/src/raw_async.rs | 108 ++----------------------------------- block/src/raw_async_aio.rs | 81 +--------------------------- block/src/raw_sync.rs | 84 ++--------------------------- 5 files changed, 17 insertions(+), 278 deletions(-) diff --git a/block/src/factory.rs b/block/src/factory.rs index 7bfef67295..4d31f98605 100644 --- a/block/src/factory.rs +++ b/block/src/factory.rs @@ -16,6 +16,8 @@ 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}; #[cfg(feature = "io_uring")] @@ -24,14 +26,11 @@ use crate::fixed_vhd_sync::FixedVhdDiskSync; #[cfg(feature = "io_uring")] use crate::qcow_async::QcowDiskAsync; use crate::qcow_sync::QcowDiskSync; -use crate::raw_async_aio::RawFileDiskAio; -use crate::raw_sync::RawFileDiskSync; +use crate::raw_disk::{RawBackend, RawDisk}; use crate::vhdx_sync::VhdxDiskSync; use crate::{ ImageType, block_aio_is_supported, detect_image_type, open_disk_image, preallocate_disk, }; -#[cfg(feature = "io_uring")] -use crate::{block_io_uring_is_supported, raw_async::RawFileDisk}; /// Options for opening a disk image via [`open_disk`]. pub struct DiskOpenOptions<'a> { @@ -156,7 +155,7 @@ fn open_raw( if !options.disable_io_uring { if io_uring_supported() { info!("Opening RAW disk file with io_uring backend"); - return Ok(Box::new(RawFileDisk::new(file))); + return Ok(Box::new(RawDisk::new(file, RawBackend::IoUring))); } info!("io_uring runtime probe failed for RAW, trying next backend"); } @@ -164,13 +163,13 @@ fn open_raw( if !options.disable_aio { if aio_supported() { info!("Opening RAW disk file with AIO backend"); - return Ok(Box::new(RawFileDiskAio::new(file))); + return Ok(Box::new(RawDisk::new(file, RawBackend::Aio))); } info!("AIO runtime probe failed for RAW, using synchronous backend"); } info!("Opening RAW disk file with synchronous backend"); - Ok(Box::new(RawFileDiskSync::new(file))) + Ok(Box::new(RawDisk::new(file, RawBackend::Sync))) } fn open_qcow2( diff --git a/block/src/lib.rs b/block/src/lib.rs index 77fb503158..ff59e514e9 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -24,15 +24,12 @@ pub mod qcow_async; pub(crate) mod qcow_common; pub mod qcow_sync; #[cfg(feature = "io_uring")] -/// Async primitives based on `io-uring` -/// -/// Enabled with the `"io_uring"` feature -pub mod raw_async; -pub mod raw_async_aio; +pub(crate) mod raw_async; +pub(crate) mod raw_async_aio; #[cfg(test)] mod raw_async_io_tests; pub mod raw_disk; -pub mod raw_sync; +pub(crate) mod raw_sync; mod request; pub mod vhd; pub mod vhdx; diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 2b8de99e59..715a39ecca 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -2,118 +2,16 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use std::fs::File; -use std::io::{self, Error}; -use std::os::unix::fs::FileTypeExt; +use std::io::Error; use std::os::unix::io::{AsRawFd, RawFd}; use io_uring::{IoUring, opcode, types}; use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; -use log::warn; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; use crate::error::{BlockError, BlockErrorKind, BlockResult}; -use crate::{ - BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, disk_file, probe_sparse_support, - query_device_size, -}; - -#[derive(Debug)] -pub struct RawFileDisk { - file: File, -} - -impl RawFileDisk { - pub fn new(file: File) -> Self { - RawFileDisk { file } - } -} - -impl disk_file::DiskSize for RawFileDisk { - fn logical_size(&self) -> BlockResult { - 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 RawFileDisk { - fn physical_size(&self) -> BlockResult { - 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 RawFileDisk { - fn fd(&self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.file.as_raw_fd()) - } -} - -impl disk_file::Geometry for RawFileDisk { - 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 RawFileDisk { - fn supports_sparse_operations(&self) -> bool { - probe_sparse_support(&self.file) - } -} - -impl disk_file::Resizable for RawFileDisk { - 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 -c, 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 RawFileDisk {} - -impl disk_file::AsyncDiskFile for RawFileDisk { - fn try_clone(&self) -> BlockResult> { - let file = self - .file - .try_clone() - .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?; - Ok(Box::new(RawFileDisk { file })) - } - - fn create_async_io(&self, ring_depth: u32) -> BlockResult> { - let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth)?; - raw.alignment = - DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); - Ok(Box::new(raw) as Box) - } -} +use crate::{BatchRequest, RequestType, SECTOR_SIZE}; pub struct RawFileAsync { fd: RawFd, diff --git a/block/src/raw_async_aio.rs b/block/src/raw_async_aio.rs index 2b63aa24c3..3636fd7fc1 100644 --- a/block/src/raw_async_aio.rs +++ b/block/src/raw_async_aio.rs @@ -6,92 +6,15 @@ // use std::collections::VecDeque; -use std::fs::File; use std::os::unix::io::{AsRawFd, RawFd}; use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; -use log::warn; use vmm_sys_util::aio; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; +use crate::SECTOR_SIZE; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; use crate::error::{BlockError, BlockErrorKind, BlockResult}; -use crate::{DiskTopology, SECTOR_SIZE, disk_file, probe_sparse_support, query_device_size}; - -#[derive(Debug)] -pub struct RawFileDiskAio { - file: File, -} - -impl RawFileDiskAio { - pub fn new(file: File) -> Self { - RawFileDiskAio { file } - } -} - -impl disk_file::DiskSize for RawFileDiskAio { - fn logical_size(&self) -> BlockResult { - 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 RawFileDiskAio { - fn physical_size(&self) -> BlockResult { - 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 RawFileDiskAio { - fn fd(&self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.file.as_raw_fd()) - } -} - -impl disk_file::Geometry for RawFileDiskAio { - 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 RawFileDiskAio { - fn supports_sparse_operations(&self) -> bool { - probe_sparse_support(&self.file) - } -} - -impl disk_file::Resizable for RawFileDiskAio { - fn resize(&mut self, size: u64) -> BlockResult<()> { - self.file - .set_len(size) - .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e))) - } -} - -impl disk_file::DiskFile for RawFileDiskAio {} - -impl disk_file::AsyncDiskFile for RawFileDiskAio { - fn try_clone(&self) -> BlockResult> { - let file = self - .file - .try_clone() - .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?; - Ok(Box::new(RawFileDiskAio { file })) - } - - fn create_async_io(&self, ring_depth: u32) -> BlockResult> { - let mut raw = RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)?; - raw.alignment = - DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); - Ok(Box::new(raw) as Box) - } -} pub struct RawFileAsyncAio { fd: RawFd, diff --git a/block/src/raw_sync.rs b/block/src/raw_sync.rs index 1b83b6707f..659693f29c 100644 --- a/block/src/raw_sync.rs +++ b/block/src/raw_sync.rs @@ -3,91 +3,13 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause use std::collections::VecDeque; -use std::fs::File; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::RawFd; use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE}; -use log::warn; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; -use crate::error::{BlockError, BlockErrorKind, BlockResult}; -use crate::{DiskTopology, SECTOR_SIZE, disk_file, probe_sparse_support, query_device_size}; - -#[derive(Debug)] -pub struct RawFileDiskSync { - file: File, -} - -impl RawFileDiskSync { - pub fn new(file: File) -> Self { - RawFileDiskSync { file } - } -} - -impl disk_file::DiskSize for RawFileDiskSync { - fn logical_size(&self) -> BlockResult { - 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 RawFileDiskSync { - fn physical_size(&self) -> BlockResult { - 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 RawFileDiskSync { - fn fd(&self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.file.as_raw_fd()) - } -} - -impl disk_file::Geometry for RawFileDiskSync { - 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 RawFileDiskSync { - fn supports_sparse_operations(&self) -> bool { - probe_sparse_support(&self.file) - } -} - -impl disk_file::Resizable for RawFileDiskSync { - fn resize(&mut self, size: u64) -> BlockResult<()> { - self.file - .set_len(size) - .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e))) - } -} - -impl disk_file::DiskFile for RawFileDiskSync {} - -impl disk_file::AsyncDiskFile for RawFileDiskSync { - fn try_clone(&self) -> BlockResult> { - let file = self - .file - .try_clone() - .map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?; - Ok(Box::new(RawFileDiskSync { file })) - } - - fn create_async_io(&self, _ring_depth: u32) -> BlockResult> { - let mut raw = RawFileSync::new(self.file.as_raw_fd()); - raw.alignment = - DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size); - Ok(Box::new(raw) as Box) - } -} +use crate::SECTOR_SIZE; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; pub struct RawFileSync { fd: RawFd, From 2f74d568123673145e3f332d70e9eee6a4d58d42 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 14:09:18 +0200 Subject: [PATCH 1378/1893] 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 --- block/src/raw_disk.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/block/src/raw_disk.rs b/block/src/raw_disk.rs index 78c1ad6fc9..86c3b25b9c 100644 --- a/block/src/raw_disk.rs +++ b/block/src/raw_disk.rs @@ -142,3 +142,28 @@ impl disk_file::AsyncDiskFile for RawDisk { } } } + +#[cfg(test)] +mod unit_tests { + use std::fs::File; + + use vmm_sys_util::tempfile::TempFile; + + use super::*; + use crate::disk_file::DiskSize; + + 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); + assert_eq!(disk.logical_size().unwrap(), TEST_SIZE); + } +} From fa247864fe3a9853def732708dcd5c1b821c2cfc Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 14:57:16 +0200 Subject: [PATCH 1379/1893] 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 --- block/src/raw_disk.rs | 51 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/block/src/raw_disk.rs b/block/src/raw_disk.rs index 86c3b25b9c..79f45339ce 100644 --- a/block/src/raw_disk.rs +++ b/block/src/raw_disk.rs @@ -18,7 +18,7 @@ use crate::raw_sync::RawFileSync; use crate::{DiskTopology, disk_file, probe_sparse_support, query_device_size}; /// Selects which async I/O backend a `RawDisk` uses. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum RawBackend { /// Blocking I/O where the caller waits for completion. Sync, @@ -150,7 +150,8 @@ mod unit_tests { use vmm_sys_util::tempfile::TempFile; use super::*; - use crate::disk_file::DiskSize; + use crate::async_io::AsyncIo; + use crate::disk_file::{AsyncDiskFile, DiskSize}; const TEST_SIZE: u64 = 0x1122_3344; @@ -166,4 +167,50 @@ mod unit_tests { let disk = RawDisk::new(file, RawBackend::Sync); assert_eq!(disk.logical_size().unwrap(), TEST_SIZE); } + + fn assert_async_io_from_dyn(disk: &dyn AsyncDiskFile, expect_backend: RawBackend) { + let io: Box = disk.create_async_io(128).unwrap(); + assert_eq!( + io.batch_requests_enabled(), + expect_backend == RawBackend::IoUring + ); + } + + 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); + assert_sync_backend(&disk); + } + + #[test] + fn aio_backend_disables_batch_requests() { + let file = make_raw_file(); + let disk = RawDisk::new(file, RawBackend::Aio); + 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); + assert_io_uring_backend(&disk); + } } From f209e571afb61c563ae5688e4ad7875d7db6d2c7 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 15:05:32 +0200 Subject: [PATCH 1380/1893] 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 --- block/src/raw_disk.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/block/src/raw_disk.rs b/block/src/raw_disk.rs index 79f45339ce..0b65bc45e6 100644 --- a/block/src/raw_disk.rs +++ b/block/src/raw_disk.rs @@ -213,4 +213,31 @@ mod unit_tests { let disk = RawDisk::new(file, RawBackend::IoUring); 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); + 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); + 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); + assert_try_clone(&disk, RawBackend::IoUring); + } } From 86e4ccecb6e8fffc95a23e2fdaf1246f211f5786 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 15:28:56 +0200 Subject: [PATCH 1381/1893] 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 --- block/src/raw_disk.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/block/src/raw_disk.rs b/block/src/raw_disk.rs index 0b65bc45e6..616c6d9835 100644 --- a/block/src/raw_disk.rs +++ b/block/src/raw_disk.rs @@ -151,7 +151,7 @@ mod unit_tests { use super::*; use crate::async_io::AsyncIo; - use crate::disk_file::{AsyncDiskFile, DiskSize}; + use crate::disk_file::{AsyncDiskFile, DiskSize, Resizable}; const TEST_SIZE: u64 = 0x1122_3344; @@ -240,4 +240,13 @@ mod unit_tests { let disk = RawDisk::new(file, RawBackend::IoUring); 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); + let new_size = TEST_SIZE * 2; + disk.resize(new_size).unwrap(); + assert_eq!(disk.logical_size().unwrap(), new_size); + } } From 76069a73d6c64722abbbe40b98959ffa3141f6fd Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 15:33:20 +0200 Subject: [PATCH 1382/1893] 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 --- block/src/raw_disk.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/block/src/raw_disk.rs b/block/src/raw_disk.rs index 616c6d9835..a6d3eab5c2 100644 --- a/block/src/raw_disk.rs +++ b/block/src/raw_disk.rs @@ -151,7 +151,7 @@ mod unit_tests { use super::*; use crate::async_io::AsyncIo; - use crate::disk_file::{AsyncDiskFile, DiskSize, Resizable}; + use crate::disk_file::{AsyncDiskFile, DiskSize, PhysicalSize, Resizable}; const TEST_SIZE: u64 = 0x1122_3344; @@ -249,4 +249,12 @@ mod unit_tests { 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); + // Sparse file: physical size is less than logical size. + assert!(disk.physical_size().unwrap() < disk.logical_size().unwrap()); + } } From cb13100fe36786ad7653e1c494d329968298c137 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 22 Apr 2026 19:34:28 +0200 Subject: [PATCH 1383/1893] 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 --- block/src/fixed_vhd_disk.rs | 117 ++++++++++++++++++++++++++++++++++++ block/src/lib.rs | 1 + 2 files changed, 118 insertions(+) create mode 100644 block/src/fixed_vhd_disk.rs diff --git a/block/src/fixed_vhd_disk.rs b/block/src/fixed_vhd_disk.rs new file mode 100644 index 0000000000..9afd70a1e4 --- /dev/null +++ b/block/src/fixed_vhd_disk.rs @@ -0,0 +1,117 @@ +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use std::fs::File; +use std::io; +use std::os::unix::io::AsRawFd; + +use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError}; +use crate::disk_file::DiskSize; +use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; +use crate::fixed_vhd::FixedVhd; +#[cfg(feature = "io_uring")] +use crate::fixed_vhd_async::FixedVhdAsync; +use crate::fixed_vhd_sync::FixedVhdSync; +use crate::{BlockBackend, Error, disk_file}; + +#[derive(Debug)] +pub struct FixedVhdDisk { + inner: FixedVhd, + use_io_uring: bool, +} + +impl FixedVhdDisk { + pub fn new(file: File, use_io_uring: bool) -> BlockResult { + #[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, + }) + } +} + +impl disk_file::DiskSize for FixedVhdDisk { + fn logical_size(&self) -> BlockResult { + self.inner + .logical_size() + .map_err(|e| BlockError::new(BlockErrorKind::Io, e)) + } +} + +impl disk_file::PhysicalSize for FixedVhdDisk { + fn physical_size(&self) -> BlockResult { + 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 FixedVhdDisk { + fn fd(&self) -> BorrowedDiskFd<'_> { + BorrowedDiskFd::new(self.inner.as_raw_fd()) + } +} + +impl disk_file::Geometry for FixedVhdDisk {} + +impl disk_file::SparseCapable for FixedVhdDisk {} + +impl disk_file::Resizable for FixedVhdDisk { + 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 FixedVhdDisk {} + +impl disk_file::AsyncDiskFile for FixedVhdDisk { + fn try_clone(&self) -> BlockResult> { + Ok(Box::new(FixedVhdDisk { + inner: self.inner.clone(), + use_io_uring: self.use_io_uring, + })) + } + + fn create_async_io(&self, ring_depth: u32) -> BlockResult> { + let size = self.logical_size()?; + + if self.use_io_uring { + #[cfg(feature = "io_uring")] + { + return Ok(Box::new(FixedVhdAsync::new( + self.inner.as_raw_fd(), + 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(self.inner.as_raw_fd(), size).map_err(|e| { + BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)) + .with_op(ErrorOp::Open) + })?, + )) + } +} diff --git a/block/src/lib.rs b/block/src/lib.rs index ff59e514e9..cd87875d52 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -17,6 +17,7 @@ pub mod fixed_vhd; #[cfg(feature = "io_uring")] /// Enabled with the `"io_uring"` feature pub mod fixed_vhd_async; +pub mod fixed_vhd_disk; pub mod fixed_vhd_sync; pub mod qcow; #[cfg(feature = "io_uring")] From d42ea61a5f2b3a72d675691c4211e0ae874e1e1e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 22 Apr 2026 22:10:08 +0200 Subject: [PATCH 1384/1893] 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 --- block/src/factory.rs | 8 ++-- block/src/fixed_vhd_async.rs | 80 ++---------------------------------- block/src/fixed_vhd_sync.rs | 79 +---------------------------------- 3 files changed, 9 insertions(+), 158 deletions(-) diff --git a/block/src/factory.rs b/block/src/factory.rs index 4d31f98605..2d59f68724 100644 --- a/block/src/factory.rs +++ b/block/src/factory.rs @@ -20,9 +20,7 @@ use log::info; use crate::block_io_uring_is_supported; use crate::disk_file::AsyncFullDiskFile; use crate::error::{BlockError, BlockErrorKind, BlockResult}; -#[cfg(feature = "io_uring")] -use crate::fixed_vhd_async::FixedVhdDiskAsync; -use crate::fixed_vhd_sync::FixedVhdDiskSync; +use crate::fixed_vhd_disk::FixedVhdDisk; #[cfg(feature = "io_uring")] use crate::qcow_async::QcowDiskAsync; use crate::qcow_sync::QcowDiskSync; @@ -131,7 +129,7 @@ fn open_fixed_vhd( if io_uring_supported() { info!("Opening fixed VHD disk file with io_uring backend"); return Ok(Box::new( - FixedVhdDiskAsync::new(file).map_err(|e| e.with_path(options.path))?, + FixedVhdDisk::new(file, true).map_err(|e| e.with_path(options.path))?, )); } info!("io_uring runtime probe failed for fixed VHD, using synchronous backend"); @@ -139,7 +137,7 @@ fn open_fixed_vhd( info!("Opening fixed VHD disk file with synchronous backend"); Ok(Box::new( - FixedVhdDiskSync::new(file).map_err(|e| e.with_path(options.path))?, + FixedVhdDisk::new(file, false).map_err(|e| e.with_path(options.path))?, )) } diff --git a/block/src/fixed_vhd_async.rs b/block/src/fixed_vhd_async.rs index bdc5202843..58dbc9a93c 100644 --- a/block/src/fixed_vhd_async.rs +++ b/block/src/fixed_vhd_async.rs @@ -2,86 +2,14 @@ // // SPDX-License-Identifier: Apache-2.0 -use std::fs::File; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::RawFd; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; -use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; -use crate::fixed_vhd::FixedVhd; +use crate::BatchRequest; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; +use crate::error::BlockResult; use crate::raw_async::RawFileAsync; -use crate::{BatchRequest, BlockBackend, disk_file}; - -#[derive(Debug)] -pub struct FixedVhdDiskAsync(FixedVhd); - -impl FixedVhdDiskAsync { - pub fn new(file: File) -> BlockResult { - Ok(Self( - FixedVhd::new(file).map_err(|e| BlockError::from(e).with_op(ErrorOp::Open))?, - )) - } -} - -impl disk_file::DiskSize for FixedVhdDiskAsync { - fn logical_size(&self) -> BlockResult { - self.0 - .logical_size() - .map_err(|e| BlockError::new(BlockErrorKind::Io, e)) - } -} - -impl disk_file::PhysicalSize for FixedVhdDiskAsync { - fn physical_size(&self) -> BlockResult { - self.0.physical_size().map_err(|e| match e { - crate::Error::GetFileMetadata(io) => { - BlockError::new(BlockErrorKind::Io, crate::Error::GetFileMetadata(io)) - } - _ => unreachable!("unexpected error from FixedVhd::physical_size(): {e}"), - }) - } -} - -impl disk_file::DiskFd for FixedVhdDiskAsync { - fn fd(&self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.0.as_raw_fd()) - } -} - -impl disk_file::Geometry for FixedVhdDiskAsync {} - -impl disk_file::SparseCapable for FixedVhdDiskAsync {} - -impl disk_file::Resizable for FixedVhdDiskAsync { - fn resize(&mut self, _size: u64) -> BlockResult<()> { - Err(BlockError::new( - BlockErrorKind::UnsupportedFeature, - DiskFileError::ResizeError(std::io::Error::other("resize not supported for fixed VHD")), - ) - .with_op(ErrorOp::Resize)) - } -} - -impl disk_file::DiskFile for FixedVhdDiskAsync {} - -impl disk_file::AsyncDiskFile for FixedVhdDiskAsync { - fn try_clone(&self) -> BlockResult> { - Ok(Box::new(FixedVhdDiskAsync(self.0.clone()))) - } - - fn create_async_io(&self, ring_depth: u32) -> BlockResult> { - let size = self - .0 - .logical_size() - .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; - Ok(Box::new(FixedVhdAsync::new( - self.0.as_raw_fd(), - ring_depth, - size, - )?)) - } -} pub struct FixedVhdAsync { raw_file_async: RawFileAsync, diff --git a/block/src/fixed_vhd_sync.rs b/block/src/fixed_vhd_sync.rs index fd74e0123b..bcf16f4f5b 100644 --- a/block/src/fixed_vhd_sync.rs +++ b/block/src/fixed_vhd_sync.rs @@ -2,87 +2,12 @@ // // SPDX-License-Identifier: Apache-2.0 -use std::fs::File; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::RawFd; use vmm_sys_util::eventfd::EventFd; -use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; -use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; -use crate::fixed_vhd::FixedVhd; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; use crate::raw_sync::RawFileSync; -use crate::{BlockBackend, disk_file}; - -#[derive(Debug)] -pub struct FixedVhdDiskSync(FixedVhd); - -impl FixedVhdDiskSync { - pub fn new(file: File) -> BlockResult { - Ok(Self( - FixedVhd::new(file).map_err(|e| BlockError::from(e).with_op(ErrorOp::Open))?, - )) - } -} - -impl disk_file::DiskSize for FixedVhdDiskSync { - fn logical_size(&self) -> BlockResult { - self.0 - .logical_size() - .map_err(|e| BlockError::new(BlockErrorKind::Io, e)) - } -} - -impl disk_file::PhysicalSize for FixedVhdDiskSync { - fn physical_size(&self) -> BlockResult { - self.0.physical_size().map_err(|e| match e { - crate::Error::GetFileMetadata(io) => { - BlockError::new(BlockErrorKind::Io, crate::Error::GetFileMetadata(io)) - } - _ => unreachable!("unexpected error from FixedVhd::physical_size(): {e}"), - }) - } -} - -impl disk_file::DiskFd for FixedVhdDiskSync { - fn fd(&self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.0.as_raw_fd()) - } -} - -impl disk_file::Geometry for FixedVhdDiskSync {} - -impl disk_file::SparseCapable for FixedVhdDiskSync {} - -impl disk_file::Resizable for FixedVhdDiskSync { - fn resize(&mut self, _size: u64) -> BlockResult<()> { - Err(BlockError::new( - BlockErrorKind::UnsupportedFeature, - DiskFileError::ResizeError(std::io::Error::other("resize not supported for fixed VHD")), - ) - .with_op(ErrorOp::Resize)) - } -} - -impl disk_file::DiskFile for FixedVhdDiskSync {} - -impl disk_file::AsyncDiskFile for FixedVhdDiskSync { - fn try_clone(&self) -> BlockResult> { - Ok(Box::new(FixedVhdDiskSync(self.0.clone()))) - } - - fn create_async_io(&self, _ring_depth: u32) -> BlockResult> { - let size = self - .0 - .logical_size() - .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; - Ok(Box::new( - FixedVhdSync::new(self.0.as_raw_fd(), size).map_err(|e| { - BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)) - .with_op(ErrorOp::Open) - })?, - )) - } -} pub struct FixedVhdSync { raw_file_sync: RawFileSync, From 66d211e73734a99ff68247f4a510682b2110c90f Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 22 Apr 2026 23:12:55 +0200 Subject: [PATCH 1385/1893] 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 --- block/src/fixed_vhd_disk.rs | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/block/src/fixed_vhd_disk.rs b/block/src/fixed_vhd_disk.rs index 9afd70a1e4..3c611c8252 100644 --- a/block/src/fixed_vhd_disk.rs +++ b/block/src/fixed_vhd_disk.rs @@ -115,3 +115,51 @@ impl disk_file::AsyncDiskFile for FixedVhdDisk { )) } } + +#[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::disk_file::DiskSize; + + /// 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 = FixedVhdDisk::new(file, false).unwrap(); + assert_eq!(disk.logical_size().unwrap(), 0x1122_3344); + } +} From 95cbb2048a3fe2f74d7140197b0dd34ae0ed538b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 22 Apr 2026 23:32:21 +0200 Subject: [PATCH 1386/1893] 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 --- block/src/fixed_vhd_disk.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/block/src/fixed_vhd_disk.rs b/block/src/fixed_vhd_disk.rs index 3c611c8252..ee43c10edf 100644 --- a/block/src/fixed_vhd_disk.rs +++ b/block/src/fixed_vhd_disk.rs @@ -124,7 +124,8 @@ mod unit_tests { use vmm_sys_util::tempfile::TempFile; use super::*; - use crate::disk_file::DiskSize; + use crate::async_io::AsyncIo; + use crate::disk_file::{AsyncDiskFile, DiskSize}; /// Minimal fixed VHD footer (disk type = 2, current_size = 0x11223344). fn fixed_vhd_footer() -> &'static [u8] { @@ -162,4 +163,28 @@ mod unit_tests { let disk = FixedVhdDisk::new(file, 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 = disk.create_async_io(128).unwrap(); + assert_eq!(io.batch_requests_enabled(), expect_batch); + } + + fn assert_async_io(disk: &FixedVhdDisk, 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 = FixedVhdDisk::new(file, 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 = FixedVhdDisk::new(file, true).unwrap(); + assert_async_io(&disk, true); + } } From 8a4cd9b15d69d8e41ae7621bb2c9009a820d9223 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 22 Apr 2026 23:52:20 +0200 Subject: [PATCH 1387/1893] 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 --- block/src/fixed_vhd_disk.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/block/src/fixed_vhd_disk.rs b/block/src/fixed_vhd_disk.rs index ee43c10edf..5d62586368 100644 --- a/block/src/fixed_vhd_disk.rs +++ b/block/src/fixed_vhd_disk.rs @@ -187,4 +187,21 @@ mod unit_tests { let disk = FixedVhdDisk::new(file, true).unwrap(); assert_async_io(&disk, true); } + + #[test] + fn try_clone_preserves_sync_dispatch() { + let file = make_vhd_file(); + let disk = FixedVhdDisk::new(file, 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 = FixedVhdDisk::new(file, true).unwrap(); + let cloned = disk.try_clone().unwrap(); + assert_async_io_from_dyn(cloned.as_ref(), true); + } } From 07ce8e07450319ceb621cb837bfc481c58121160 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 00:12:08 +0200 Subject: [PATCH 1388/1893] 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 --- block/src/fixed_vhd_disk.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/block/src/fixed_vhd_disk.rs b/block/src/fixed_vhd_disk.rs index 5d62586368..f5c6e9547b 100644 --- a/block/src/fixed_vhd_disk.rs +++ b/block/src/fixed_vhd_disk.rs @@ -125,7 +125,7 @@ mod unit_tests { use super::*; use crate::async_io::AsyncIo; - use crate::disk_file::{AsyncDiskFile, DiskSize}; + use crate::disk_file::{AsyncDiskFile, DiskSize, Resizable}; /// Minimal fixed VHD footer (disk type = 2, current_size = 0x11223344). fn fixed_vhd_footer() -> &'static [u8] { @@ -204,4 +204,11 @@ mod unit_tests { 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 = FixedVhdDisk::new(file, false).unwrap(); + assert!(disk.resize(0x2000_0000).is_err()); + } } From 63cc26e1ce90f5d07466f541e087cb94550821ec Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 00:17:06 +0200 Subject: [PATCH 1389/1893] 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 --- block/src/fixed_vhd_disk.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/block/src/fixed_vhd_disk.rs b/block/src/fixed_vhd_disk.rs index f5c6e9547b..8a27cdf963 100644 --- a/block/src/fixed_vhd_disk.rs +++ b/block/src/fixed_vhd_disk.rs @@ -125,7 +125,7 @@ mod unit_tests { use super::*; use crate::async_io::AsyncIo; - use crate::disk_file::{AsyncDiskFile, DiskSize, Resizable}; + use crate::disk_file::{AsyncDiskFile, DiskSize, PhysicalSize, Resizable}; /// Minimal fixed VHD footer (disk type = 2, current_size = 0x11223344). fn fixed_vhd_footer() -> &'static [u8] { @@ -211,4 +211,12 @@ mod unit_tests { let mut disk = FixedVhdDisk::new(file, false).unwrap(); assert!(disk.resize(0x2000_0000).is_err()); } + + #[test] + fn physical_size_includes_footer() { + let file = make_vhd_file(); + let disk = FixedVhdDisk::new(file, false).unwrap(); + // Data region (0x1122_3344) + VHD footer (0x200). + assert_eq!(disk.physical_size().unwrap(), 0x1122_3344 + 0x200); + } } From 435d0ad47c9b790d37877a37cb99a9c31c2ff989 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 20 Apr 2026 16:15:03 +0200 Subject: [PATCH 1390/1893] 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 --- .github/workflows/integration-arm64.yaml | 5 +++++ .github/workflows/integration-x86-64.yaml | 5 +++++ scripts/dev_cli.sh | 2 ++ scripts/run_integration_tests_aarch64.sh | 17 +++++++++++------ scripts/run_integration_tests_live_migration.sh | 8 +++++++- scripts/run_integration_tests_x86_64.sh | 13 +++++++++---- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/.github/workflows/integration-arm64.yaml b/.github/workflows/integration-arm64.yaml index 873daaa747..d34d28c3c7 100644 --- a/.github/workflows/integration-arm64.yaml +++ b/.github/workflows/integration-arm64.yaml @@ -7,6 +7,11 @@ concurrency: jobs: build: 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 name: Tests (ARM64) runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'bookworm-arm64' }} steps: diff --git a/.github/workflows/integration-x86-64.yaml b/.github/workflows/integration-x86-64.yaml index 98e2a8dcdc..213442d98d 100644 --- a/.github/workflows/integration-x86-64.yaml +++ b/.github/workflows/integration-x86-64.yaml @@ -7,6 +7,11 @@ concurrency: jobs: build: 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: diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 3b94513216..a5d3947ce1 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -512,6 +512,7 @@ cmd_tests() { --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ --env TARGET_CC="$target_cc" \ + --env PARALLEL_INTEGRATION_TESTS_NUM="${PARALLEL_INTEGRATION_TESTS_NUM:-}" \ --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ "$CTR_IMAGE" \ @@ -613,6 +614,7 @@ cmd_tests() { --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ --env TARGET_CC="$target_cc" \ + --env PARALLEL_INTEGRATION_TESTS_NUM="${PARALLEL_INTEGRATION_TESTS_NUM:-}" \ --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ --env MIGRATABLE_VERSION="$MIGRATABLE_VERSION" \ diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 5a1dce52b9..fb9d5daa57 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -247,8 +247,13 @@ PAGE_NUM=$((12288 * 1024 / HUGEPAGESIZE)) echo "$PAGE_NUM" | sudo tee /proc/sys/vm/nr_hugepages sudo chmod a+rwX /dev/hugepages +TEST_THREADS_DEFAULT="$(($(nproc) / 4))" +if ! [[ "${PARALLEL_INTEGRATION_TESTS_NUM:-}" =~ ^[1-9][0-9]*$ ]]; then + PARALLEL_INTEGRATION_TESTS_NUM="${TEST_THREADS_DEFAULT}" +fi +echo "nproc:$(nproc), parallel_integration_tests:${PARALLEL_INTEGRATION_TESTS_NUM}" # Run all direct kernel boot (Device Tree) test cases in mod `parallel` -time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "common_parallel::$test_filter" -- ${test_binary_args[*]} +time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="${PARALLEL_INTEGRATION_TESTS_NUM}" "common_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? # Run some tests in sequence since the result could be affected by other tests @@ -262,7 +267,7 @@ fi # Run all ACPI test cases if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "aarch64_acpi::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="$TEST_THREADS_DEFAULT" "aarch64_acpi::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES @@ -270,7 +275,7 @@ fi # Run all test cases related to live migration if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="$TEST_THREADS_DEFAULT" "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? else exit $RES @@ -288,7 +293,7 @@ if [ $RES -eq 0 ]; then cargo build --features "mshv,dbus_api" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 # integration tests now do not reply on build feature "dbus_api" - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="$TEST_THREADS_DEFAULT" "dbus_api::$test_filter" -- ${test_binary_args[*]} RES=$? fi @@ -296,14 +301,14 @@ fi if [ $RES -eq 0 ]; then cargo build --features "mshv,fw_cfg" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="$TEST_THREADS_DEFAULT" "fw_cfg::$test_filter" -- ${test_binary_args[*]} RES=$? fi if [ $RES -eq 0 ]; then cargo build --features "mshv,ivshmem" --all --release --target "$BUILD_TARGET" export RUST_BACKTRACE=1 - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="$TEST_THREADS_DEFAULT" "ivshmem::$test_filter" -- ${test_binary_args[*]} RES=$? fi diff --git a/scripts/run_integration_tests_live_migration.sh b/scripts/run_integration_tests_live_migration.sh index 11cf59b41b..5198e2c896 100755 --- a/scripts/run_integration_tests_live_migration.sh +++ b/scripts/run_integration_tests_live_migration.sh @@ -87,7 +87,13 @@ sudo chmod a+rwX /dev/hugepages export RUST_BACKTRACE=1 export RUSTFLAGS="$RUSTFLAGS" -time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} +TEST_THREADS_DEFAULT="$(($(nproc) / 4))" +if ! [[ "${PARALLEL_INTEGRATION_TESTS_NUM:-}" =~ ^[1-9][0-9]*$ ]]; then + PARALLEL_INTEGRATION_TESTS_NUM="${TEST_THREADS_DEFAULT}" +fi +echo "nproc:$(nproc), parallel_integration_tests:${PARALLEL_INTEGRATION_TESTS_NUM}" + +time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="${PARALLEL_INTEGRATION_TESTS_NUM}" "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index f12fa9aaaa..d4bf928470 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -189,7 +189,12 @@ ulimit -n 4096 export RUST_BACKTRACE=1 export RUSTFLAGS="$RUSTFLAGS" -time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "common_parallel::$test_filter" -- ${test_binary_args[*]} +TEST_THREADS_DEFAULT="$(($(nproc) / 4))" +if ! [[ "${PARALLEL_INTEGRATION_TESTS_NUM:-}" =~ ^[1-9][0-9]*$ ]]; then + PARALLEL_INTEGRATION_TESTS_NUM="${TEST_THREADS_DEFAULT}" +fi +echo "nproc:$(nproc), parallel_integration_tests:${PARALLEL_INTEGRATION_TESTS_NUM}" +time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="${PARALLEL_INTEGRATION_TESTS_NUM}" "common_parallel::$test_filter" -- ${test_binary_args[*]} RES=$? # Run some tests in sequence since the result could be affected by other tests @@ -203,20 +208,20 @@ fi if [ $RES -eq 0 ]; then cargo build --features "mshv,dbus_api" --all --release --target "$BUILD_TARGET" # integration tests now do not reply on build feature "dbus_api" - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "dbus_api::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="$TEST_THREADS_DEFAULT" "dbus_api::$test_filter" -- ${test_binary_args[*]} RES=$? fi # Run tests on fw_cfg if [ $RES -eq 0 ]; then cargo build --features "mshv,fw_cfg" --all --release --target "$BUILD_TARGET" - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "fw_cfg::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="$TEST_THREADS_DEFAULT" "fw_cfg::$test_filter" -- ${test_binary_args[*]} RES=$? fi if [ $RES -eq 0 ]; then cargo build --features "mshv,ivshmem" --all --release --target "$BUILD_TARGET" - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=$(($(nproc) / 4)) "ivshmem::$test_filter" -- ${test_binary_args[*]} + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="$TEST_THREADS_DEFAULT" "ivshmem::$test_filter" -- ${test_binary_args[*]} RES=$? fi From 75e6e694a241e4c26691afc6604f2fe0ce0f7cb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 00:06:18 +0000 Subject: [PATCH 1391/1893] 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] --- Cargo.lock | 136 ++++++++++++++++++++++-------------------- Cargo.toml | 8 +-- block/Cargo.toml | 2 +- devices/Cargo.toml | 2 +- fuzz/Cargo.lock | 133 ++++++++++++++++++++++------------------- fuzz/Cargo.toml | 2 +- hypervisor/Cargo.toml | 2 +- test_infra/Cargo.toml | 2 +- 8 files changed, 151 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c03c08766..a866c5ebd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,9 +225,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io", "async-lock", @@ -298,9 +298,9 @@ dependencies = [ [[package]] name = "bitfield-struct" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" +checksum = "3ca6739863c590881f038d033a146c51ddae239186a4327014839fd864f44ed5" dependencies = [ "proc-macro2", "quote", @@ -315,15 +315,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block" version = "0.1.0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "byteorder", "crc-any", "flate2", @@ -370,9 +370,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.59" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -399,9 +399,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", ] @@ -562,8 +562,8 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitfield-struct 0.12.1", - "bitflags 2.11.0", + "bitfield-struct 0.13.0", + "bitflags 2.11.1", "byteorder", "event_monitor", "hypervisor", @@ -682,7 +682,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", ] @@ -736,11 +736,11 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.3.4", ] [[package]] @@ -890,7 +890,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bafc7e33650ab9f05dcc16325f05d56b8d10393114e31a19a353b86fa60cfe7" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "log", "managed", @@ -915,10 +915,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -928,9 +926,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -998,7 +998,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arc-swap", - "bitfield-struct 0.12.1", + "bitfield-struct 0.13.0", "byteorder", "cfg-if", "concat-idents", @@ -1088,11 +1088,11 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" +checksum = "4d09b98f7eace8982db770e4408e7470b028ce513ac28fecdc6bf4c30fe92b62" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "libc", ] @@ -1180,9 +1180,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "once_cell", "wasm-bindgen", @@ -1205,7 +1205,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "333f77a20344a448f3f70664918135fddeb804e938f28a99d685bd92926e0b19" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "kvm-bindings", "libc", "vmm-sys-util", @@ -1236,15 +1236,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "libc", ] @@ -1478,18 +1478,18 @@ dependencies = [ [[package]] name = "openssl-src" -version = "300.5.5+3.5.5" +version = "300.6.0+3.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", @@ -1611,9 +1611,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "pnet" @@ -1728,9 +1728,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] @@ -1786,9 +1786,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", "getrandom 0.4.2", @@ -1797,9 +1797,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "range_map_vec" @@ -1824,7 +1824,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -1896,7 +1896,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -2069,7 +2069,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f84d13b3b8a0d4e91a2629911e951db1bb8671512f5c09d7d4ba34500ba68c8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "libssh2-sys", "parking_lot", @@ -2275,9 +2275,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -2328,7 +2328,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "731c2582dd43f4f174ab47b4c933a1a9bb872d9d1b7f54c5867e12dbc1491b75" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "log", "serde", @@ -2346,7 +2346,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee90657203a8644e9a0860a0db6a7887d8ef0c7bc09fc22dfa4ae75df65bac86" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "uuid", "vm-memory", @@ -2523,7 +2523,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "blocking", "cfg-if", @@ -2604,11 +2604,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -2617,14 +2617,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -2635,9 +2635,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2645,9 +2645,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -2658,9 +2658,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -2693,7 +2693,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -2763,6 +2763,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -2812,7 +2818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap", "log", "serde", diff --git a/Cargo.toml b/Cargo.toml index 92a52f81a3..69488e5192 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,21 +82,21 @@ serde_with = { version = "3.18.0", default-features = false } # other crates anyhow = "1.0.102" -bitflags = "2.11.0" +bitflags = "2.11.1" byteorder = "1.5.0" cfg-if = "1.0.4" -clap = "4.6.0" +clap = "4.6.1" dhat = "0.3.3" dirs = "6.0.0" env_logger = "0.11.10" epoll = "4.4.0" flume = "0.12.0" itertools = "0.14.0" -libc = "0.2.184" +libc = "0.2.185" log = "0.4.29" signal-hook = "0.4.4" thiserror = "2.0.18" -uuid = { version = "1.23.0" } +uuid = { version = "1.23.1" } wait-timeout = "0.2.1" zerocopy = { version = "0.8.48", default-features = false } diff --git a/block/Cargo.toml b/block/Cargo.toml index ab62c2308c..6183c73375 100644 --- a/block/Cargo.toml +++ b/block/Cargo.toml @@ -14,7 +14,7 @@ bitflags = { workspace = true } byteorder = { workspace = true } crc-any = "2.5.0" flate2 = "1.1" -io-uring = { version = "0.7.11", optional = true } +io-uring = { version = "0.7.12", optional = true } libc = { workspace = true } log = { workspace = true } remain = "0.2.15" diff --git a/devices/Cargo.toml b/devices/Cargo.toml index afdc3403bf..1045d961c9 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -9,7 +9,7 @@ version = "0.1.0" acpi_tables = { workspace = true } anyhow = { workspace = true } arch = { path = "../arch" } -bitfield-struct = { version = "0.12.0", optional = true } +bitfield-struct = { version = "0.13.0", optional = true } bitflags = { workspace = true } byteorder = { workspace = true } event_monitor = { path = "../event_monitor" } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 7906fd5cd4..51a21c27cd 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -132,15 +132,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block" version = "0.1.0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "byteorder", "crc-any", "flate2", @@ -173,9 +173,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.59" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -202,9 +202,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", ] @@ -343,7 +343,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.11.0", + "bitflags 2.11.1", "byteorder", "event_monitor", "hypervisor", @@ -393,7 +393,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", ] @@ -426,11 +426,11 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.3.4", ] [[package]] @@ -491,7 +491,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bafc7e33650ab9f05dcc16325f05d56b8d10393114e31a19a353b86fa60cfe7" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "log", "managed", @@ -509,19 +509,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - [[package]] name = "getrandom" version = "0.3.4" @@ -529,9 +516,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -629,6 +618,23 @@ dependencies = [ "serde_core", ] +[[package]] +name = "iommufd-bindings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7de3a04f6fd55f171a6682852f7aa360bb848a85e0c610513349e006b3c139" + +[[package]] +name = "iommufd-ioctls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eabd3414d9c4e716c9a198fbfac484625f088c075605372daf037edfe336e18" +dependencies = [ + "iommufd-bindings", + "thiserror", + "vmm-sys-util", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -662,9 +668,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "once_cell", "wasm-bindgen", @@ -687,7 +693,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "333f77a20344a448f3f70664918135fddeb804e938f28a99d685bd92926e0b19" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "kvm-bindings", "libc", "vmm-sys-util", @@ -718,9 +724,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libfuzzer-sys" @@ -918,9 +924,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "prettyplease" @@ -973,9 +979,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", "getrandom 0.4.2", @@ -984,9 +990,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rate_limiter" @@ -1261,9 +1267,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -1287,6 +1293,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4b1d98dff7f0d219278e406323e7eda4d426447bd203c7828189baf0d8c07b7" dependencies = [ "byteorder", + "iommufd-bindings", + "iommufd-ioctls", "kvm-bindings", "kvm-ioctls", "libc", @@ -1303,7 +1311,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "731c2582dd43f4f174ab47b4c933a1a9bb872d9d1b7f54c5867e12dbc1491b75" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "log", "serde", @@ -1321,7 +1329,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee90657203a8644e9a0860a0db6a7887d8ef0c7bc09fc22dfa4ae75df65bac86" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "uuid", "vm-memory", @@ -1444,7 +1452,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "cfg-if", "clap", @@ -1455,6 +1463,7 @@ dependencies = [ "gdbstub", "gdbstub_arch", "hypervisor", + "iommufd-ioctls", "landlock", "libc", "linux-loader", @@ -1498,19 +1507,13 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -1519,14 +1522,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -1537,9 +1540,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1547,9 +1550,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -1560,9 +1563,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -1595,7 +1598,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -1656,6 +1659,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -1705,7 +1714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap", "log", "serde", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 83dead0c9e..bb80658aef 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.4.0" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.184" +libc = "0.2.185" libfuzzer-sys = "0.4.12" linux-loader = { version = "0.13.2", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 19a9ca794d..af5181b294 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -16,7 +16,7 @@ tdx = [] [dependencies] anyhow = { workspace = true } arc-swap = "1.9.1" -bitfield-struct = "0.12.0" +bitfield-struct = "0.13.0" byteorder = { workspace = true } cfg-if = { workspace = true } concat-idents = "1.1.5" diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index b5854521ad..8d9b64870d 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -9,7 +9,7 @@ version = "0.1.0" dirs = { workspace = true } epoll = { workspace = true } libc = { workspace = true } -rand = "0.10.0" +rand = "0.10.1" serde_json = { workspace = true } ssh2 = { version = "0.9.5", features = ["vendored-openssl"] } thiserror = { workspace = true } From 6d01695e5c83bac0d3cb12ccdb786a9302bf4495 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Fri, 24 Apr 2026 10:27:08 +0200 Subject: [PATCH 1392/1893] 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 --- vmm/src/cpu.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index cb445cda15..303f5de997 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -1583,16 +1583,22 @@ impl CpuManager { /// Calls [`VcpuState::signal_thread`] and /// [`VcpuState::wait_until_signal_acknowledged`] for each vCPU. fn signal_vcpus(&mut self) -> Result<()> { - // Holding the lock for the whole operation is correct: - let vcpu_states = self.vcpu_states.lock().unwrap(); + let vcpu_count = self.vcpu_states.lock().unwrap().len(); // Splitting this into two loops reduced the time to pause many vCPUs // massively. Example: 254 vCPUs. >254ms -> ~4ms. - for state in vcpu_states.iter() { - state.signal_thread(); + // + // Do not hold `vcpu_states` across the wait phase. A vCPU can handle + // the kick in userspace while servicing MMIO on the ACPI CPU hotplug + // device, and that path also takes `vcpu_states`. Holding the mutex + // here while waiting would deadlock pause against that MMIO access. + for cpu_id in 0..vcpu_count { + let vcpu_states = self.vcpu_states.lock().unwrap(); + vcpu_states[cpu_id].signal_thread(); } - for state in vcpu_states.iter() { - state.wait_until_signal_acknowledged()?; + for cpu_id in 0..vcpu_count { + let vcpu_states = self.vcpu_states.lock().unwrap(); + vcpu_states[cpu_id].wait_until_signal_acknowledged()?; } Ok(()) From 326cd88074482875d69e21f4b7bcee23a3701bcc Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Apr 2026 17:39:09 -0700 Subject: [PATCH 1393/1893] 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 --- virtio-devices/src/balloon.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index f9db09bd33..37ebb056fc 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -23,7 +23,7 @@ use std::sync::{Arc, Barrier}; use anyhow::anyhow; use event_monitor::event; -use log::{error, info}; +use log::{error, info, warn}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -201,15 +201,30 @@ impl BalloonEpollHandler { let region = memory.find_region(range_base).ok_or(Error::GuestMemory( GuestMemoryError::InvalidGuestAddress(range_base), ))?; + + // No underflow possible because range_base was found in the region by `find_region`. + let offset = range_base.0 - region.start_addr().0; + let region_limit = region.len() - offset; + let len = std::cmp::min(range_len as u64, region_limit); + if len < range_len as u64 { + warn!( + "Clamping reported range at GPA 0x{:x} from {} to {} bytes \ + to fit inside its memory region", + range_base.0, range_len, len + ); + } + if len == 0 { + return Ok(()); + } + if let Some(f_off) = region.file_offset() { - let offset = range_base.0 - region.start_addr().0; // SAFETY: FFI call with valid arguments let res = unsafe { libc::fallocate64( f_off.file().as_raw_fd(), libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE, (offset + f_off.start()) as libc::off64_t, - range_len as libc::off64_t, + len as libc::off64_t, ) }; @@ -218,7 +233,7 @@ impl BalloonEpollHandler { } } - Self::advise_memory_range(memory, range_base, range_len, libc::MADV_DONTNEED) + Self::advise_memory_range(memory, range_base, len as usize, libc::MADV_DONTNEED) } fn release_memory_range_4k( From 842d02fdd9d4308fb7108a65cb5b10583f46d22e Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 24 Apr 2026 14:49:33 -0700 Subject: [PATCH 1394/1893] 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 Signed-off-by: Muminul Islam --- hypervisor/src/mshv/mod.rs | 21 +++++++++++++++++++-- hypervisor/src/mshv/x86_64/emulator.rs | 7 ++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index a61f2e44ef..32ef68bc55 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -720,11 +720,28 @@ impl cpu::Vcpu for MshvVcpu { let gva = info.guest_virtual_address; let gpa = info.guest_physical_address; - debug!("Exit ({msg_type:?}) GVA {gva:x} GPA {gpa:x}"); + // The GvaGpaValid flag indicates that the GPA in the intercept + // message corresponds to the GVA in the message. If that flag + // is set, and the GVA in the message matches the GVA in the decoded + // instruction, then the emulator can use the GPA provided by + // the hypervisor. Otherwise, the emulator must translate the GVA + // via a hypercall. + // SAFETY: accessing the bitfield union variant. + let gva_gpa_valid = + unsafe { info.memory_access_info.__bindgen_anon_1.gva_gpa_valid() != 0 }; + + debug!( + "Exit ({msg_type:?}) GVA {gva:x} GPA {gpa:x} \ + gva_gpa_valid={gva_gpa_valid}" + ); let mut context = MshvEmulatorContext { vcpu: self, - map: (gva, gpa), + map: if gva_gpa_valid { + (gva, gpa) + } else { + (u64::MAX, 0) + }, }; let old_state = context diff --git a/hypervisor/src/mshv/x86_64/emulator.rs b/hypervisor/src/mshv/x86_64/emulator.rs index d668dedde2..eb2be3d2f2 100644 --- a/hypervisor/src/mshv/x86_64/emulator.rs +++ b/hypervisor/src/mshv/x86_64/emulator.rs @@ -19,7 +19,12 @@ pub struct MshvEmulatorContext<'a> { } impl MshvEmulatorContext<'_> { - // Do the actual gva -> gpa translation + // Do the actual gva -> gpa translation. + // + // When the hypervisor sets GvaGpaValid in the intercept message, `map` + // caches the (gva, gpa) pair as a fast path that avoids a translate + // hypercall. When the flag is clear, `map` is set to a sentinel + // (u64::MAX, 0) so this shortcut never fires. #[allow(non_upper_case_globals)] fn translate(&self, gva: u64, flags: u32) -> Result { if self.map.0 == gva { From 6527fc22c0a2435f22cd08c630409c3b842ec73a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 26 Apr 2026 09:52:08 +0100 Subject: [PATCH 1395/1893] 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 --- vhost_user_block/src/lib.rs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index cf009f05fc..12b45f5ee7 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -9,8 +9,8 @@ // SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause) use std::fs::{File, OpenOptions}; -use std::io::{Read, Seek, SeekFrom, Write}; -use std::ops::{Deref, DerefMut}; +use std::io::{Seek, SeekFrom}; +use std::ops::Deref; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{FromRawFd, IntoRawFd}; use std::path::PathBuf; @@ -19,7 +19,7 @@ use std::sync::{Arc, Mutex, RwLock, RwLockWriteGuard}; use std::time::Instant; use std::{convert, io, process, result}; -use block::qcow::{self, ImageType, QcowFile}; +use block::qcow::RawFile; use block::{Request, RequestType, VirtioBlockConfig, build_serial}; use libc::EFD_NONBLOCK; use log::{debug, error, info, warn}; @@ -48,9 +48,6 @@ const BLK_SIZE: u32 = 512; // and the overhead of the emulation layer. const POLL_QUEUE_US: u128 = 50; -trait DiskFile: Read + Seek + Write + Send {} -impl DiskFile for D {} - type Result = std::result::Result; type VhostUserBackendResult = std::result::Result; @@ -89,7 +86,7 @@ impl convert::From for io::Error { } struct VhostUserBlkThread { - disk_image: Arc>, + disk_image: Arc>, serial: Vec, disk_nsectors: u64, event_idx: bool, @@ -100,7 +97,7 @@ struct VhostUserBlkThread { impl VhostUserBlkThread { fn new( - disk_image: Arc>, + disk_image: Arc>, serial: Vec, disk_nsectors: u64, writeback: Arc, @@ -133,7 +130,7 @@ impl VhostUserBlkThread { debug!("element is a valid request"); request.writeback = self.writeback.load(Ordering::Acquire); let (status, len) = match request.execute( - &mut self.disk_image.lock().unwrap().deref_mut(), + &mut *self.disk_image.lock().unwrap(), self.disk_nsectors, desc_chain.memory(), &self.serial, @@ -217,16 +214,10 @@ impl VhostUserBlkBackend { options.custom_flags(libc::O_DIRECT); } let image: File = options.open(image_path).unwrap(); - let mut raw_img: qcow::RawFile = qcow::RawFile::new(image, direct); + let raw_img = RawFile::new(image, direct); let serial = build_serial(&PathBuf::from(&image_path)); - let image_type = qcow::detect_image_type(&mut raw_img).unwrap(); - let image = match image_type { - ImageType::Raw => Arc::new(Mutex::new(raw_img)) as Arc>, - ImageType::Qcow2 => Arc::new(Mutex::new( - QcowFile::from_with_nesting_depth(raw_img, 0, true).unwrap(), - )) as Arc>, - }; + let image = Arc::new(Mutex::new(raw_img)); let nsectors = (image.lock().unwrap().seek(SeekFrom::End(0)).unwrap()) / SECTOR_SIZE; let config = VirtioBlockConfig { From 5909ce85edfc35ae61bd090d778bed9b718bf20f Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 25 Apr 2026 18:39:11 +0100 Subject: [PATCH 1396/1893] 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 --- scripts/run_integration_tests_aarch64.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index fb9d5daa57..128ec31c5a 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -148,8 +148,7 @@ update_workloads() { popd || exit # Download Cloud Hypervisor binary from its last stable release - LAST_RELEASE_VERSION="v39.0" - CH_RELEASE_URL="https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/$LAST_RELEASE_VERSION/cloud-hypervisor-static-aarch64" + CH_RELEASE_URL="https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/${migratable_version}/cloud-hypervisor-static-aarch64" CH_RELEASE_NAME="cloud-hypervisor-static-aarch64" pushd "$WORKLOADS_DIR" || exit # Repeat a few times to workaround a random wget failure @@ -209,12 +208,23 @@ update_workloads() { process_common_args "$@" +migratable_version=v39.0 test_features="" if [ "$hypervisor" = "mshv" ]; then test_features="--features mshv" fi +# if migratable version is set to override the default +if [ -n "${MIGRATABLE_VERSION}" ]; then + # validate the version if matched with vxx.0 + if ! [[ "${MIGRATABLE_VERSION}" =~ ^v[0-9]{2,}\.[0-9]$ ]]; then + echo "MIGRATABLE_VERSION should be in format vxx.0, e.g. v47.0" + exit 1 + fi + migratable_version=${MIGRATABLE_VERSION} +fi + # lock the workloads folder to avoid parallel updating by different containers ( echo "try to lock $WORKLOADS_DIR folder and update" From c118606d645f210d3eded192c6eb73d88c8696d6 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 25 Apr 2026 18:41:24 +0100 Subject: [PATCH 1397/1893] 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 --- .github/workflows/integration-x86-64.yaml | 6 +- scripts/dev_cli.sh | 31 +---- .../run_integration_tests_live_migration.sh | 108 ------------------ scripts/run_integration_tests_x86_64.sh | 30 +++++ 4 files changed, 32 insertions(+), 143 deletions(-) delete mode 100755 scripts/run_integration_tests_live_migration.sh diff --git a/.github/workflows/integration-x86-64.yaml b/.github/workflows/integration-x86-64.yaml index 213442d98d..38dbcb06f2 100644 --- a/.github/workflows/integration-x86-64.yaml +++ b/.github/workflows/integration-x86-64.yaml @@ -47,12 +47,8 @@ jobs: run: sudo modprobe openvswitch - name: Run integration tests if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }} - timeout-minutes: 60 + timeout-minutes: 80 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" diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index a5d3947ce1..21d6362805 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -259,7 +259,6 @@ cmd_help() { echo " --integration Run the integration tests." echo " --integration-vfio Run the VFIO integration tests." echo " --integration-windows Run the Windows guest integration tests." - echo " --integration-live-migration Run the live-migration integration tests." echo " --integration-rate-limiter Run the rate-limiter integration tests." echo " --integration-cvm Run the Confidential VM integration tests." echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu" @@ -395,7 +394,6 @@ cmd_tests() { integration=false integration_vfio=false integration_windows=false - integration_live_migration=false integration_rate_limiter=false integration_cvm=false metrics=false @@ -414,7 +412,6 @@ cmd_tests() { "--integration") { integration=true; } ;; "--integration-vfio") { integration_vfio=true; } ;; "--integration-windows") { integration_windows=true; } ;; - "--integration-live-migration") { integration_live_migration=true; } ;; "--integration-rate-limiter") { integration_rate_limiter=true; } ;; "--integration-cvm") { integration_cvm=true; } ;; "--metrics") { metrics=true; } ;; @@ -515,6 +512,7 @@ cmd_tests() { --env PARALLEL_INTEGRATION_TESTS_NUM="${PARALLEL_INTEGRATION_TESTS_NUM:-}" \ --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ + --env MIGRATABLE_VERSION="$MIGRATABLE_VERSION" \ "$CTR_IMAGE" \ dbus-run-session ./scripts/run_integration_tests_"$(uname -m)".sh "$@" || fix_dir_perms $? || exit $? fi @@ -595,33 +593,6 @@ cmd_tests() { ./scripts/run_integration_tests_windows_"$(uname -m)".sh "$@" || fix_dir_perms $? || exit $? fi - if [ "$integration_live_migration" = true ]; then - say "Running 'live migration' integration tests for $target..." - run_container "$DOCKER_RUNTIME" run \ - --name "$CLH_CTR_NAME" \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ - --privileged \ - --security-opt seccomp=unconfined \ - --ipc=host \ - --net="$CTR_CLH_NET" \ - --mount type=tmpfs,destination=/tmp \ - --volume /dev:/dev \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+$exported_volumes} \ - --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ - --env USER="root" \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ - --env PARALLEL_INTEGRATION_TESTS_NUM="${PARALLEL_INTEGRATION_TESTS_NUM:-}" \ - --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ - --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ - --env MIGRATABLE_VERSION="$MIGRATABLE_VERSION" \ - "$CTR_IMAGE" \ - ./scripts/run_integration_tests_live_migration.sh "$@" || fix_dir_perms $? || exit $? - fi - if [ "$integration_rate_limiter" = true ]; then say "Running 'rate limiter' integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ diff --git a/scripts/run_integration_tests_live_migration.sh b/scripts/run_integration_tests_live_migration.sh deleted file mode 100755 index 5198e2c896..0000000000 --- a/scripts/run_integration_tests_live_migration.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env bash -# shellcheck disable=SC2048,SC2086 -set -x - -# shellcheck source=/dev/null -source "$HOME"/.cargo/env -source "$(dirname "$0")"/test-util.sh - -WORKLOADS_DIR="$HOME/workloads" -mkdir -p "$WORKLOADS_DIR" - -process_common_args "$@" - -migratable_version=v39.0 -# For now these values are default for kvm -test_features="" - -if [ "$hypervisor" = "mshv" ]; then - test_features="--features mshv" -fi - -# if migratable version is set to override the default -if [ -n "${MIGRATABLE_VERSION}" ]; then - # validate the version if matched with vxx.0 - if ! [[ "${MIGRATABLE_VERSION}" =~ ^v[0-9]{2,}\.[0-9]$ ]]; then - echo "MIGRATABLE_VERSION should be in format vxx.0, e.g. v47.0" - exit 1 - fi - migratable_version=${MIGRATABLE_VERSION} -fi -cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR" - -FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2" -FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME" -FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME" -if [ ! -f "$FOCAL_OS_IMAGE" ]; then - pushd "$WORKLOADS_DIR" || exit - time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1 - popd || exit -fi - -FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.raw" -FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME" -if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then - pushd "$WORKLOADS_DIR" || exit - time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1 - popd || exit -fi - -pushd "$WORKLOADS_DIR" || exit -if ! grep focal sha1sums-x86_64-common | sha1sum --check; then - echo "sha1sum validation of images failed, remove invalid images to fix the issue." - exit 1 -fi -popd || exit - -# Download Cloud Hypervisor binary from its last stable release -CH_RELEASE_URL="https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/${migratable_version}/cloud-hypervisor-static" -CH_RELEASE_NAME="cloud-hypervisor-static" -pushd "$WORKLOADS_DIR" || exit -time wget --quiet $CH_RELEASE_URL -O "$CH_RELEASE_NAME" || exit 1 -chmod +x $CH_RELEASE_NAME -popd || exit - -# Build custom kernel based on virtio-pmem and virtio-fs upstream patches -VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux-x86_64" -if [ ! -f "$VMLINUX_IMAGE" ]; then - # Prepare linux image (build from source or download pre-built) - prepare_linux -fi - -CFLAGS="" -if [[ "${BUILD_TARGET}" == "x86_64-unknown-linux-musl" ]]; then - # shellcheck disable=SC2034 - CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/" -fi - -cargo build --features mshv --all --release --target "$BUILD_TARGET" - -# Test ovs-dpdk relies on hugepages -HUGEPAGESIZE=$(grep Hugepagesize /proc/meminfo | awk '{print $2}') -PAGE_NUM=$((12288 * 1024 / HUGEPAGESIZE)) -echo "$PAGE_NUM" | sudo tee /proc/sys/vm/nr_hugepages -sudo chmod a+rwX /dev/hugepages - -# Common configuration for every test run -export RUST_BACKTRACE=1 -export RUSTFLAGS="$RUSTFLAGS" - -TEST_THREADS_DEFAULT="$(($(nproc) / 4))" -if ! [[ "${PARALLEL_INTEGRATION_TESTS_NUM:-}" =~ ^[1-9][0-9]*$ ]]; then - PARALLEL_INTEGRATION_TESTS_NUM="${TEST_THREADS_DEFAULT}" -fi -echo "nproc:$(nproc), parallel_integration_tests:${PARALLEL_INTEGRATION_TESTS_NUM}" - -time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="${PARALLEL_INTEGRATION_TESTS_NUM}" "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} - -RES=$? - -# Run some tests in sequence since the result could be affected by other tests -# running in parallel. -if [ $RES -eq 0 ]; then - export RUST_BACKTRACE=1 - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} - RES=$? -fi - -exit $RES diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index d4bf928470..eab2508099 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -11,6 +11,7 @@ mkdir -p "$WORKLOADS_DIR" process_common_args "$@" +migratable_version=v39.0 # For now these values are default for kvm test_features="" @@ -18,6 +19,16 @@ if [ "$hypervisor" = "mshv" ]; then test_features="--features mshv" fi +# if migratable version is set to override the default +if [ -n "${MIGRATABLE_VERSION}" ]; then + # validate the version if matched with vxx.0 + if ! [[ "${MIGRATABLE_VERSION}" =~ ^v[0-9]{2,}\.[0-9]$ ]]; then + echo "MIGRATABLE_VERSION should be in format vxx.0, e.g. v47.0" + exit 1 + fi + migratable_version=${MIGRATABLE_VERSION} +fi + cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR" if [ ! -f "$WORKLOADS_DIR/hypervisor-fw" ]; then @@ -111,6 +122,14 @@ if ! sha1sum sha1sums-x86_64 sha1sums-x86_64-common --check; then fi popd || exit +# Download Cloud Hypervisor binary from its last stable release for live-upgrade tests +CH_RELEASE_URL="https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/${migratable_version}/cloud-hypervisor-static" +CH_RELEASE_NAME="cloud-hypervisor-static" +pushd "$WORKLOADS_DIR" || exit +time wget --quiet $CH_RELEASE_URL -O "$CH_RELEASE_NAME" || exit 1 +chmod +x $CH_RELEASE_NAME +popd || exit + # Build custom kernel based on virtio-pmem and virtio-fs upstream patches VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux-x86_64" if [ ! -f "$VMLINUX_IMAGE" ]; then @@ -204,6 +223,17 @@ if [ $RES -eq 0 ]; then RES=$? fi +# Run all live-migration test cases +if [ $RES -eq 0 ]; then + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="${PARALLEL_INTEGRATION_TESTS_NUM}" "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} + RES=$? +fi + +if [ $RES -eq 0 ]; then + time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} + RES=$? +fi + # Run tests on dbus_api if [ $RES -eq 0 ]; then cargo build --features "mshv,dbus_api" --all --release --target "$BUILD_TARGET" From d9395b9773839e44654b2b2f4b1f39fcca96baf7 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 25 Apr 2026 18:43:25 +0100 Subject: [PATCH 1398/1893] 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 --- cloud-hypervisor/tests/common/utils.rs | 137 ++++++++++++++++++++++- cloud-hypervisor/tests/integration.rs | 145 +------------------------ 2 files changed, 139 insertions(+), 143 deletions(-) diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs index 1821575d49..a1bbb743e5 100644 --- a/cloud-hypervisor/tests/common/utils.rs +++ b/cloud-hypervisor/tests/common/utils.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use std::fs::{File, OpenOptions}; use std::io::{BufRead, Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; -use std::process::{Child, Command}; +use std::process::{Child, Command, Stdio}; use std::string::String; use std::sync::mpsc; use std::sync::mpsc::Receiver; @@ -14,6 +14,7 @@ use std::{cmp, fs, io, thread}; use test_infra::*; use vmm_sys_util::tempdir::TempDir; +use wait_timeout::ChildExt; const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72; // 10MB is our maximum accepted overhead. @@ -1059,3 +1060,137 @@ pub(crate) fn bdf_from_hotplug_response( (segment_id, bus_id, device_id, function_id) } + +pub(crate) fn start_live_migration( + migration_socket: &str, + src_api_socket: &str, + dest_api_socket: &str, + local: bool, +) -> bool { + // Start to receive migration from the destination VM + let mut receive_migration = Command::new(clh_command("ch-remote")) + .args([ + &format!("--api-socket={dest_api_socket}"), + "receive-migration", + &format! {"unix:{migration_socket}"}, + ]) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + // Give it '1s' to make sure the 'migration_socket' file is properly created + thread::sleep(std::time::Duration::new(1, 0)); + // Start to send migration from the source VM + + let args = [ + format!("--api-socket={}", &src_api_socket), + "send-migration".to_string(), + format!( + "destination_url=unix:{migration_socket},local={}", + if local { "on" } else { "off" } + ), + ] + .to_vec(); + + let mut send_migration = Command::new(clh_command("ch-remote")) + .args(&args) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + // The 'send-migration' command should be executed successfully within the given timeout + let send_success = if let Some(status) = send_migration + .wait_timeout(std::time::Duration::from_secs(30)) + .unwrap() + { + status.success() + } else { + false + }; + + if !send_success { + let _ = send_migration.kill(); + let output = send_migration.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'send_migration' output ==== \ + \n\n---stdout---\n{}\n\n---stderr---\n{} \ + \n\n==== End 'send_migration' output ====\n\n", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + + // The 'receive-migration' command should be executed successfully within the given timeout + let receive_success = if let Some(status) = receive_migration + .wait_timeout(std::time::Duration::from_secs(30)) + .unwrap() + { + status.success() + } else { + false + }; + + if !receive_success { + let _ = receive_migration.kill(); + let output = receive_migration.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'receive_migration' output ==== \ + \n\n---stdout---\n{}\n\n---stderr---\n{} \ + \n\n==== End 'receive_migration' output ====\n\n", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + + send_success && receive_success +} + +pub(crate) fn print_and_panic( + src_vm: Child, + dest_vm: Child, + ovs_vm: Option, + message: &str, +) -> ! { + let mut src_vm = src_vm; + let mut dest_vm = dest_vm; + + let _ = src_vm.kill(); + let src_output = src_vm.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'source_vm' stdout ====\n\n{}\n\n==== End 'source_vm' stdout ====", + String::from_utf8_lossy(&src_output.stdout) + ); + eprintln!( + "\n\n==== Start 'source_vm' stderr ====\n\n{}\n\n==== End 'source_vm' stderr ====", + String::from_utf8_lossy(&src_output.stderr) + ); + let _ = dest_vm.kill(); + let dest_output = dest_vm.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'destination_vm' stdout ====\n\n{}\n\n==== End 'destination_vm' stdout ====", + String::from_utf8_lossy(&dest_output.stdout) + ); + eprintln!( + "\n\n==== Start 'destination_vm' stderr ====\n\n{}\n\n==== End 'destination_vm' stderr ====", + String::from_utf8_lossy(&dest_output.stderr) + ); + + if let Some(ovs_vm) = ovs_vm { + let mut ovs_vm = ovs_vm; + let _ = ovs_vm.kill(); + let ovs_output = ovs_vm.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'ovs_vm' stdout ====\n\n{}\n\n==== End 'ovs_vm' stdout ====", + String::from_utf8_lossy(&ovs_output.stdout) + ); + eprintln!( + "\n\n==== Start 'ovs_vm' stderr ====\n\n{}\n\n==== End 'ovs_vm' stderr ====", + String::from_utf8_lossy(&ovs_output.stderr) + ); + + cleanup_ovs_dpdk(); + } + + panic!("Test failed: {message}") +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index d0a7b49c88..e860af7d48 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -6351,19 +6351,14 @@ mod ivshmem { ); assert!( - live_migration::start_live_migration( - &migration_socket, - &src_api_socket, - &dest_api_socket, - local - ), + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), "Unsuccessful command: 'send-migration' or 'receive-migration'." ); }); // Check and report any errors occurred during the live-migration if r.is_err() { - live_migration::print_and_panic( + print_and_panic( src_child, dest_child, None, @@ -6374,7 +6369,7 @@ mod ivshmem { // Check the source vm has been terminated successful (give it '3s' to settle) thread::sleep(std::time::Duration::new(3, 0)); if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - live_migration::print_and_panic( + print_and_panic( src_child, dest_child, None, @@ -9170,140 +9165,6 @@ mod live_migration { use crate::*; - pub fn start_live_migration( - migration_socket: &str, - src_api_socket: &str, - dest_api_socket: &str, - local: bool, - ) -> bool { - // Start to receive migration from the destination VM - let mut receive_migration = Command::new(clh_command("ch-remote")) - .args([ - &format!("--api-socket={dest_api_socket}"), - "receive-migration", - &format! {"unix:{migration_socket}"}, - ]) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); - // Give it '1s' to make sure the 'migration_socket' file is properly created - thread::sleep(std::time::Duration::new(1, 0)); - // Start to send migration from the source VM - - let args = [ - format!("--api-socket={}", &src_api_socket), - "send-migration".to_string(), - format!( - "destination_url=unix:{migration_socket},local={}", - if local { "on" } else { "off" } - ), - ] - .to_vec(); - - let mut send_migration = Command::new(clh_command("ch-remote")) - .args(&args) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); - - // The 'send-migration' command should be executed successfully within the given timeout - let send_success = if let Some(status) = send_migration - .wait_timeout(std::time::Duration::from_secs(30)) - .unwrap() - { - status.success() - } else { - false - }; - - if !send_success { - let _ = send_migration.kill(); - let output = send_migration.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'send_migration' output ==== \ - \n\n---stdout---\n{}\n\n---stderr---\n{} \ - \n\n==== End 'send_migration' output ====\n\n", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - } - - // The 'receive-migration' command should be executed successfully within the given timeout - let receive_success = if let Some(status) = receive_migration - .wait_timeout(std::time::Duration::from_secs(30)) - .unwrap() - { - status.success() - } else { - false - }; - - if !receive_success { - let _ = receive_migration.kill(); - let output = receive_migration.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'receive_migration' output ==== \ - \n\n---stdout---\n{}\n\n---stderr---\n{} \ - \n\n==== End 'receive_migration' output ====\n\n", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - } - - send_success && receive_success - } - - pub fn print_and_panic( - src_vm: Child, - dest_vm: Child, - ovs_vm: Option, - message: &str, - ) -> ! { - let mut src_vm = src_vm; - let mut dest_vm = dest_vm; - - let _ = src_vm.kill(); - let src_output = src_vm.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'source_vm' stdout ====\n\n{}\n\n==== End 'source_vm' stdout ====", - String::from_utf8_lossy(&src_output.stdout) - ); - eprintln!( - "\n\n==== Start 'source_vm' stderr ====\n\n{}\n\n==== End 'source_vm' stderr ====", - String::from_utf8_lossy(&src_output.stderr) - ); - let _ = dest_vm.kill(); - let dest_output = dest_vm.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'destination_vm' stdout ====\n\n{}\n\n==== End 'destination_vm' stdout ====", - String::from_utf8_lossy(&dest_output.stdout) - ); - eprintln!( - "\n\n==== Start 'destination_vm' stderr ====\n\n{}\n\n==== End 'destination_vm' stderr ====", - String::from_utf8_lossy(&dest_output.stderr) - ); - - if let Some(ovs_vm) = ovs_vm { - let mut ovs_vm = ovs_vm; - let _ = ovs_vm.kill(); - let ovs_output = ovs_vm.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'ovs_vm' stdout ====\n\n{}\n\n==== End 'ovs_vm' stdout ====", - String::from_utf8_lossy(&ovs_output.stdout) - ); - eprintln!( - "\n\n==== Start 'ovs_vm' stderr ====\n\n{}\n\n==== End 'ovs_vm' stderr ====", - String::from_utf8_lossy(&ovs_output.stderr) - ); - - cleanup_ovs_dpdk(); - } - - panic!("Test failed: {message}") - } - // This test exercises the local live-migration between two Cloud Hypervisor VMs on the // same host. It ensures the following behaviors: // 1. The source VM is up and functional (including various virtio-devices are working properly); From 2fb08c4b5dddfa3a8a35ce5e46549cd8f5b1ae8c Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 25 Apr 2026 18:50:08 +0100 Subject: [PATCH 1399/1893] 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 --- cloud-hypervisor/tests/common/utils.rs | 7 +- cloud-hypervisor/tests/integration.rs | 7698 +++++++++++----------- scripts/run_integration_tests_aarch64.sh | 15 - scripts/run_integration_tests_x86_64.sh | 11 - 4 files changed, 3841 insertions(+), 3890 deletions(-) diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs index a1bbb743e5..45a803dced 100644 --- a/cloud-hypervisor/tests/common/utils.rs +++ b/cloud-hypervisor/tests/common/utils.rs @@ -5,7 +5,9 @@ use std::collections::HashMap; use std::fs::{File, OpenOptions}; use std::io::{BufRead, Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; -use std::process::{Child, Command, Stdio}; +#[cfg(not(feature = "mshv"))] +use std::process::Stdio; +use std::process::{Child, Command}; use std::string::String; use std::sync::mpsc; use std::sync::mpsc::Receiver; @@ -14,6 +16,7 @@ use std::{cmp, fs, io, thread}; use test_infra::*; use vmm_sys_util::tempdir::TempDir; +#[cfg(not(feature = "mshv"))] use wait_timeout::ChildExt; const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72; @@ -1061,6 +1064,7 @@ pub(crate) fn bdf_from_hotplug_response( (segment_id, bus_id, device_id, function_id) } +#[cfg(not(feature = "mshv"))] pub(crate) fn start_live_migration( migration_socket: &str, src_api_socket: &str, @@ -1146,6 +1150,7 @@ pub(crate) fn start_live_migration( send_success && receive_success } +#[cfg(not(feature = "mshv"))] pub(crate) fn print_and_panic( src_vm: Child, dest_vm: Child, diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index e860af7d48..bafcd7f43d 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -10,6 +10,7 @@ #![allow(dead_code)] use std::fs::{File, OpenOptions, copy}; use std::io::{Read, Seek, Write}; +#[cfg(not(feature = "mshv"))] use std::net::TcpListener; use std::os::unix::io::AsRawFd; use std::path::PathBuf; @@ -38,9 +39,13 @@ macro_rules! basic_regular_guest { mod common_parallel { use std::io::{self, SeekFrom}; + #[cfg(not(feature = "mshv"))] + use std::num::NonZeroU32; use std::process::Command; use test_infra::GuestFactory; + #[cfg(not(feature = "mshv"))] + use vmm::api::TimeoutStrategy; use crate::*; @@ -6093,135 +6098,17 @@ mod common_parallel { handle_child_output(r, &output); } -} - -mod dbus_api { - use crate::*; - - // Start cloud-hypervisor with no VM parameters, running both the HTTP - // and DBus APIs. Alternate calls to the external APIs (HTTP and DBus) - // to create a VM, boot it, and verify that it can be shut down and then - // booted again. - #[test] - fn test_api_dbus_and_http_interleaved() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let dbus_api = TargetApi::new_dbus_api(&guest.tmp_dir); - let http_api = TargetApi::new_http_api(&guest.tmp_dir); - - let mut child = GuestCommand::new(&guest) - .args(dbus_api.guest_args()) - .args(http_api.guest_args()) - .capture_output() - .spawn() - .unwrap(); - - thread::sleep(std::time::Duration::new(1, 0)); - - // Verify API servers are running - assert!(dbus_api.remote_command("ping", None)); - assert!(http_api.remote_command("ping", None)); - - // Create the VM first - let request_body = guest.api_create_body(); - - let temp_config_path = guest.tmp_dir.as_path().join("config"); - std::fs::write(&temp_config_path, request_body).unwrap(); - let create_config = temp_config_path.as_os_str().to_str().unwrap(); - - let r = std::panic::catch_unwind(|| { - // Create the VM - assert!(dbus_api.remote_command("create", Some(create_config),)); - - // Then boot it - assert!(http_api.remote_command("boot", None)); - guest.wait_vm_boot().unwrap(); - - // Check that the VM booted as expected - guest.validate_cpu_count(None); - guest.validate_memory(None); - - // Sync and shutdown without powering off to prevent filesystem - // corruption. - guest.ssh_command("sync").unwrap(); - guest.ssh_command("sudo shutdown -H now").unwrap(); - - // Wait for the guest to be fully shutdown - assert!(guest.wait_for_ssh_unresponsive(Duration::from_secs(20))); - - // Then shutdown the VM - assert!(dbus_api.remote_command("shutdown", None)); - - // Then boot it again - assert!(http_api.remote_command("boot", None)); - guest.wait_vm_boot().unwrap(); - - // Check that the VM booted as expected - guest.validate_cpu_count(None); - guest.validate_memory(None); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - } - - #[test] - fn test_api_dbus_create_boot() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); - - let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); - _test_api_create_boot(&target_api, &guest); - } - - #[test] - fn test_api_dbus_shutdown() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); - - let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); - _test_api_shutdown(&target_api, &guest); - } - - #[test] - fn test_api_dbus_delete() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); - - let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); - _test_api_delete(&target_api, &guest); - } - - #[test] - fn test_api_dbus_pause_resume() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = GuestFactory::new_regular_guest_factory() - .create_guest(Box::new(disk_config)) - .with_cpu(4); - - let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); - _test_api_pause_resume(&target_api, &guest); - } -} -mod ivshmem { + // This test exercises the local live-migration between two Cloud Hypervisor VMs on the + // same host. It ensures the following behaviors: + // 1. The source VM is up and functional (including various virtio-devices are working properly); + // 2. The 'send-migration' and 'receive-migration' command finished successfully; + // 3. The source VM terminated gracefully after live migration; + // 4. The destination VM is functional (including various virtio-devices are working properly) after + // live migration; + // Note: This test does not use vsock as we can't create two identical vsock on the same host. #[cfg(not(feature = "mshv"))] - use std::fs::remove_dir_all; - use std::process::Command; - - use test_infra::{Guest, GuestCommand, UbuntuDiskConfig, handle_child_output, kill_child}; - - use crate::*; - - fn _test_live_migration_ivshmem(local: bool) { + fn _test_live_migration(upgrade_test: bool, local: bool) { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); @@ -6233,9 +6120,9 @@ mod ivshmem { ); let memory_param: &[&str] = if local { - &["--memory", "size=4G,shared=on"] + &["--memory", "size=1500M,shared=on"] } else { - &["--memory", "size=4G"] + &["--memory", "size=1500M"] }; let boot_vcpus = 2; @@ -6249,29 +6136,12 @@ mod ivshmem { .expect("Expect creating disk image to succeed"); let pmem_path = String::from("/dev/pmem0"); - let ivshmem_file_path = String::from( - guest - .tmp_dir - .as_path() - .join("ivshmem.data") - .to_str() - .unwrap(), - ); - let file_size = "1M"; - - // Create a file to be used as the shared memory - Command::new("dd") - .args([ - "if=/dev/zero", - format!("of={ivshmem_file_path}").as_str(), - format!("bs={file_size}").as_str(), - "count=1", - ]) - .status() - .unwrap(); - // Start the source VM - let src_vm_path = clh_command("cloud-hypervisor"); + let src_vm_path = if upgrade_test { + cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") + }; let src_api_socket = temp_api_path(&guest.tmp_dir); let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); src_vm_cmd @@ -6288,10 +6158,6 @@ mod ivshmem { .args([ "--pmem", format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), - ]) - .args([ - "--ivshmem", - format!("path={ivshmem_file_path},size={file_size}").as_str(), ]); let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); @@ -6310,10 +6176,13 @@ mod ivshmem { // Make sure the source VM is functional // Check the number of vCPUs assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); + // Check the guest virtio-devices, e.g. block, rng, console, and net guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + // x86_64: Following what's done in the `test_snapshot_restore`, we need // to make sure that removing and adding back the virtio-net device does // not break the live-migration support for virtio-pci. @@ -6324,7 +6193,9 @@ mod ivshmem { "remove-device", Some(net_id), )); - thread::sleep(Duration::new(10, 0)); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); // Plug the virtio-net device again assert!(remote_command( @@ -6332,14 +6203,9 @@ mod ivshmem { "add-net", Some(net_params.as_str()), )); - thread::sleep(Duration::new(10, 0)); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); } - // Check ivshmem device in src guest. - _test_ivshmem(&guest, &ivshmem_file_path, file_size); - // Allow some normal time to elapse to check we don't get spurious reboots - thread::sleep(std::time::Duration::new(40, 0)); - // Start the live-migration let migration_socket = String::from( guest @@ -6381,12 +6247,9 @@ mod ivshmem { let r = std::panic::catch_unwind(|| { // Perform same checks to validate VM has been properly migrated assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); - - // Check ivshmem device - _test_ivshmem(&guest, &ivshmem_file_path, file_size); }); // Clean-up the destination VM and make sure it terminated correctly @@ -6401,1244 +6264,1271 @@ mod ivshmem { handle_child_output(r, &dest_output); } - #[test] - fn test_ivshmem() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + #[cfg(not(feature = "mshv"))] + fn _test_live_migration_watchdog(upgrade_test: bool, local: bool) { + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - let kernel_path = direct_kernel_boot_path(); - - let ivshmem_file_path = String::from( - guest - .tmp_dir - .as_path() - .join("ivshmem.data") - .to_str() - .unwrap(), + let console_text = String::from("On a branch floating down river a cricket, singing."); + let net_id = "net123"; + let net_params = format!( + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 ); - let file_size = "1M"; - // Create a file to be used as the shared memory - Command::new("dd") - .args([ - "if=/dev/zero", - format!("of={ivshmem_file_path}").as_str(), - format!("bs={file_size}").as_str(), - "count=1", - ]) - .status() - .unwrap(); + let memory_param: &[&str] = if local { + &["--memory", "size=1500M,shared=on"] + } else { + &["--memory", "size=1500M"] + }; - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=2"]) - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .args([ - "--ivshmem", - format!("path={ivshmem_file_path},size={file_size}").as_str(), - ]) - .args(["--api-socket", &api_socket]) - .capture_output() - .spawn() - .unwrap(); + let boot_vcpus = 2; + let max_vcpus = 4; - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - _test_ivshmem(&guest, &ivshmem_file_path, file_size); - }); - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - } - - #[test] - #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_ivshmem() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - - let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); - - let ivshmem_file_path = String::from( - guest - .tmp_dir - .as_path() - .join("ivshmem.data") - .to_str() - .unwrap(), - ); - let file_size = "1M"; + let pmem_temp_file = TempFile::new().unwrap(); + pmem_temp_file.as_file().set_len(128 << 20).unwrap(); + std::process::Command::new("mkfs.ext4") + .arg(pmem_temp_file.as_path()) + .output() + .expect("Expect creating disk image to succeed"); + let pmem_path = String::from("/dev/pmem0"); - // Create a file to be used as the shared memory - Command::new("dd") + // Start the source VM + let src_vm_path = if upgrade_test { + cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") + }; + let src_api_socket = temp_api_path(&guest.tmp_dir); + let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); + src_vm_cmd .args([ - "if=/dev/zero", - format!("of={ivshmem_file_path}").as_str(), - format!("bs={file_size}").as_str(), - "count=1", + "--cpus", + format!("boot={boot_vcpus},max={max_vcpus}").as_str(), ]) - .status() - .unwrap(); - - let socket = temp_vsock_path(&guest.tmp_dir); - let event_path = temp_event_monitor_path(&guest.tmp_dir); - - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_source]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .args(["--cpus", "boot=2"]) - .args(["--memory", "size=1G"]) + .args(memory_param) .args(["--kernel", kernel_path.to_str().unwrap()]) - .default_disks() - .default_net() - .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--net", net_params.as_str()]) + .args(["--api-socket", &src_api_socket]) .args([ - "--ivshmem", - format!("path={ivshmem_file_path},size={file_size}").as_str(), + "--pmem", + format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), ]) + .args(["--watchdog"]); + let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); + + // Start the destination VM + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) .capture_output() .spawn() .unwrap(); - let console_text = String::from("On a branch floating down river a cricket, singing."); - // Create the snapshot directory - let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); - let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); + // Make sure the source VM is functional // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + // Check the guest RAM + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); + // Check the guest virtio-devices, e.g. block, rng, console, and net + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + // x86_64: Following what's done in the `test_snapshot_restore`, we need + // to make sure that removing and adding back the virtio-net device does + // not break the live-migration support for virtio-pci. + #[cfg(target_arch = "x86_64")] + { + assert!(remote_command( + &src_api_socket, + "remove-device", + Some(net_id), + )); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); - snapshot_restore_common::snapshot_and_check_events( - &api_socket_source, - &snapshot_dir, - &event_path, - ); - }); + // Plug the virtio-net device again + assert!(remote_command( + &src_api_socket, + "add-net", + Some(net_params.as_str()), + )); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); + } - // Shutdown the source VM and check console output - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + // Enable watchdog and ensure its functional + let expected_reboot_count = 1; + // Enable the watchdog with a 15s timeout + enable_guest_watchdog(&guest, 15); - // Remove the vsock socket file. - Command::new("rm") - .arg("-f") - .arg(socket.as_str()) - .output() - .unwrap(); + assert_eq!(get_reboot_count(&guest), expected_reboot_count); + assert_eq!( + guest + .ssh_command("sudo journalctl | grep -c -- \"Watchdog started\"") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 1 + ); + // Allow some normal time to elapse to check we don't get spurious reboots + thread::sleep(std::time::Duration::new(40, 0)); + // Check no reboot + assert_eq!(get_reboot_count(&guest), expected_reboot_count); - let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); - let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + // Start the live-migration + let migration_socket = String::from( + guest + .tmp_dir + .as_path() + .join("live-migration.sock") + .to_str() + .unwrap(), + ); - // Restore the VM from the snapshot - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_restored]) - .args([ - "--event-monitor", - format!("path={event_path_restored}").as_str(), - ]) - .args([ - "--restore", - format!("source_url=file://{snapshot_dir}").as_str(), - ]) - .capture_output() - .spawn() - .unwrap(); + assert!( + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), + "Unsuccessful command: 'send-migration' or 'receive-migration'." + ); + }); - let latest_events = [&MetaEvent { - event: "restored".to_string(), - device_id: None, - }]; - // Wait for the restored event to show up in the monitor file. - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); + // Check and report any errors occurred during the live-migration + if r.is_err() { + print_and_panic( + src_child, + dest_child, + None, + "Error occurred during live-migration", + ); + } - // Remove the snapshot dir - let _ = remove_dir_all(snapshot_dir.as_str()); + // Check the source vm has been terminated successful (give it '3s' to settle) + thread::sleep(std::time::Duration::new(3, 0)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + print_and_panic( + src_child, + dest_child, + None, + "source VM was not terminated successfully.", + ); + } + // Post live-migration check to make sure the destination VM is functional let r = std::panic::catch_unwind(|| { - // Resume the VM - assert!(wait_until(Duration::from_secs(30), || remote_command( - &api_socket_restored, - "info", - None - ))); - assert!(remote_command(&api_socket_restored, "resume", None)); - let latest_events = [ - &MetaEvent { - event: "resuming".to_string(), - device_id: None, - }, - &MetaEvent { - event: "resumed".to_string(), - device_id: None, - }, - ]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); - - // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); - guest.check_devices_common(Some(&socket), Some(&console_text), None); - _test_ivshmem(&guest, &ivshmem_file_path, file_size); - }); - // Shutdown the target VM and check console output - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + // Perform same checks to validate VM has been properly migrated + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); - }); + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); - handle_child_output(r, &output); - } + // Perform checks on watchdog + let mut expected_reboot_count = 1; - #[test] - #[cfg(not(feature = "mshv"))] - fn test_live_migration_ivshmem() { - _test_live_migration_ivshmem(false); - } + // Allow some normal time to elapse to check we don't get spurious reboots + thread::sleep(std::time::Duration::new(40, 0)); + // Check no reboot + assert_eq!(get_reboot_count(&guest), expected_reboot_count); - #[test] - #[cfg(not(feature = "mshv"))] - fn test_live_migration_ivshmem_local() { - _test_live_migration_ivshmem(true); - } + // Trigger a panic (sync first). We need to do this inside a screen with a delay so the SSH command returns. + guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); + // Allow some time for the watchdog to trigger (max 30s) and reboot to happen + guest.wait_vm_boot_custom_timeout(50).unwrap(); + // Check a reboot is triggered by the watchdog + expected_reboot_count += 1; + assert_eq!(get_reboot_count(&guest), expected_reboot_count); - #[test] - #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_hotplug_virtiomem() { - snapshot_restore_common::_test_snapshot_restore(true, false); - } + #[cfg(target_arch = "x86_64")] + { + // Now pause the VM and remain offline for 30s + assert!(remote_command(&dest_api_socket, "pause", None)); + thread::sleep(std::time::Duration::new(30, 0)); + assert!(remote_command(&dest_api_socket, "resume", None)); - #[test] - #[cfg(not(feature = "mshv"))] // See issue #7437 - fn test_snapshot_restore_basic() { - snapshot_restore_common::_test_snapshot_restore(false, false); - } + // Check no reboot + assert_eq!(get_reboot_count(&guest), expected_reboot_count); + } + }); - #[test] - #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_with_resume() { - snapshot_restore_common::_test_snapshot_restore(false, true); - } + // Clean-up the destination VM and make sure it terminated correctly + let _ = dest_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + handle_child_output(r, &dest_output); - #[test] - #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_uffd() { - snapshot_restore_common::_test_snapshot_restore_uffd("size=2G", &[], 1_920_000); + // Check the destination VM has the expected 'console_text' from its output + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); + }); + handle_child_output(r, &dest_output); } - #[test] + // This test exercises the local live-migration between two Cloud Hypervisor VMs on the + // same host with Landlock enabled on both VMs. The test validates the following: + // 1. The source VM is up and functional + // 2. Ensure Landlock is enabled on source VM by hotplugging a disk. As the path for this + // disk is not known to the source VM this step will fail. + // 3. The 'send-migration' and 'receive-migration' command finished successfully; + // 4. The source VM terminated gracefully after live migration; + // 5. The destination VM is functional after live migration; + // 6. Ensure Landlock is enabled on destination VM by hotplugging a disk. As the path for + // this disk is not known to the destination VM this step will fail. #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_uffd_shared_memory() { - snapshot_restore_common::_test_snapshot_restore_uffd("size=512M,shared=on", &[], 480_000); - } - - #[test] - #[cfg(not(feature = "mshv"))] // See issue #7437 - #[cfg(target_arch = "x86_64")] - fn test_snapshot_restore_pvpanic() { - snapshot_restore_common::_test_snapshot_restore_devices(true); - } - - #[test] - fn test_virtio_pmem_persist_writes() { - test_virtio_pmem(false, false); - } -} - -#[cfg(not(feature = "mshv"))] -mod snapshot_restore_common { - use std::fs::remove_dir_all; - use std::process::Command; - - use crate::*; - - pub(crate) fn snapshot_and_check_events( - api_socket: &str, - snapshot_dir: &str, - event_path: &str, - ) { - // Pause the VM - assert!(remote_command(api_socket, "pause", None)); - let latest_events: [&MetaEvent; 2] = [ - &MetaEvent { - event: "pausing".to_string(), - device_id: None, - }, - &MetaEvent { - event: "paused".to_string(), - device_id: None, - }, - ]; - - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, event_path) - })); - - // Take a snapshot from the VM - assert!(remote_command( - api_socket, - "snapshot", - Some(format!("file://{snapshot_dir}").as_str()), - )); - - let latest_events = [ - &MetaEvent { - event: "snapshotting".to_string(), - device_id: None, - }, - &MetaEvent { - event: "snapshotted".to_string(), - device_id: None, - }, - ]; - - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, event_path) - })); - } - - pub(crate) fn _test_snapshot_restore(use_hotplug: bool, use_resume_option: bool) { + fn _test_live_migration_with_landlock() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); - - let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); - let net_id = "net123"; let net_params = format!( "id={},tap=,mac={},ip={},mask=255.255.255.128", net_id, guest.network.guest_mac0, guest.network.host_ip0 ); - let mut mem_params = "size=1G"; - - if use_hotplug { - mem_params = "size=2G,hotplug_method=virtio-mem,hotplug_size=32G"; - } - let cloudinit_params = format!( - "path={},iommu=on", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ); + let boot_vcpus = 2; + let max_vcpus = 4; - let socket = temp_vsock_path(&guest.tmp_dir); - let event_path = temp_event_monitor_path(&guest.tmp_dir); + let mut blk_file_path = dirs::home_dir().unwrap(); + blk_file_path.push("workloads"); + blk_file_path.push("blk.img"); - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_source]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .args(["--cpus", "boot=4"]) - .args(["--memory", mem_params]) - .args(["--balloon", "size=0"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) + let src_api_socket = temp_api_path(&guest.tmp_dir); + let mut src_child = GuestCommand::new(&guest) .args([ - "--disk", - format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - cloudinit_params.as_str(), + "--cpus", + format!("boot={boot_vcpus},max={max_vcpus}").as_str(), ]) - .args(["--net", net_params.as_str()]) - .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) + .args(["--memory", "size=1500M,shared=on"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--api-socket", &src_api_socket]) + .args(["--landlock"]) + .args(["--net", net_params.as_str()]) + .args([ + "--landlock-rules", + format!("path={:?},access=rw", guest.tmp_dir.as_path()).as_str(), + ]) .capture_output() .spawn() .unwrap(); - let console_text = String::from("On a branch floating down river a cricket, singing."); - // Create the snapshot directory - let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); + // Start the destination VM + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) + .capture_output() + .spawn() + .unwrap(); let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); + // Make sure the source VM is functaionl // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + // Check the guest RAM - let total_memory = guest.get_total_memory().unwrap_or_default(); - if use_hotplug { - assert!(total_memory > 1_900_000, "total memory: {total_memory}"); - } else { - assert!(total_memory > 900_000, "total memory: {total_memory}"); - } - if use_hotplug { - // Increase guest RAM with virtio-mem - resize_command( - &api_socket_source, - None, - Some(6 << 30), - None, - Some(&event_path), - ); - thread::sleep(std::time::Duration::new(5, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); - // Use balloon to remove RAM from the VM - resize_command( - &api_socket_source, - None, - None, - Some(1 << 30), - Some(&event_path), - ); - thread::sleep(std::time::Duration::new(5, 0)); - let total_memory = guest.get_total_memory().unwrap_or_default(); - assert!(total_memory > 4_800_000, "total_memory is {total_memory}"); - assert!(total_memory < 5_760_000, "total_memory is {total_memory}"); - } - // Check the guest virtio-devices, e.g. block, rng, vsock, console, and net - guest.check_devices_common(Some(&socket), Some(&console_text), None); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); - // x86_64: We check that removing and adding back the virtio-net device - // does not break the snapshot/restore support for virtio-pci. - // This is an important thing to test as the hotplug will - // trigger a PCI BAR reprogramming, which is a good way of - // checking if the stored resources are correctly restored. - // Unplug the virtio-net device - // AArch64: Device hotplug is currently not supported, skipping here. - #[cfg(target_arch = "x86_64")] - { - assert!(remote_command( - &api_socket_source, - "remove-device", - Some(net_id), - )); - thread::sleep(std::time::Duration::new(10, 0)); - let latest_events = [&MetaEvent { - event: "device-removed".to_string(), - device_id: Some(net_id.to_string()), - }]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path) - })); + // Check Landlock is enabled by hot-plugging a disk. + assert!(!remote_command( + &src_api_socket, + "add-disk", + Some(format!("path={},id=test0", blk_file_path.to_str().unwrap()).as_str()), + )); - // Plug the virtio-net device again - assert!(remote_command( - &api_socket_source, - "add-net", - Some(net_params.as_str()), - )); - thread::sleep(std::time::Duration::new(10, 0)); - } + // Start the live-migration + let migration_socket = String::from( + guest + .tmp_dir + .as_path() + .join("live-migration.sock") + .to_str() + .unwrap(), + ); - snapshot_restore_common::snapshot_and_check_events( - &api_socket_source, - &snapshot_dir, - &event_path, + assert!( + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, true), + "Unsuccessful command: 'send-migration' or 'receive-migration'." ); }); - // Shutdown the source VM and check console output - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + // Check and report any errors occurred during the live-migration + if r.is_err() { + print_and_panic( + src_child, + dest_child, + None, + "Error occurred during live-migration", + ); + } + + // Check the source vm has been terminated successful (give it '3s' to settle) + thread::sleep(std::time::Duration::new(3, 0)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + print_and_panic( + src_child, + dest_child, + None, + "source VM was not terminated successfully.", + ); + } + // Post live-migration check to make sure the destination VM is functioning let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + // Perform same checks to validate VM has been properly migrated + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); }); - handle_child_output(r, &output); + // Check Landlock is enabled on destination VM by hot-plugging a disk. + assert!(!remote_command( + &dest_api_socket, + "add-disk", + Some(format!("path={},id=test0", blk_file_path.to_str().unwrap()).as_str()), + )); - // Remove the vsock socket file. - Command::new("rm") - .arg("-f") - .arg(socket.as_str()) - .output() - .unwrap(); + // Clean-up the destination VM and make sure it terminated correctly + let _ = dest_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + handle_child_output(r, &dest_output); + } - let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); - let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + // Function to get an available port + #[cfg(not(feature = "mshv"))] + fn get_available_port() -> u16 { + TcpListener::bind("127.0.0.1:0") + .expect("Failed to bind to address") + .local_addr() + .unwrap() + .port() + } - // Restore the VM from the snapshot - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_restored]) + #[cfg(not(feature = "mshv"))] + fn start_live_migration_tcp( + src_api_socket: &str, + dest_api_socket: &str, + connections: NonZeroU32, + ) -> bool { + // Get an available TCP port + let migration_port = get_available_port(); + let host_ip = "127.0.0.1"; + + // Start the 'receive-migration' command on the destination + let mut receive_migration = Command::new(clh_command("ch-remote")) .args([ - "--event-monitor", - format!("path={event_path_restored}").as_str(), + &format!("--api-socket={dest_api_socket}"), + "receive-migration", + &format!("tcp:0.0.0.0:{migration_port}"), ]) + .stdin(Stdio::null()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + // Give the destination some time to start listening + thread::sleep(Duration::from_secs(1)); + + // Start the 'send-migration' command on the source + let connections = connections.get(); + let mut send_migration = Command::new(clh_command("ch-remote")) .args([ - "--restore", - format!("source_url=file://{snapshot_dir},resume={use_resume_option}").as_str(), + &format!("--api-socket={src_api_socket}"), + "send-migration", + &format!( + "destination_url=tcp:{host_ip}:{migration_port},connections={connections}" + ), ]) - .capture_output() + .stdin(Stdio::null()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) .spawn() .unwrap(); - let expected_events = [ - &MetaEvent { - event: "starting".to_string(), - device_id: None, - }, - &MetaEvent { - event: "activated".to_string(), - device_id: Some("__console".to_string()), - }, - &MetaEvent { - event: "activated".to_string(), - device_id: Some("__rng".to_string()), - }, - &MetaEvent { - event: "restoring".to_string(), - device_id: None, - }, - ]; - assert!(wait_until(Duration::from_secs(30), || { - check_sequential_events(&expected_events, &event_path_restored) - })); - if use_resume_option { - let latest_events = [ - &MetaEvent { - event: "restored".to_string(), - device_id: None, - }, - &MetaEvent { - event: "resuming".to_string(), - device_id: None, - }, - &MetaEvent { - event: "resumed".to_string(), - device_id: None, - }, - ]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); + // Check if the 'send-migration' command executed successfully + let send_success = if let Some(status) = send_migration + .wait_timeout(Duration::from_secs(60)) + .unwrap() + { + status.success() } else { - let latest_events = [&MetaEvent { - event: "restored".to_string(), - device_id: None, - }]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); - } + false + }; - // Wait until the restored VM API is ready before issuing follow-up requests. - assert!(wait_until(Duration::from_secs(30), || remote_command( - &api_socket_restored, - "info", - None - ))); + if !send_success { + let _ = send_migration.kill(); + let output = send_migration.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'send_migration' output ====\n\n---stdout---\n{}\n\n---stderr---\n{}\n\n==== End 'send_migration' output ====\n\n", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } - // Remove the snapshot dir - let _ = remove_dir_all(snapshot_dir.as_str()); + // Check if the 'receive-migration' command executed successfully + let receive_success = if let Some(status) = receive_migration + .wait_timeout(Duration::from_secs(60)) + .unwrap() + { + status.success() + } else { + false + }; - let r = std::panic::catch_unwind(|| { - if use_resume_option { - // VM was automatically resumed via restore option, just wait for events - thread::sleep(std::time::Duration::new(1, 0)); - } else { - // Resume the VM manually - assert!(wait_until(Duration::from_secs(30), || remote_command( - &api_socket_restored, - "info", - None - ))); - assert!(remote_command(&api_socket_restored, "resume", None)); + if !receive_success { + let _ = receive_migration.kill(); + let output = receive_migration.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'receive_migration' output ====\n\n---stdout---\n{}\n\n---stderr---\n{}\n\n==== End 'receive_migration' output ====\n\n", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } - let latest_events = [ - &MetaEvent { - event: "resuming".to_string(), - device_id: None, - }, - &MetaEvent { - event: "resumed".to_string(), - device_id: None, - }, - ]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); - } + send_success && receive_success + } - // Perform same checks to validate VM has been properly restored - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); - let total_memory = guest.get_total_memory().unwrap_or_default(); - if use_hotplug { - assert!(total_memory > 4_800_000, "total_memory is {total_memory}"); - assert!(total_memory < 5_760_000, "total_memory is {total_memory}"); - // Deflate balloon to restore entire RAM to the VM - resize_command(&api_socket_restored, None, None, Some(0), None); - thread::sleep(std::time::Duration::new(5, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); - // Decrease guest RAM with virtio-mem - resize_command(&api_socket_restored, None, Some(5 << 30), None, None); - thread::sleep(std::time::Duration::new(5, 0)); - let total_memory = guest.get_total_memory().unwrap_or_default(); - assert!(total_memory > 4_800_000, "total_memory is {total_memory}"); - assert!(total_memory < 5_760_000, "total_memory is {total_memory}"); - } else { - assert!(total_memory > 900_000, "total memory: {total_memory}"); - } - - guest.check_devices_common(Some(&socket), Some(&console_text), None); - }); - // Shutdown the target VM and check console output - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); - - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); - }); - - handle_child_output(r, &output); - } - - pub(crate) fn _test_snapshot_restore_uffd( - memory_config: &str, - memory_zone_config: &[&str], - min_total_memory_kib: u32, - ) { + #[cfg(not(feature = "mshv"))] + fn _test_live_migration_tcp(connections: NonZeroU32) { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); - - let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); - let console_text = String::from("On a branch floating down river a cricket, singing."); - let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); - let socket = temp_vsock_path(&guest.tmp_dir); - let event_path = temp_event_monitor_path(&guest.tmp_dir); - - let mut source_cmd = GuestCommand::new(&guest); - source_cmd - .args(["--api-socket", &api_socket_source]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .args(["--cpus", "boot=4"]) - .args(["--memory", memory_config]); - - if !memory_zone_config.is_empty() { - source_cmd.args(["--memory-zone"]).args(memory_zone_config); - } + let net_id = "net123"; + let net_params = format!( + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 + ); + let memory_param: &[&str] = &["--memory", "size=1500M,shared=on"]; + let boot_vcpus = 2; + let max_vcpus = 4; + let pmem_temp_file = TempFile::new().unwrap(); + pmem_temp_file.as_file().set_len(128 << 20).unwrap(); + std::process::Command::new("mkfs.ext4") + .arg(pmem_temp_file.as_path()) + .output() + .expect("Expect creating disk image to succeed"); + let pmem_path = String::from("/dev/pmem0"); - let mut child = source_cmd + // Start the source VM + let src_vm_path = clh_command("cloud-hypervisor"); + let src_api_socket = temp_api_path(&guest.tmp_dir); + let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); + src_vm_cmd + .args([ + "--cpus", + format!("boot={boot_vcpus},max={max_vcpus}").as_str(), + ]) + .args(memory_param) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() - .default_net() - .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) + .args(["--net", net_params.as_str()]) + .args(["--api-socket", &src_api_socket]) + .args([ + "--pmem", + format!( + "file={},discard_writes=on", + pmem_temp_file.as_path().to_str().unwrap(), + ) + .as_str(), + ]) + .capture_output(); + let mut src_child = src_vm_cmd.spawn().unwrap(); + + // Start the destination VM + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) .capture_output() .spawn() .unwrap(); let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); + // Ensure the source VM is running normally + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); - assert!(guest.get_total_memory().unwrap_or_default() > min_total_memory_kib); + // On x86_64 architecture, remove and re-add the virtio-net device + #[cfg(target_arch = "x86_64")] + { + assert!(remote_command( + &src_api_socket, + "remove-device", + Some(net_id), + )); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); + // Re-add the virtio-net device + assert!(remote_command( + &src_api_socket, + "add-net", + Some(net_params.as_str()), + )); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); + } + // Start TCP live migration + assert!( + start_live_migration_tcp(&src_api_socket, &dest_api_socket, connections), + "Unsuccessful command: 'send-migration' or 'receive-migration'." + ); + }); - guest.check_devices_common(Some(&socket), Some(&console_text), None); + // Check and report any errors that occurred during live migration + if r.is_err() { + print_and_panic( + src_child, + dest_child, + None, + "Error occurred during live-migration", + ); + } - snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); + // Check the source vm has been terminated successful (give it '3s' to settle) + thread::sleep(std::time::Duration::new(3, 0)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + print_and_panic( + src_child, + dest_child, + None, + "Source VM was not terminated successfully.", + ); + } + + // After live migration, ensure the destination VM is running normally + let r = std::panic::catch_unwind(|| { + // Perform the same checks to ensure the VM has migrated correctly + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); }); - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + // Clean up the destination VM and ensure it terminates properly + let _ = dest_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + handle_child_output(r, &dest_output); + // Check if the expected `console_text` is present in the destination VM's output let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); }); - handle_child_output(r, &output); + handle_child_output(r, &dest_output); + } - Command::new("rm") - .arg("-f") - .arg(socket.as_str()) - .output() - .unwrap(); + #[cfg(not(feature = "mshv"))] + fn _test_live_migration_tcp_timeout(timeout_strategy: TimeoutStrategy) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + let net_id = "net1337"; + let net_params = format!( + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 + ); + let memory_param: &[&str] = &["--memory", "size=1500M,shared=on"]; + let boot_vcpus = 2; - let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); - let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + let src_vm_path = clh_command("cloud-hypervisor"); + let src_api_socket = temp_api_path(&guest.tmp_dir); + let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); + src_vm_cmd + .args(["--cpus", format!("boot={boot_vcpus}").as_str()]) + .args(memory_param) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .args(["--net", net_params.as_str()]) + .args(["--api-socket", &src_api_socket]) + .capture_output(); + let mut src_child = src_vm_cmd.spawn().unwrap(); - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_restored]) - .args([ - "--event-monitor", - format!("path={event_path_restored}").as_str(), - ]) - .args([ - "--restore", - format!("source_url=file://{snapshot_dir},memory_restore_mode=ondemand").as_str(), - ]) + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) .capture_output() .spawn() .unwrap(); - let latest_events = [&MetaEvent { - event: "restored".to_string(), - device_id: None, - }]; - - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); - - let r = std::panic::catch_unwind(|| { - assert!(wait_until(Duration::from_secs(30), || remote_command( - &api_socket_restored, - "info", - None - ))); - assert!(remote_command(&api_socket_restored, "resume", None)); - - let latest_events = [ - &MetaEvent { - event: "resuming".to_string(), - device_id: None, - }, - &MetaEvent { - event: "resumed".to_string(), - device_id: None, - }, - ]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); + let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + guest.wait_vm_boot().unwrap(); - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); - assert!(guest.get_total_memory().unwrap_or_default() > min_total_memory_kib); + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - guest.check_devices_common(Some(&socket), Some(&console_text), None); - }); + // Start a memory stressor in the background to keep pages dirty, + // ensuring the precopy loop cannot converge within the 1s timeout. + guest + .ssh_command("nohup stress --vm 2 --vm-bytes 220M --vm-keep &>/dev/null &") + .unwrap(); + // Give stress a moment to actually start dirtying memory + thread::sleep(Duration::from_secs(3)); - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + let migration_port = get_available_port(); + let host_ip = "127.0.0.1"; - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); - - let logs = format!( - "{}\n{}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - assert!( - logs.contains("UFFD restore: demand-paged restore enabled"), - "Expected UFFD restore path to be enabled. output: {logs}" - ); - }); - handle_child_output(r, &output); + let mut receive_migration = Command::new(clh_command("ch-remote")) + .args([ + &format!("--api-socket={dest_api_socket}"), + "receive-migration", + &format!("tcp:0.0.0.0:{migration_port}"), + ]) + .stdin(Stdio::null()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); - let _ = remove_dir_all(snapshot_dir.as_str()); - } + thread::sleep(Duration::from_secs(1)); - pub(crate) fn _test_snapshot_restore_devices(pvpanic: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); + // Use a tight downtime budget (1ms) combined with a 1s timeout so the + // migration practically cannot converge regardless of strategy. + let mut send_migration = Command::new(clh_command("ch-remote")) + .args([ + &format!("--api-socket={src_api_socket}"), + "send-migration", + &format!( + "destination_url=tcp:{host_ip}:{migration_port},downtime_ms=1,timeout_s=1,timeout_strategy={timeout_strategy:?}" + ), + ]) + .stdin(Stdio::null()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); - let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); + let send_status = send_migration + .wait_timeout(Duration::from_secs(60)) + .unwrap(); + let receive_status = receive_migration + .wait_timeout(Duration::from_secs(60)) + .unwrap(); - let device_params = { - let mut data = vec![]; - if pvpanic { - data.push(String::from("--pvpanic")); + // Clean up receive-migration regardless of its outcome + if receive_status.is_none() { + let _ = receive_migration.kill(); } - data - }; - - let socket = temp_vsock_path(&guest.tmp_dir); - let event_path = temp_event_monitor_path(&guest.tmp_dir); - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_source]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .args(["--cpus", "boot=2"]) - .args(["--memory", "size=1G"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .default_disks() - .default_net() - .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(device_params) - .capture_output() - .spawn() - .unwrap(); + // Kill the stressor now that migration has completed or aborted, + // to reduce system load during post-migration checks. + let _ = guest.ssh_command("pkill -f 'stress --vm'"); - let console_text = String::from("On a branch floating down river a cricket, singing."); - let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); + match timeout_strategy { + TimeoutStrategy::Cancel => { + // With cancel strategy the send must fail and the source VM + // must keep running. + let send_failed = match send_status { + Some(status) => !status.success(), + None => { + let _ = send_migration.kill(); + false + } + }; + assert!( + send_failed, + "send-migration should have failed due to 1s timeout with cancel strategy" + ); - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); + thread::sleep(Duration::from_secs(2)); + assert!( + src_child.try_wait().unwrap().is_none(), + "Source VM should still be running after a cancelled migration" + ); - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + // Confirm the source VM is still responsive over SSH + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + } + TimeoutStrategy::Ignore => { + // With Ignore strategy the send must succeed despite the timeout + // being reached, and the source VM must have terminated. + let send_succeeded = match send_status { + Some(status) => status.success(), + None => { + let _ = send_migration.kill(); + false + } + }; + assert!( + send_succeeded, + "send-migration should have succeeded with timeout_strategy=ignore" + ); - snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); - }); + thread::sleep(Duration::from_secs(3)); + assert!( + src_child.try_wait().unwrap().is_some(), + "Source VM should have terminated after a forced migration" + ); - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + // Confirm the VM is still responsive over SSH on the new host + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + } + } + })); - Command::new("rm") - .arg("-f") - .arg(socket.as_str()) - .output() - .unwrap(); + let _ = src_child.kill(); + let src_output = src_child.wait_with_output().unwrap(); + let _ = dest_child.kill(); + let _dest_output = dest_child.wait_with_output().unwrap(); - let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); - let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + handle_child_output(r, &src_output); + } - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_restored]) - .args([ - "--event-monitor", - format!("path={event_path_restored}").as_str(), - ]) - .args([ - "--restore", - format!("source_url=file://{snapshot_dir}").as_str(), - ]) - .capture_output() - .spawn() - .unwrap(); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_basic() { + _test_live_migration(false, false); + } - let latest_events = [&MetaEvent { - event: "restored".to_string(), - device_id: None, - }]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_local() { + _test_live_migration(false, true); + } - let _ = remove_dir_all(snapshot_dir.as_str()); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_tcp() { + _test_live_migration_tcp(NonZeroU32::new(1).unwrap()); + } - let r = std::panic::catch_unwind(|| { - assert!(wait_until(Duration::from_secs(30), || remote_command( - &api_socket_restored, - "info", - None - ))); - assert!(remote_command(&api_socket_restored, "resume", None)); - let latest_events = [ - &MetaEvent { - event: "resuming".to_string(), - device_id: None, - }, - &MetaEvent { - event: "resumed".to_string(), - device_id: None, - }, - ]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_tcp_parallel_connections() { + _test_live_migration_tcp(NonZeroU32::new(8).unwrap()); + } - assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); - guest.check_devices_common(Some(&socket), Some(&console_text), None); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_tcp_timeout_cancel() { + _test_live_migration_tcp_timeout(TimeoutStrategy::Cancel); + } - if pvpanic { - make_guest_panic(&guest); - thread::sleep(std::time::Duration::new(10, 0)); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_tcp_timeout_ignore() { + _test_live_migration_tcp_timeout(TimeoutStrategy::Ignore); + } - let expected_sequential_events = [&MetaEvent { - event: "panic".to_string(), - device_id: None, - }]; - assert!(check_latest_events_exact( - &expected_sequential_events, - &event_path_restored - )); - } - }); - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_watchdog() { + _test_live_migration_watchdog(false, false); + } - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); - }); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_watchdog_local() { + _test_live_migration_watchdog(false, true); + } - handle_child_output(r, &output); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_upgrade_basic() { + _test_live_migration(true, false); } -} -mod common_sequential { + #[test] #[cfg(not(feature = "mshv"))] - use std::fs::remove_dir_all; + fn test_live_upgrade_local() { + _test_live_migration(true, true); + } + #[test] #[cfg(not(feature = "mshv"))] - use crate::*; + fn test_live_upgrade_watchdog() { + _test_live_migration_watchdog(true, false); + } #[test] #[cfg(not(feature = "mshv"))] - fn test_memory_mergeable_on() { - test_memory_mergeable(true); + fn test_live_upgrade_watchdog_local() { + _test_live_migration_watchdog(true, true); } #[test] #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_uffd_hugepage_zone() { - if !exec_host_command_status( - "grep -q '^Hugepagesize:[[:space:]]*2048 kB' /proc/meminfo && test $(awk '/HugePages_Free/ {print $2}' /proc/meminfo) -ge 256", - ) - .success() - { - println!("SKIPPED: not enough free 2MiB hugepages for UFFD restore test"); - return; - } - - snapshot_restore_common::_test_snapshot_restore_uffd( - "size=0", - &["id=mem0,size=512M,hugepages=on,hugepage_size=2M"], - 480_000, - ); + #[cfg(target_arch = "x86_64")] + fn test_live_migration_with_landlock() { + _test_live_migration_with_landlock(); } +} + +mod dbus_api { + use crate::*; + // Start cloud-hypervisor with no VM parameters, running both the HTTP + // and DBus APIs. Alternate calls to the external APIs (HTTP and DBus) + // to create a VM, boot it, and verify that it can be shut down and then + // booted again. #[test] - #[cfg(not(feature = "mshv"))] // See issue #7437 - #[ignore = "See #6970"] - fn test_snapshot_restore_with_fd() { + fn test_api_dbus_and_http_interleaved() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - - let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); - - let net_id = "net123"; - let num_queue_pairs: usize = 2; - // use a name that does not conflict with tap dev created from other tests - let tap_name = "chtap999"; - use std::str::FromStr; - let taps = net_util::open_tap( - Some(tap_name), - Some(std::net::IpAddr::V4( - std::net::Ipv4Addr::from_str(&guest.network.host_ip0).unwrap(), - )), - None, - &mut None, - None, - num_queue_pairs, - Some(libc::O_RDWR | libc::O_NONBLOCK), - ) - .unwrap(); - let net_params = format!( - "id={},fd=[{},{}],mac={},ip={},mask=255.255.255.128,num_queues={}", - net_id, - taps[0].as_raw_fd(), - taps[1].as_raw_fd(), - guest.network.guest_mac0, - guest.network.host_ip0, - num_queue_pairs * 2 - ); - - let cloudinit_params = format!( - "path={},iommu=on", - guest.disk_config.disk(DiskType::CloudInit).unwrap() - ); - - let n_cpu = 2; - let event_path = temp_event_monitor_path(&guest.tmp_dir); + let dbus_api = TargetApi::new_dbus_api(&guest.tmp_dir); + let http_api = TargetApi::new_http_api(&guest.tmp_dir); let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_source]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .args(["--cpus", format!("boot={n_cpu}").as_str()]) - .args(["--memory", "size=1G"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args([ - "--disk", - format!( - "path={}", - guest.disk_config.disk(DiskType::OperatingSystem).unwrap() - ) - .as_str(), - cloudinit_params.as_str(), - ]) - .args(["--net", net_params.as_str()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args(dbus_api.guest_args()) + .args(http_api.guest_args()) .capture_output() .spawn() .unwrap(); - let console_text = String::from("On a branch floating down river a cricket, singing."); - // Create the snapshot directory - let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); + thread::sleep(std::time::Duration::new(1, 0)); + + // Verify API servers are running + assert!(dbus_api.remote_command("ping", None)); + assert!(http_api.remote_command("ping", None)); + + // Create the VM first + let request_body = guest.api_create_body(); + + let temp_config_path = guest.tmp_dir.as_path().join("config"); + std::fs::write(&temp_config_path, request_body).unwrap(); + let create_config = temp_config_path.as_os_str().to_str().unwrap(); let r = std::panic::catch_unwind(|| { + // Create the VM + assert!(dbus_api.remote_command("create", Some(create_config),)); + + // Then boot it + assert!(http_api.remote_command("boot", None)); guest.wait_vm_boot().unwrap(); - // close the fds after VM boots, as CH duplicates them before using - for tap in taps.iter() { - unsafe { libc::close(tap.as_raw_fd()) }; - } + // Check that the VM booted as expected + guest.validate_cpu_count(None); + guest.validate_memory(None); - // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), n_cpu); - // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + // Sync and shutdown without powering off to prevent filesystem + // corruption. + guest.ssh_command("sync").unwrap(); + guest.ssh_command("sudo shutdown -H now").unwrap(); - // Check the guest virtio-devices, e.g. block, rng, vsock, console, and net - guest.check_devices_common(None, Some(&console_text), None); + // Wait for the guest to be fully shutdown + assert!(guest.wait_for_ssh_unresponsive(Duration::from_secs(20))); - snapshot_restore_common::snapshot_and_check_events( - &api_socket_source, - &snapshot_dir, - &event_path, - ); + // Then shutdown the VM + assert!(dbus_api.remote_command("shutdown", None)); + + // Then boot it again + assert!(http_api.remote_command("boot", None)); + guest.wait_vm_boot().unwrap(); + + // Check that the VM booted as expected + guest.validate_cpu_count(None); + guest.validate_memory(None); }); - // Shutdown the source VM and check console output kill_child(&mut child); let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); - - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); - }); handle_child_output(r, &output); + } - let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); - let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + #[test] + fn test_api_dbus_create_boot() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); - // Restore the VM from the snapshot - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_restored]) - .args([ - "--event-monitor", - format!("path={event_path_restored}").as_str(), - ]) - .capture_output() - .spawn() - .unwrap(); - thread::sleep(std::time::Duration::new(2, 0)); + let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); + _test_api_create_boot(&target_api, &guest); + } - let taps = net_util::open_tap( - Some(tap_name), - Some(std::net::IpAddr::V4( - std::net::Ipv4Addr::from_str(&guest.network.host_ip0).unwrap(), - )), - None, - &mut None, - None, - num_queue_pairs, - Some(libc::O_RDWR | libc::O_NONBLOCK), - ) - .unwrap(); - let restore_params = format!( - "source_url=file://{},net_fds=[{}@[{},{}]]", - snapshot_dir, - net_id, - taps[0].as_raw_fd(), - taps[1].as_raw_fd() - ); - assert!(remote_command( - &api_socket_restored, - "restore", - Some(restore_params.as_str()) - )); + #[test] + fn test_api_dbus_shutdown() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); - // Wait for the VM to be restored - assert!(wait_until(Duration::from_secs(20), || { - remote_command(&api_socket_restored, "info", None) - })); + let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); + _test_api_shutdown(&target_api, &guest); + } - // close the fds as CH duplicates them before using - for tap in taps.iter() { - unsafe { libc::close(tap.as_raw_fd()) }; - } + #[test] + fn test_api_dbus_delete() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); - let expected_events = [ - &MetaEvent { - event: "starting".to_string(), - device_id: None, - }, - &MetaEvent { - event: "activated".to_string(), - device_id: Some("__console".to_string()), - }, - &MetaEvent { - event: "activated".to_string(), - device_id: Some("__rng".to_string()), - }, - &MetaEvent { - event: "restoring".to_string(), - device_id: None, - }, - ]; - // Wait for the restore event sequence to be recorded. - assert!(wait_until(Duration::from_secs(30), || { - check_sequential_events(&expected_events, &event_path_restored) - })); - let latest_events = [&MetaEvent { - event: "restored".to_string(), - device_id: None, - }]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); + let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); + _test_api_delete(&target_api, &guest); + } - // Remove the snapshot dir - let _ = remove_dir_all(snapshot_dir.as_str()); + #[test] + fn test_api_dbus_pause_resume() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = GuestFactory::new_regular_guest_factory() + .create_guest(Box::new(disk_config)) + .with_cpu(4); - let r = std::panic::catch_unwind(|| { - // Resume the VM - assert!(wait_until(Duration::from_secs(20), || remote_command( - &api_socket_restored, - "info", - None - ))); - assert!(remote_command(&api_socket_restored, "resume", None)); - - let latest_events = [ - &MetaEvent { - event: "resuming".to_string(), - device_id: None, - }, - &MetaEvent { - event: "resumed".to_string(), - device_id: None, - }, - ]; - assert!(wait_until(Duration::from_secs(30), || { - check_latest_events_exact(&latest_events, &event_path_restored) - })); - - // Perform same checks to validate VM has been properly restored - assert_eq!(guest.get_cpu_count().unwrap_or_default(), n_cpu); - assert!(guest.get_total_memory().unwrap_or_default() > 960_000); + let target_api = TargetApi::new_dbus_api(&guest.tmp_dir); + _test_api_pause_resume(&target_api, &guest); + } +} - guest.check_devices_common(None, Some(&console_text), None); - }); - // Shutdown the target VM and check console output - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); +mod ivshmem { + #[cfg(not(feature = "mshv"))] + use std::fs::remove_dir_all; + use std::process::Command; - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); - }); + use test_infra::{Guest, GuestCommand, UbuntuDiskConfig, handle_child_output, kill_child}; - handle_child_output(r, &output); - } + use crate::*; - #[test] #[cfg(not(feature = "mshv"))] - fn test_snapshot_restore_virtio_fs() { + fn _test_live_migration_ivshmem(local: bool) { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); + let console_text = String::from("On a branch floating down river a cricket, singing."); + let net_id = "net123"; + let net_params = format!( + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 + ); - let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); + let memory_param: &[&str] = if local { + &["--memory", "size=4G,shared=on"] + } else { + &["--memory", "size=4G"] + }; - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - let mut shared_dir = workload_path; - shared_dir.push("shared_dir"); + let boot_vcpus = 2; + let max_vcpus = 4; - let (mut daemon_child, virtiofsd_socket_path) = - prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); + let pmem_temp_file = TempFile::new().unwrap(); + pmem_temp_file.as_file().set_len(128 << 20).unwrap(); + std::process::Command::new("mkfs.ext4") + .arg(pmem_temp_file.as_path()) + .output() + .expect("Expect creating disk image to succeed"); + let pmem_path = String::from("/dev/pmem0"); - let event_path = temp_event_monitor_path(&guest.tmp_dir); + let ivshmem_file_path = String::from( + guest + .tmp_dir + .as_path() + .join("ivshmem.data") + .to_str() + .unwrap(), + ); + let file_size = "1M"; - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_source]) - .args(["--event-monitor", format!("path={event_path}").as_str()]) - .args(["--cpus", "boot=2"]) - .args(["--memory", "size=512M,shared=on"]) + // Create a file to be used as the shared memory + Command::new("dd") + .args([ + "if=/dev/zero", + format!("of={ivshmem_file_path}").as_str(), + format!("bs={file_size}").as_str(), + "count=1", + ]) + .status() + .unwrap(); + + // Start the source VM + let src_vm_path = clh_command("cloud-hypervisor"); + let src_api_socket = temp_api_path(&guest.tmp_dir); + let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); + src_vm_cmd + .args([ + "--cpus", + format!("boot={boot_vcpus},max={max_vcpus}").as_str(), + ]) + .args(memory_param) .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() - .default_net() + .args(["--net", net_params.as_str()]) + .args(["--api-socket", &src_api_socket]) .args([ - "--fs", - format!("socket={virtiofsd_socket_path},tag=myfs,num_queues=1,queue_size=1024") - .as_str(), + "--pmem", + format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), ]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--ivshmem", + format!("path={ivshmem_file_path},size={file_size}").as_str(), + ]); + let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); + + // Start the destination VM + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) .capture_output() .spawn() .unwrap(); - let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); - let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); - // Mount virtiofs and write a test file - guest - .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") - .unwrap(); + // Make sure the source VM is functional + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + // Check the guest RAM + assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + // Check the guest virtio-devices, e.g. block, rng, console, and net + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + // x86_64: Following what's done in the `test_snapshot_restore`, we need + // to make sure that removing and adding back the virtio-net device does + // not break the live-migration support for virtio-pci. + #[cfg(target_arch = "x86_64")] + { + assert!(remote_command( + &src_api_socket, + "remove-device", + Some(net_id), + )); + thread::sleep(Duration::new(10, 0)); - // Verify the shared directory is accessible - assert_eq!( - guest.ssh_command("cat mount_dir/file1").unwrap().trim(), - "foo" + // Plug the virtio-net device again + assert!(remote_command( + &src_api_socket, + "add-net", + Some(net_params.as_str()), + )); + thread::sleep(Duration::new(10, 0)); + } + + // Check ivshmem device in src guest. + _test_ivshmem(&guest, &ivshmem_file_path, file_size); + // Allow some normal time to elapse to check we don't get spurious reboots + thread::sleep(std::time::Duration::new(40, 0)); + + // Start the live-migration + let migration_socket = String::from( + guest + .tmp_dir + .as_path() + .join("live-migration.sock") + .to_str() + .unwrap(), ); - // Write a file from the guest - guest - .ssh_command( - "sudo bash -c 'echo snapshot_test_data > mount_dir/snapshot_test_file'", - ) - .unwrap(); - snapshot_restore_common::snapshot_and_check_events( - &api_socket_source, - &snapshot_dir, - &event_path, + assert!( + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), + "Unsuccessful command: 'send-migration' or 'receive-migration'." ); }); - // Shutdown the source VM - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - handle_child_output(r, &output); + // Check and report any errors occurred during the live-migration + if r.is_err() { + print_and_panic( + src_child, + dest_child, + None, + "Error occurred during live-migration", + ); + } - // Kill the old virtiofsd - let _ = daemon_child.kill(); - let _ = daemon_child.wait(); + // Check the source vm has been terminated successful (give it '3s' to settle) + thread::sleep(std::time::Duration::new(3, 0)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + print_and_panic( + src_child, + dest_child, + None, + "source VM was not terminated successfully.", + ); + } - // Start a fresh virtiofsd (reusing the same socket path) - let (mut daemon_child, _) = prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); + // Post live-migration check to make sure the destination VM is functional + let r = std::panic::catch_unwind(|| { + // Perform same checks to validate VM has been properly migrated + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); - let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); - let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); - // Restore the VM from the snapshot - let mut child = GuestCommand::new(&guest) - .args(["--api-socket", &api_socket_restored]) - .args([ - "--event-monitor", - format!("path={event_path_restored}").as_str(), - ]) - .args([ - "--restore", - format!("source_url=file://{snapshot_dir}").as_str(), - ]) - .capture_output() - .spawn() - .unwrap(); + // Check ivshmem device + _test_ivshmem(&guest, &ivshmem_file_path, file_size); + }); - // Wait for the VM to be restored - assert!(wait_until(Duration::from_secs(30), || { - remote_command(&api_socket_restored, "info", None) - })); + // Clean-up the destination VM and make sure it terminated correctly + let _ = dest_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + handle_child_output(r, &dest_output); - let latest_events = [&MetaEvent { - event: "restored".to_string(), + // Check the destination VM has the expected 'console_text' from its output + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); + }); + handle_child_output(r, &dest_output); + } + + #[test] + fn test_ivshmem() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + + let kernel_path = direct_kernel_boot_path(); + + let ivshmem_file_path = String::from( + guest + .tmp_dir + .as_path() + .join("ivshmem.data") + .to_str() + .unwrap(), + ); + let file_size = "1M"; + + // Create a file to be used as the shared memory + Command::new("dd") + .args([ + "if=/dev/zero", + format!("of={ivshmem_file_path}").as_str(), + format!("bs={file_size}").as_str(), + "count=1", + ]) + .status() + .unwrap(); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=2"]) + .default_memory() + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .args([ + "--ivshmem", + format!("path={ivshmem_file_path},size={file_size}").as_str(), + ]) + .args(["--api-socket", &api_socket]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + _test_ivshmem(&guest, &ivshmem_file_path, file_size); + }); + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_ivshmem() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); + + let ivshmem_file_path = String::from( + guest + .tmp_dir + .as_path() + .join("ivshmem.data") + .to_str() + .unwrap(), + ); + let file_size = "1M"; + + // Create a file to be used as the shared memory + Command::new("dd") + .args([ + "if=/dev/zero", + format!("of={ivshmem_file_path}").as_str(), + format!("bs={file_size}").as_str(), + "count=1", + ]) + .status() + .unwrap(); + + let socket = temp_vsock_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_source]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=1G"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .default_disks() + .default_net() + .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--ivshmem", + format!("path={ivshmem_file_path},size={file_size}").as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + let console_text = String::from("On a branch floating down river a cricket, singing."); + // Create the snapshot directory + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + + snapshot_restore_common::snapshot_and_check_events( + &api_socket_source, + &snapshot_dir, + &event_path, + ); + }); + + // Shutdown the source VM and check console output + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + + // Remove the vsock socket file. + Command::new("rm") + .arg("-f") + .arg(socket.as_str()) + .output() + .unwrap(); + + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + + // Restore the VM from the snapshot + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_restored]) + .args([ + "--event-monitor", + format!("path={event_path_restored}").as_str(), + ]) + .args([ + "--restore", + format!("source_url=file://{snapshot_dir}").as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + let latest_events = [&MetaEvent { + event: "restored".to_string(), device_id: None, }]; - assert!(check_latest_events_exact( - &latest_events, - &event_path_restored - )); + // Wait for the restored event to show up in the monitor file. + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); // Remove the snapshot dir let _ = remove_dir_all(snapshot_dir.as_str()); @@ -7651,511 +7541,522 @@ mod common_sequential { None ))); assert!(remote_command(&api_socket_restored, "resume", None)); - thread::sleep(std::time::Duration::new(5, 0)); - - // Verify virtiofs still works after restore - // Read the file written before snapshot - assert_eq!( - guest - .ssh_command("cat mount_dir/snapshot_test_file") - .unwrap() - .trim(), - "snapshot_test_data" - ); - - // Read the pre-existing shared file - assert_eq!( - guest.ssh_command("cat mount_dir/file1").unwrap().trim(), - "foo" - ); - - // Write a new file after restore - guest - .ssh_command("sudo bash -c 'echo post_restore_data > mount_dir/post_restore_file'") - .unwrap(); + let latest_events = [ + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); - // Verify the new file exists on the host - let post_restore_content = - std::fs::read_to_string(shared_dir.join("post_restore_file")).unwrap(); - assert_eq!(post_restore_content.trim(), "post_restore_data"); + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + guest.check_devices_common(Some(&socket), Some(&console_text), None); + _test_ivshmem(&guest, &ivshmem_file_path, file_size); }); - - // Shutdown the target VM + // Shutdown the target VM and check console output kill_child(&mut child); let output = child.wait_with_output().unwrap(); handle_child_output(r, &output); - // Clean up virtiofsd and test files - let _ = daemon_child.kill(); - let _ = daemon_child.wait(); - let _ = std::fs::remove_file(shared_dir.join("snapshot_test_file")); - let _ = std::fs::remove_file(shared_dir.join("post_restore_file")); - } -} - -mod windows { - use std::sync::LazyLock; - - use crate::*; + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); - static NEXT_DISK_ID: LazyLock> = LazyLock::new(|| Mutex::new(1)); + handle_child_output(r, &output); + } - struct WindowsGuest { - guest: Guest, - auth: PasswordAuth, + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_ivshmem() { + _test_live_migration_ivshmem(false); } - trait FsType { - const FS_FAT: u8; - const FS_NTFS: u8; + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_ivshmem_local() { + _test_live_migration_ivshmem(true); } - impl FsType for WindowsGuest { - const FS_FAT: u8 = 0; - const FS_NTFS: u8 = 1; + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_hotplug_virtiomem() { + snapshot_restore_common::_test_snapshot_restore(true, false); } - impl WindowsGuest { - fn new() -> Self { - let disk = WindowsDiskConfig::new(WINDOWS_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk)); - let auth = PasswordAuth { - username: String::from("administrator"), - password: String::from("Admin123"), - }; - - WindowsGuest { guest, auth } - } - - fn guest(&self) -> &Guest { - &self.guest - } + #[test] + #[cfg(not(feature = "mshv"))] // See issue #7437 + fn test_snapshot_restore_basic() { + snapshot_restore_common::_test_snapshot_restore(false, false); + } - fn ssh_cmd(&self, cmd: &str) -> String { - ssh_command_ip_with_auth_retry( - cmd, - &self.auth, - &self.guest.network.guest_ip0, - DEFAULT_SSH_RETRIES, - DEFAULT_SSH_TIMEOUT, - ) - .unwrap() - } + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_with_resume() { + snapshot_restore_common::_test_snapshot_restore(false, true); + } - fn cpu_count(&self) -> u8 { - self.ssh_cmd("powershell -Command \"(Get-CimInstance win32_computersystem).NumberOfLogicalProcessors\"") - .trim() - .parse::() - .unwrap_or(0) - } + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_uffd() { + snapshot_restore_common::_test_snapshot_restore_uffd("size=2G", &[], 1_920_000); + } - fn ram_size(&self) -> usize { - self.ssh_cmd("powershell -Command \"(Get-CimInstance win32_computersystem).TotalPhysicalMemory\"") - .trim() - .parse::() - .unwrap_or(0) - } + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_uffd_shared_memory() { + snapshot_restore_common::_test_snapshot_restore_uffd("size=512M,shared=on", &[], 480_000); + } - fn netdev_count(&self) -> u8 { - self.ssh_cmd("powershell -Command \"netsh int ipv4 show interfaces | Select-String ethernet | Measure-Object -Line | Format-Table -HideTableHeaders\"") - .trim() - .parse::() - .unwrap_or(0) - } + #[test] + #[cfg(not(feature = "mshv"))] // See issue #7437 + #[cfg(target_arch = "x86_64")] + fn test_snapshot_restore_pvpanic() { + snapshot_restore_common::_test_snapshot_restore_devices(true); + } - fn disk_count(&self) -> u8 { - self.ssh_cmd("powershell -Command \"Get-Disk | Measure-Object -Line | Format-Table -HideTableHeaders\"") - .trim() - .parse::() - .unwrap_or(0) - } + #[test] + fn test_virtio_pmem_persist_writes() { + test_virtio_pmem(false, false); + } +} - fn reboot(&self) { - let _ = self.ssh_cmd("shutdown /r /t 0"); - } +#[cfg(not(feature = "mshv"))] +mod snapshot_restore_common { + use std::fs::remove_dir_all; + use std::process::Command; - fn shutdown(&self) { - let _ = self.ssh_cmd("shutdown /s /t 0"); - } + use crate::*; - fn run_dnsmasq(&self) -> std::process::Child { - let listen_address = format!("--listen-address={}", self.guest.network.host_ip0); - let dhcp_host = format!( - "--dhcp-host={},{}", - self.guest.network.guest_mac0, self.guest.network.guest_ip0 - ); - let dhcp_range = format!( - "--dhcp-range=eth,{},{}", - self.guest.network.guest_ip0, self.guest.network.guest_ip0 - ); + pub(crate) fn snapshot_and_check_events( + api_socket: &str, + snapshot_dir: &str, + event_path: &str, + ) { + // Pause the VM + assert!(remote_command(api_socket, "pause", None)); + let latest_events: [&MetaEvent; 2] = [ + &MetaEvent { + event: "pausing".to_string(), + device_id: None, + }, + &MetaEvent { + event: "paused".to_string(), + device_id: None, + }, + ]; - Command::new("dnsmasq") - .arg("--no-daemon") - .arg("--log-queries") - .arg(listen_address.as_str()) - .arg("--except-interface=lo") - .arg("--bind-dynamic") // Allow listening to host_ip while the interface is not ready yet. - .arg("--conf-file=/dev/null") - .arg(dhcp_host.as_str()) - .arg(dhcp_range.as_str()) - .spawn() - .unwrap() - } + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, event_path) + })); - // TODO Cleanup image file explicitly after test, if there's some space issues. - fn disk_new(&self, fs: u8, sz: usize) -> String { - let mut guard = NEXT_DISK_ID.lock().unwrap(); - let id = *guard; - *guard = id + 1; + // Take a snapshot from the VM + assert!(remote_command( + api_socket, + "snapshot", + Some(format!("file://{snapshot_dir}").as_str()), + )); - let img = PathBuf::from(format!("/tmp/test-hotplug-{id}.raw")); - let _ = fs::remove_file(&img); + let latest_events = [ + &MetaEvent { + event: "snapshotting".to_string(), + device_id: None, + }, + &MetaEvent { + event: "snapshotted".to_string(), + device_id: None, + }, + ]; - // Create an image file - let out = Command::new("qemu-img") - .args([ - "create", - "-f", - "raw", - img.to_str().unwrap(), - format!("{sz}m").as_str(), - ]) - .output() - .expect("qemu-img command failed") - .stdout; - println!("{out:?}"); + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, event_path) + })); + } - // Associate image to a loop device - let out = Command::new("losetup") - .args(["--show", "-f", img.to_str().unwrap()]) - .output() - .expect("failed to create loop device") - .stdout; - let _tmp = String::from_utf8_lossy(&out); - let loop_dev = _tmp.trim(); - println!("{out:?}"); + pub(crate) fn _test_snapshot_restore(use_hotplug: bool, use_resume_option: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); - // Create a partition table - // echo 'type=7' | sudo sfdisk "${LOOP}" - let mut child = Command::new("sfdisk") - .args([loop_dev]) - .stdin(Stdio::piped()) - .spawn() - .unwrap(); - let stdin = child.stdin.as_mut().expect("failed to open stdin"); - stdin - .write_all("type=7".as_bytes()) - .expect("failed to write stdin"); - let out = child.wait_with_output().expect("sfdisk failed").stdout; - println!("{out:?}"); + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); - // Disengage the loop device - let out = Command::new("losetup") - .args(["-d", loop_dev]) - .output() - .expect("loop device not found") - .stdout; - println!("{out:?}"); + let net_id = "net123"; + let net_params = format!( + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 + ); + let mut mem_params = "size=1G"; - // Re-associate loop device pointing to the partition only - let out = Command::new("losetup") - .args([ - "--show", - "--offset", - (512 * 2048).to_string().as_str(), - "-f", - img.to_str().unwrap(), - ]) - .output() - .expect("failed to create loop device") - .stdout; - let _tmp = String::from_utf8_lossy(&out); - let loop_dev = _tmp.trim(); - println!("{out:?}"); + if use_hotplug { + mem_params = "size=2G,hotplug_method=virtio-mem,hotplug_size=32G"; + } - // Create filesystem. - let fs_cmd = match fs { - WindowsGuest::FS_FAT => "mkfs.msdos", - WindowsGuest::FS_NTFS => "mkfs.ntfs", - _ => panic!("Unknown filesystem type '{fs}'"), - }; - let out = Command::new(fs_cmd) - .args([&loop_dev]) - .output() - .unwrap_or_else(|_| panic!("{fs_cmd} failed")) - .stdout; - println!("{out:?}"); + let cloudinit_params = format!( + "path={},iommu=on", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ); - // Disengage the loop device - let out = Command::new("losetup") - .args(["-d", loop_dev]) - .output() - .unwrap_or_else(|_| panic!("loop device '{loop_dev}' not found")) - .stdout; - println!("{out:?}"); + let socket = temp_vsock_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); - img.to_str().unwrap().to_string() - } - - fn disks_set_rw(&self) { - let _ = self.ssh_cmd("powershell -Command \"Get-Disk | Where-Object IsOffline -eq $True | Set-Disk -IsReadOnly $False\""); - } - - fn disks_online(&self) { - let _ = self.ssh_cmd("powershell -Command \"Get-Disk | Where-Object IsOffline -eq $True | Set-Disk -IsOffline $False\""); - } - - fn disk_file_put(&self, fname: &str, data: &str) { - let _ = self.ssh_cmd(&format!( - "powershell -Command \"'{data}' | Set-Content -Path {fname}\"" - )); - } - - fn disk_file_read(&self, fname: &str) -> String { - self.ssh_cmd(&format!( - "powershell -Command \"Get-Content -Path {fname}\"" - )) - } - - fn wait_for_boot(&self) -> Result<(), WaitForSshError> { - let out = wait_for_ssh( - "dir /b c:\\ | find \"Windows\"", - &self.auth, - &self.guest.network.guest_ip0, - Duration::from_secs(180), - )?; - - if out.trim() == "Windows" { - Ok(()) - } else { - panic!("Unexpected Windows boot probe output: {:?}", out.trim()); - } - } - } - - fn vcpu_threads_count(pid: u32) -> u8 { - // ps -T -p 12345 | grep vcpu | wc -l - let out = Command::new("ps") - .args(["-T", "-p", format!("{pid}").as_str()]) - .output() - .expect("ps command failed") - .stdout; - String::from_utf8_lossy(&out).matches("vcpu").count() as u8 - } - - fn netdev_ctrl_threads_count(pid: u32) -> u8 { - // ps -T -p 12345 | grep "_net[0-9]*_ctrl" | wc -l - let out = Command::new("ps") - .args(["-T", "-p", format!("{pid}").as_str()]) - .output() - .expect("ps command failed") - .stdout; - let mut n = 0; - String::from_utf8_lossy(&out) - .split_whitespace() - .for_each(|s| n += (s.starts_with("_net") && s.ends_with("_ctrl")) as u8); // _net1_ctrl - n - } - - fn disk_ctrl_threads_count(pid: u32) -> u8 { - // ps -T -p 15782 | grep "_disk[0-9]*_q0" | wc -l - let out = Command::new("ps") - .args(["-T", "-p", format!("{pid}").as_str()]) - .output() - .expect("ps command failed") - .stdout; - let mut n = 0; - String::from_utf8_lossy(&out) - .split_whitespace() - .for_each(|s| n += (s.starts_with("_disk") && s.ends_with("_q0")) as u8); // _disk0_q0, don't care about multiple queues as they're related to the same hdd - n - } - - #[test] - fn test_windows_guest() { - let windows_guest = WindowsGuest::new(); - - let mut child = GuestCommand::new(windows_guest.guest()) - .args(["--cpus", "boot=2,kvm_hyperv=on"]) - .args(["--memory", "size=4G"]) - .args(["--kernel", edk2_path().to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) - .default_disks() - .default_net() + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_source]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", "boot=4"]) + .args(["--memory", mem_params]) + .args(["--balloon", "size=0"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + cloudinit_params.as_str(), + ]) + .args(["--net", net_params.as_str()]) + .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .capture_output() .spawn() .unwrap(); - let fd = child.stdout.as_ref().unwrap().as_raw_fd(); - let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - let fd = child.stderr.as_ref().unwrap().as_raw_fd(); - let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; + let console_text = String::from("On a branch floating down river a cricket, singing."); + // Create the snapshot directory + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); - assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE); + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); - let mut child_dnsmasq = windows_guest.run_dnsmasq(); + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); + // Check the guest RAM + let total_memory = guest.get_total_memory().unwrap_or_default(); + if use_hotplug { + assert!(total_memory > 1_900_000, "total memory: {total_memory}"); + } else { + assert!(total_memory > 900_000, "total memory: {total_memory}"); + } + if use_hotplug { + // Increase guest RAM with virtio-mem + resize_command( + &api_socket_source, + None, + Some(6 << 30), + None, + Some(&event_path), + ); + thread::sleep(std::time::Duration::new(5, 0)); + assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); + // Use balloon to remove RAM from the VM + resize_command( + &api_socket_source, + None, + None, + Some(1 << 30), + Some(&event_path), + ); + thread::sleep(std::time::Duration::new(5, 0)); + let total_memory = guest.get_total_memory().unwrap_or_default(); + assert!(total_memory > 4_800_000, "total_memory is {total_memory}"); + assert!(total_memory < 5_760_000, "total_memory is {total_memory}"); + } + // Check the guest virtio-devices, e.g. block, rng, vsock, console, and net + guest.check_devices_common(Some(&socket), Some(&console_text), None); - let r = std::panic::catch_unwind(|| { - // Wait to make sure Windows boots up - windows_guest.wait_for_boot().unwrap(); + // x86_64: We check that removing and adding back the virtio-net device + // does not break the snapshot/restore support for virtio-pci. + // This is an important thing to test as the hotplug will + // trigger a PCI BAR reprogramming, which is a good way of + // checking if the stored resources are correctly restored. + // Unplug the virtio-net device + // AArch64: Device hotplug is currently not supported, skipping here. + #[cfg(target_arch = "x86_64")] + { + assert!(remote_command( + &api_socket_source, + "remove-device", + Some(net_id), + )); + thread::sleep(std::time::Duration::new(10, 0)); + let latest_events = [&MetaEvent { + event: "device-removed".to_string(), + device_id: Some(net_id.to_string()), + }]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path) + })); - windows_guest.shutdown(); + // Plug the virtio-net device again + assert!(remote_command( + &api_socket_source, + "add-net", + Some(net_params.as_str()), + )); + thread::sleep(std::time::Duration::new(10, 0)); + } + + snapshot_restore_common::snapshot_and_check_events( + &api_socket_source, + &snapshot_dir, + &event_path, + ); }); - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); + // Shutdown the source VM and check console output + kill_child(&mut child); let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); handle_child_output(r, &output); - } - #[test] - fn test_windows_guest_multiple_queues() { - let windows_guest = WindowsGuest::new(); + // Remove the vsock socket file. + Command::new("rm") + .arg("-f") + .arg(socket.as_str()) + .output() + .unwrap(); - let mut ovmf_path = dirs::home_dir().unwrap(); - ovmf_path.push("workloads"); - ovmf_path.push(OVMF_NAME); + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); - let mut child = GuestCommand::new(windows_guest.guest()) - .args(["--cpus", "boot=4,kvm_hyperv=on"]) - .args(["--memory", "size=4G"]) - .args(["--kernel", ovmf_path.to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) + // Restore the VM from the snapshot + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_restored]) .args([ - "--disk", - format!( - "path={},num_queues=4", - windows_guest - .guest() - .disk_config - .disk(DiskType::OperatingSystem) - .unwrap() - ) - .as_str(), + "--event-monitor", + format!("path={event_path_restored}").as_str(), ]) .args([ - "--net", - format!( - "tap=,mac={},ip={},mask=255.255.255.128,num_queues=8", - windows_guest.guest().network.guest_mac0, - windows_guest.guest().network.host_ip0 - ) - .as_str(), + "--restore", + format!("source_url=file://{snapshot_dir},resume={use_resume_option}").as_str(), ]) .capture_output() .spawn() .unwrap(); - let fd = child.stdout.as_ref().unwrap().as_raw_fd(); - let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - let fd = child.stderr.as_ref().unwrap().as_raw_fd(); - let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - - assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE); - - let mut child_dnsmasq = windows_guest.run_dnsmasq(); + let expected_events = [ + &MetaEvent { + event: "starting".to_string(), + device_id: None, + }, + &MetaEvent { + event: "activated".to_string(), + device_id: Some("__console".to_string()), + }, + &MetaEvent { + event: "activated".to_string(), + device_id: Some("__rng".to_string()), + }, + &MetaEvent { + event: "restoring".to_string(), + device_id: None, + }, + ]; + assert!(wait_until(Duration::from_secs(30), || { + check_sequential_events(&expected_events, &event_path_restored) + })); + if use_resume_option { + let latest_events = [ + &MetaEvent { + event: "restored".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); + } else { + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); + } + + // Wait until the restored VM API is ready before issuing follow-up requests. + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); + + // Remove the snapshot dir + let _ = remove_dir_all(snapshot_dir.as_str()); let r = std::panic::catch_unwind(|| { - // Wait to make sure Windows boots up - windows_guest.wait_for_boot().unwrap(); + if use_resume_option { + // VM was automatically resumed via restore option, just wait for events + thread::sleep(std::time::Duration::new(1, 0)); + } else { + // Resume the VM manually + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); + assert!(remote_command(&api_socket_restored, "resume", None)); - windows_guest.shutdown(); - }); + let latest_events = [ + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); + } - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); + // Perform same checks to validate VM has been properly restored + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); + let total_memory = guest.get_total_memory().unwrap_or_default(); + if use_hotplug { + assert!(total_memory > 4_800_000, "total_memory is {total_memory}"); + assert!(total_memory < 5_760_000, "total_memory is {total_memory}"); + // Deflate balloon to restore entire RAM to the VM + resize_command(&api_socket_restored, None, None, Some(0), None); + thread::sleep(std::time::Duration::new(5, 0)); + assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); + // Decrease guest RAM with virtio-mem + resize_command(&api_socket_restored, None, Some(5 << 30), None, None); + thread::sleep(std::time::Duration::new(5, 0)); + let total_memory = guest.get_total_memory().unwrap_or_default(); + assert!(total_memory > 4_800_000, "total_memory is {total_memory}"); + assert!(total_memory < 5_760_000, "total_memory is {total_memory}"); + } else { + assert!(total_memory > 900_000, "total memory: {total_memory}"); + } + + guest.check_devices_common(Some(&socket), Some(&console_text), None); + }); + // Shutdown the target VM and check console output + kill_child(&mut child); let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); handle_child_output(r, &output); } - #[test] - #[cfg(not(feature = "mshv"))] - #[cfg_attr(target_arch = "aarch64", ignore = "See #4327")] - fn test_windows_guest_snapshot_restore() { - let windows_guest = WindowsGuest::new(); + pub(crate) fn _test_snapshot_restore_uffd( + memory_config: &str, + memory_zone_config: &[&str], + min_total_memory_kib: u32, + ) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); - let mut ovmf_path = dirs::home_dir().unwrap(); - ovmf_path.push("workloads"); - ovmf_path.push(OVMF_NAME); + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); - let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); - let api_socket_source = format!("{}.1", temp_api_path(&tmp_dir)); + let console_text = String::from("On a branch floating down river a cricket, singing."); + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); + let socket = temp_vsock_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); - let mut child = GuestCommand::new(windows_guest.guest()) + let mut source_cmd = GuestCommand::new(&guest); + source_cmd .args(["--api-socket", &api_socket_source]) - .args(["--cpus", "boot=2,kvm_hyperv=on"]) - .args(["--memory", "size=4G"]) - .args(["--kernel", ovmf_path.to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", "boot=4"]) + .args(["--memory", memory_config]); + + if !memory_zone_config.is_empty() { + source_cmd.args(["--memory-zone"]).args(memory_zone_config); + } + + let mut child = source_cmd + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() .default_net() + .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) .capture_output() .spawn() .unwrap(); - let fd = child.stdout.as_ref().unwrap().as_raw_fd(); - let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - let fd = child.stderr.as_ref().unwrap().as_raw_fd(); - let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - - assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE); - - let mut child_dnsmasq = windows_guest.run_dnsmasq(); + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); - // Wait to make sure Windows boots up - windows_guest.wait_for_boot().unwrap(); + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); + assert!(guest.get_total_memory().unwrap_or_default() > min_total_memory_kib); - let snapshot_dir = temp_snapshot_dir_path(&tmp_dir); + guest.check_devices_common(Some(&socket), Some(&console_text), None); - // Pause the VM - assert!(remote_command(&api_socket_source, "pause", None)); + snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); + }); - // Take a snapshot from the VM - assert!(remote_command( - &api_socket_source, - "snapshot", - Some(format!("file://{snapshot_dir}").as_str()), - )); + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); - let snapshot_state_path = std::path::Path::new(&snapshot_dir).join("state.json"); - let snapshot_config_path = std::path::Path::new(&snapshot_dir).join("config.json"); - assert!(wait_until(Duration::from_secs(30), || { - snapshot_state_path.exists() && snapshot_config_path.exists() - })); + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); + handle_child_output(r, &output); - let _ = child.kill(); - child.wait().unwrap(); + Command::new("rm") + .arg("-f") + .arg(socket.as_str()) + .output() + .unwrap(); - let api_socket_restored = format!("{}.2", temp_api_path(&tmp_dir)); + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); - // Restore the VM from the snapshot - let mut child = GuestCommand::new(windows_guest.guest()) + let mut child = GuestCommand::new(&guest) .args(["--api-socket", &api_socket_restored]) + .args([ + "--event-monitor", + format!("path={event_path_restored}").as_str(), + ]) .args([ "--restore", - format!("source_url=file://{snapshot_dir}").as_str(), + format!("source_url=file://{snapshot_dir},memory_restore_mode=ondemand").as_str(), ]) .capture_output() .spawn() .unwrap(); - // Wait for the VM to be restored + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(wait_until(Duration::from_secs(30), || { - remote_command(&api_socket_restored, "info", None) + check_latest_events_exact(&latest_events, &event_path_restored) })); let r = std::panic::catch_unwind(|| { - // Resume the VM assert!(wait_until(Duration::from_secs(30), || remote_command( &api_socket_restored, "info", @@ -8163,671 +8064,670 @@ mod windows { ))); assert!(remote_command(&api_socket_restored, "resume", None)); - windows_guest.shutdown(); - }); + let latest_events = [ + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); - let output = child.wait_with_output().unwrap(); + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4); + assert!(guest.get_total_memory().unwrap_or_default() > min_total_memory_kib); - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); + guest.check_devices_common(Some(&socket), Some(&console_text), None); + }); + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); handle_child_output(r, &output); - } - #[test] - #[cfg(not(feature = "mshv"))] - #[cfg(not(target_arch = "aarch64"))] - fn test_windows_guest_cpu_hotplug() { - let windows_guest = WindowsGuest::new(); + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); - let mut ovmf_path = dirs::home_dir().unwrap(); - ovmf_path.push("workloads"); - ovmf_path.push(OVMF_NAME); + let logs = format!( + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert!( + logs.contains("UFFD restore: demand-paged restore enabled"), + "Expected UFFD restore path to be enabled. output: {logs}" + ); + }); + handle_child_output(r, &output); - let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); - let api_socket = temp_api_path(&tmp_dir); + let _ = remove_dir_all(snapshot_dir.as_str()); + } - let mut child = GuestCommand::new(windows_guest.guest()) - .args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=2,max=8,kvm_hyperv=on"]) - .args(["--memory", "size=4G"]) - .args(["--kernel", ovmf_path.to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) + pub(crate) fn _test_snapshot_restore_devices(pvpanic: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); + + let device_params = { + let mut data = vec![]; + if pvpanic { + data.push(String::from("--pvpanic")); + } + data + }; + + let socket = temp_vsock_path(&guest.tmp_dir); + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_source]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=1G"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) .default_disks() .default_net() + .args(["--vsock", format!("cid=3,socket={socket}").as_str()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args(device_params) .capture_output() .spawn() .unwrap(); - let mut child_dnsmasq = windows_guest.run_dnsmasq(); + let console_text = String::from("On a branch floating down river a cricket, singing."); + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); let r = std::panic::catch_unwind(|| { - // Wait to make sure Windows boots up - windows_guest.wait_for_boot().unwrap(); - - let vcpu_num = 2; - // Check the initial number of CPUs the guest sees - assert_eq!(windows_guest.cpu_count(), vcpu_num); - // Check the initial number of vcpu threads in the CH process - assert_eq!(vcpu_threads_count(child.id()), vcpu_num); - - let vcpu_num = 6; - // Hotplug some CPUs - resize_command(&api_socket, Some(vcpu_num), None, None, None); - // Wait for Windows to report the hotplugged CPUs. - assert!(wait_until(Duration::from_secs(10), || windows_guest - .cpu_count() - == vcpu_num)); - // Check the guest sees the correct number - assert_eq!(windows_guest.cpu_count(), vcpu_num); - // Check the CH process has the correct number of vcpu threads - assert_eq!(vcpu_threads_count(child.id()), vcpu_num); - - let vcpu_num = 4; - // Remove some CPUs. Note that Windows doesn't support hot-remove. - resize_command(&api_socket, Some(vcpu_num), None, None, None); - thread::sleep(std::time::Duration::new(10, 0)); + guest.wait_vm_boot().unwrap(); - // Reboot to let Windows catch up - windows_guest.reboot(); - // Wait for Windows to come back after the reboot. - windows_guest.wait_for_boot().unwrap(); - // Wait for Windows to reflect the unplugged CPU count. - assert!(wait_until(Duration::from_secs(60), || windows_guest - .cpu_count() - == vcpu_num)); - // Check the guest sees the correct number - assert_eq!(windows_guest.cpu_count(), vcpu_num); - // Check the CH process has the correct number of vcpu threads - assert_eq!(vcpu_threads_count(child.id()), vcpu_num); + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); - windows_guest.shutdown(); + snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path); }); - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); + kill_child(&mut child); let output = child.wait_with_output().unwrap(); - - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); - handle_child_output(r, &output); - } - - #[test] - #[cfg(not(feature = "mshv"))] - #[cfg(not(target_arch = "aarch64"))] - fn test_windows_guest_ram_hotplug() { - let windows_guest = WindowsGuest::new(); - let mut ovmf_path = dirs::home_dir().unwrap(); - ovmf_path.push("workloads"); - ovmf_path.push(OVMF_NAME); + Command::new("rm") + .arg("-f") + .arg(socket.as_str()) + .output() + .unwrap(); - let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); - let api_socket = temp_api_path(&tmp_dir); + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); - let mut child = GuestCommand::new(windows_guest.guest()) - .args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=2,kvm_hyperv=on"]) - .args(["--memory", "size=2G,hotplug_size=5G"]) - .args(["--kernel", ovmf_path.to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) - .default_disks() - .default_net() + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_restored]) + .args([ + "--event-monitor", + format!("path={event_path_restored}").as_str(), + ]) + .args([ + "--restore", + format!("source_url=file://{snapshot_dir}").as_str(), + ]) .capture_output() .spawn() .unwrap(); - let mut child_dnsmasq = windows_guest.run_dnsmasq(); + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); - let r = std::panic::catch_unwind(|| { - // Wait to make sure Windows boots up - windows_guest.wait_for_boot().unwrap(); + let _ = remove_dir_all(snapshot_dir.as_str()); - let ram_size = 2 * 1024 * 1024 * 1024; - // Check the initial number of RAM the guest sees - let current_ram_size = windows_guest.ram_size(); - // This size seems to be reserved by the system and thus the - // reported amount differs by this constant value. - let reserved_ram_size = ram_size - current_ram_size; - // Verify that there's not more than 4mb constant diff wasted - // by the reserved ram. - assert!(reserved_ram_size < 4 * 1024 * 1024); + let r = std::panic::catch_unwind(|| { + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); + assert!(remote_command(&api_socket_restored, "resume", None)); + let latest_events = [ + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); - let ram_size = 4 * 1024 * 1024 * 1024; - // Hotplug some RAM - resize_command(&api_socket, None, Some(ram_size), None, None); - // Wait for Windows to report the hotplugged memory. - assert!(wait_until(Duration::from_secs(10), || windows_guest - .ram_size() - == ram_size - reserved_ram_size)); + assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2); + guest.check_devices_common(Some(&socket), Some(&console_text), None); - let ram_size = 3 * 1024 * 1024 * 1024; - // Unplug some RAM. Note that hot-remove most likely won't work. - resize_command(&api_socket, None, Some(ram_size), None, None); - // Reboot to let Windows catch up - windows_guest.reboot(); - // Wait for Windows to come back after the reboot. - windows_guest.wait_for_boot().unwrap(); - // Wait for Windows to reflect the unplugged RAM amount. - assert!(wait_until(Duration::from_secs(60), || windows_guest - .ram_size() - == ram_size - reserved_ram_size)); - // Check the guest sees the correct number - assert_eq!(windows_guest.ram_size(), ram_size - reserved_ram_size); + if pvpanic { + make_guest_panic(&guest); + thread::sleep(std::time::Duration::new(10, 0)); - windows_guest.shutdown(); + let expected_sequential_events = [&MetaEvent { + event: "panic".to_string(), + device_id: None, + }]; + assert!(check_latest_events_exact( + &expected_sequential_events, + &event_path_restored + )); + } }); - - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); + kill_child(&mut child); let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); handle_child_output(r, &output); } +} - #[test] +mod common_sequential { #[cfg(not(feature = "mshv"))] - fn test_windows_guest_netdev_hotplug() { - let windows_guest = WindowsGuest::new(); + use std::fs::remove_dir_all; - let mut ovmf_path = dirs::home_dir().unwrap(); - ovmf_path.push("workloads"); - ovmf_path.push(OVMF_NAME); + #[cfg(not(feature = "mshv"))] + use crate::*; - let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); - let api_socket = temp_api_path(&tmp_dir); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_memory_mergeable_on() { + test_memory_mergeable(true); + } - let mut child = GuestCommand::new(windows_guest.guest()) - .args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=2,kvm_hyperv=on"]) - .args(["--memory", "size=4G"]) - .args(["--kernel", ovmf_path.to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); - - let mut child_dnsmasq = windows_guest.run_dnsmasq(); - - let r = std::panic::catch_unwind(|| { - // Wait to make sure Windows boots up - windows_guest.wait_for_boot().unwrap(); - - // Initially present network device - let netdev_num = 1; - assert_eq!(windows_guest.netdev_count(), netdev_num); - assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num); - - // Hotplug network device - let (cmd_success, cmd_output, _) = remote_command_w_output( - &api_socket, - "add-net", - Some(windows_guest.guest().default_net_string().as_str()), - ); - assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output).contains("\"id\":\"_net2\"")); - // Wait for Windows to enumerate the added network device. - assert!(wait_until(Duration::from_secs(5), || windows_guest - .netdev_count() - == 2 - && netdev_ctrl_threads_count(child.id()) == 2)); - // Verify the device is on the system - let netdev_num = 2; - assert_eq!(windows_guest.netdev_count(), netdev_num); - assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num); - - // Remove network device - let cmd_success = remote_command(&api_socket, "remove-device", Some("_net2")); - assert!(cmd_success); - // Wait for Windows to drop the removed network device. - assert!(wait_until(Duration::from_secs(5), || windows_guest - .netdev_count() - == 1 - && netdev_ctrl_threads_count(child.id()) == 1)); - // Verify the device has been removed - let netdev_num = 1; - assert_eq!(windows_guest.netdev_count(), netdev_num); - assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num); - - windows_guest.shutdown(); - }); - - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); - let output = child.wait_with_output().unwrap(); - - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_snapshot_restore_uffd_hugepage_zone() { + if !exec_host_command_status( + "grep -q '^Hugepagesize:[[:space:]]*2048 kB' /proc/meminfo && test $(awk '/HugePages_Free/ {print $2}' /proc/meminfo) -ge 256", + ) + .success() + { + println!("SKIPPED: not enough free 2MiB hugepages for UFFD restore test"); + return; + } - handle_child_output(r, &output); + snapshot_restore_common::_test_snapshot_restore_uffd( + "size=0", + &["id=mem0,size=512M,hugepages=on,hugepage_size=2M"], + 480_000, + ); } #[test] - #[ignore = "See #6037"] - #[cfg(not(feature = "mshv"))] - #[cfg(not(target_arch = "aarch64"))] - fn test_windows_guest_disk_hotplug() { - let windows_guest = WindowsGuest::new(); + #[cfg(not(feature = "mshv"))] // See issue #7437 + #[ignore = "See #6970"] + fn test_snapshot_restore_with_fd() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); - let mut ovmf_path = dirs::home_dir().unwrap(); - ovmf_path.push("workloads"); - ovmf_path.push(OVMF_NAME); + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); - let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); - let api_socket = temp_api_path(&tmp_dir); + let net_id = "net123"; + let num_queue_pairs: usize = 2; + // use a name that does not conflict with tap dev created from other tests + let tap_name = "chtap999"; + use std::str::FromStr; + let taps = net_util::open_tap( + Some(tap_name), + Some(std::net::IpAddr::V4( + std::net::Ipv4Addr::from_str(&guest.network.host_ip0).unwrap(), + )), + None, + &mut None, + None, + num_queue_pairs, + Some(libc::O_RDWR | libc::O_NONBLOCK), + ) + .unwrap(); + let net_params = format!( + "id={},fd=[{},{}],mac={},ip={},mask=255.255.255.128,num_queues={}", + net_id, + taps[0].as_raw_fd(), + taps[1].as_raw_fd(), + guest.network.guest_mac0, + guest.network.host_ip0, + num_queue_pairs * 2 + ); - let mut child = GuestCommand::new(windows_guest.guest()) - .args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=2,kvm_hyperv=on"]) - .args(["--memory", "size=4G"]) - .args(["--kernel", ovmf_path.to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) - .default_disks() - .default_net() + let cloudinit_params = format!( + "path={},iommu=on", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ); + + let n_cpu = 2; + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_source]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", format!("boot={n_cpu}").as_str()]) + .args(["--memory", "size=1G"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + cloudinit_params.as_str(), + ]) + .args(["--net", net_params.as_str()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .capture_output() .spawn() .unwrap(); - let mut child_dnsmasq = windows_guest.run_dnsmasq(); - - let disk = windows_guest.disk_new(WindowsGuest::FS_FAT, 100); + let console_text = String::from("On a branch floating down river a cricket, singing."); + // Create the snapshot directory + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); let r = std::panic::catch_unwind(|| { - // Wait to make sure Windows boots up - windows_guest.wait_for_boot().unwrap(); - - // Initially present disk device - let disk_num = 1; - assert_eq!(windows_guest.disk_count(), disk_num); - assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + guest.wait_vm_boot().unwrap(); - // Hotplug disk device - let (cmd_success, cmd_output, _) = remote_command_w_output( - &api_socket, - "add-disk", - Some(format!("path={disk},readonly=off").as_str()), - ); - assert!(cmd_success); - assert!(String::from_utf8_lossy(&cmd_output).contains("\"id\":\"_disk2\"")); - // Online disk device - windows_guest.disks_set_rw(); - windows_guest.disks_online(); - // Wait for Windows to enumerate the added disk. - assert!(wait_until(Duration::from_secs(5), || windows_guest - .disk_count() - == 2 - && disk_ctrl_threads_count(child.id()) == 2)); - // Verify the device is on the system - let disk_num = 2; - assert_eq!(windows_guest.disk_count(), disk_num); - assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + // close the fds after VM boots, as CH duplicates them before using + for tap in taps.iter() { + unsafe { libc::close(tap.as_raw_fd()) }; + } - let data = "hello"; - let fname = "d:\\world"; - windows_guest.disk_file_put(fname, data); + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), n_cpu); + // Check the guest RAM + assert!(guest.get_total_memory().unwrap_or_default() > 960_000); - // Unmount disk device - let cmd_success = remote_command(&api_socket, "remove-device", Some("_disk2")); - assert!(cmd_success); - // Wait for Windows to drop the removed disk. - assert!(wait_until(Duration::from_secs(5), || windows_guest - .disk_count() - == 1 - && disk_ctrl_threads_count(child.id()) == 1)); - // Verify the device has been removed - let disk_num = 1; - assert_eq!(windows_guest.disk_count(), disk_num); - assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + // Check the guest virtio-devices, e.g. block, rng, vsock, console, and net + guest.check_devices_common(None, Some(&console_text), None); - // Remount and check the file exists with the expected contents - let (cmd_success, _cmd_output, _) = remote_command_w_output( - &api_socket, - "add-disk", - Some(format!("path={disk},readonly=off").as_str()), + snapshot_restore_common::snapshot_and_check_events( + &api_socket_source, + &snapshot_dir, + &event_path, ); - assert!(cmd_success); - // Wait for Windows to mount the re-added disk again. - assert!(wait_until(Duration::from_secs(5), || windows_guest - .disk_file_read(fname) - .trim() - == data)); - let out = windows_guest.disk_file_read(fname); - assert_eq!(data, out.trim()); - - // Intentionally no unmount, it'll happen at shutdown. - - windows_guest.shutdown(); }); - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); + // Shutdown the source VM and check console output + kill_child(&mut child); let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); handle_child_output(r, &output); - } - #[test] - #[ignore = "See #6037"] - #[cfg(not(feature = "mshv"))] - #[cfg(not(target_arch = "aarch64"))] - fn test_windows_guest_disk_hotplug_multi() { - let windows_guest = WindowsGuest::new(); + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); - let mut ovmf_path = dirs::home_dir().unwrap(); - ovmf_path.push("workloads"); - ovmf_path.push(OVMF_NAME); - - let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); - let api_socket = temp_api_path(&tmp_dir); - - let mut child = GuestCommand::new(windows_guest.guest()) - .args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=2,kvm_hyperv=on"]) - .args(["--memory", "size=2G"]) - .args(["--kernel", ovmf_path.to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) - .default_disks() - .default_net() + // Restore the VM from the snapshot + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_restored]) + .args([ + "--event-monitor", + format!("path={event_path_restored}").as_str(), + ]) .capture_output() .spawn() .unwrap(); + thread::sleep(std::time::Duration::new(2, 0)); - let mut child_dnsmasq = windows_guest.run_dnsmasq(); - - // Predefined data to used at various test stages - let disk_test_data: [[String; 4]; 2] = [ - [ - "_disk2".to_string(), - windows_guest.disk_new(WindowsGuest::FS_FAT, 123), - "d:\\world".to_string(), - "hello".to_string(), - ], - [ - "_disk3".to_string(), - windows_guest.disk_new(WindowsGuest::FS_NTFS, 333), - "e:\\hello".to_string(), - "world".to_string(), - ], - ]; - - let r = std::panic::catch_unwind(|| { - // Wait to make sure Windows boots up - windows_guest.wait_for_boot().unwrap(); - - // Initially present disk device - let disk_num = 1; - assert_eq!(windows_guest.disk_count(), disk_num); - assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); - - for it in &disk_test_data { - let disk_id = it[0].as_str(); - let disk = it[1].as_str(); - - let expected_disk_num = windows_guest.disk_count() + 1; - let expected_ctrl_threads = disk_ctrl_threads_count(child.id()) + 1; - - // Hotplug disk device - let (cmd_success, cmd_output, _) = remote_command_w_output( - &api_socket, - "add-disk", - Some(format!("path={disk},readonly=off").as_str()), - ); - assert!(cmd_success); - assert!( - String::from_utf8_lossy(&cmd_output) - .contains(format!("\"id\":\"{disk_id}\"").as_str()) - ); - - // Wait for disk to appear - assert!(wait_until(Duration::from_secs(5), || { - windows_guest.disk_count() == expected_disk_num - && disk_ctrl_threads_count(child.id()) == expected_ctrl_threads - })); + let taps = net_util::open_tap( + Some(tap_name), + Some(std::net::IpAddr::V4( + std::net::Ipv4Addr::from_str(&guest.network.host_ip0).unwrap(), + )), + None, + &mut None, + None, + num_queue_pairs, + Some(libc::O_RDWR | libc::O_NONBLOCK), + ) + .unwrap(); + let restore_params = format!( + "source_url=file://{},net_fds=[{}@[{},{}]]", + snapshot_dir, + net_id, + taps[0].as_raw_fd(), + taps[1].as_raw_fd() + ); + assert!(remote_command( + &api_socket_restored, + "restore", + Some(restore_params.as_str()) + )); - // Online disk devices - windows_guest.disks_set_rw(); - windows_guest.disks_online(); - } - // Verify the devices are on the system - let disk_num = (disk_test_data.len() + 1) as u8; - assert_eq!(windows_guest.disk_count(), disk_num); - assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + // Wait for the VM to be restored + assert!(wait_until(Duration::from_secs(20), || { + remote_command(&api_socket_restored, "info", None) + })); - // Put test data - for it in &disk_test_data { - let fname = it[2].as_str(); - let data = it[3].as_str(); - windows_guest.disk_file_put(fname, data); - } + // close the fds as CH duplicates them before using + for tap in taps.iter() { + unsafe { libc::close(tap.as_raw_fd()) }; + } - // Unmount disk devices - for it in &disk_test_data { - let disk_id = it[0].as_str(); - let cmd_success = remote_command(&api_socket, "remove-device", Some(disk_id)); - assert!(cmd_success); - } + let expected_events = [ + &MetaEvent { + event: "starting".to_string(), + device_id: None, + }, + &MetaEvent { + event: "activated".to_string(), + device_id: Some("__console".to_string()), + }, + &MetaEvent { + event: "activated".to_string(), + device_id: Some("__rng".to_string()), + }, + &MetaEvent { + event: "restoring".to_string(), + device_id: None, + }, + ]; + // Wait for the restore event sequence to be recorded. + assert!(wait_until(Duration::from_secs(30), || { + check_sequential_events(&expected_events, &event_path_restored) + })); + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) + })); - // Wait for Windows to drop all removed disks. - assert!(wait_until(Duration::from_secs(5), || windows_guest - .disk_count() - == 1 - && disk_ctrl_threads_count(child.id()) == 1)); - // Verify the devices have been removed - let disk_num = 1; - assert_eq!(windows_guest.disk_count(), disk_num); - assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + // Remove the snapshot dir + let _ = remove_dir_all(snapshot_dir.as_str()); - // Remount - for it in &disk_test_data { - let disk = it[1].as_str(); - let (cmd_success, _cmd_output, _) = remote_command_w_output( - &api_socket, - "add-disk", - Some(format!("path={disk},readonly=off").as_str()), - ); - assert!(cmd_success); - } + let r = std::panic::catch_unwind(|| { + // Resume the VM + assert!(wait_until(Duration::from_secs(20), || remote_command( + &api_socket_restored, + "info", + None + ))); + assert!(remote_command(&api_socket_restored, "resume", None)); - // Wait for Windows to enumerate the re-added disks. - assert!(wait_until(Duration::from_secs(5), || { - windows_guest.disk_count() == 4 && disk_ctrl_threads_count(child.id()) == 4 + let latest_events = [ + &MetaEvent { + event: "resuming".to_string(), + device_id: None, + }, + &MetaEvent { + event: "resumed".to_string(), + device_id: None, + }, + ]; + assert!(wait_until(Duration::from_secs(30), || { + check_latest_events_exact(&latest_events, &event_path_restored) })); - // Check the files exists with the expected contents - for it in &disk_test_data { - let fname = it[2].as_str(); - let data = it[3].as_str(); - let out = windows_guest.disk_file_read(fname); - assert_eq!(data, out.trim()); - } - // Intentionally no unmount, it'll happen at shutdown. + // Perform same checks to validate VM has been properly restored + assert_eq!(guest.get_cpu_count().unwrap_or_default(), n_cpu); + assert!(guest.get_total_memory().unwrap_or_default() > 960_000); - windows_guest.shutdown(); + guest.check_devices_common(None, Some(&console_text), None); }); - - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); + // Shutdown the target VM and check console output + kill_child(&mut child); let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text)); + }); handle_child_output(r, &output); } #[test] #[cfg(not(feature = "mshv"))] - #[cfg(not(target_arch = "aarch64"))] - fn test_windows_guest_netdev_multi() { - let windows_guest = WindowsGuest::new(); + fn test_snapshot_restore_virtio_fs() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); - let mut ovmf_path = dirs::home_dir().unwrap(); - ovmf_path.push("workloads"); - ovmf_path.push(OVMF_NAME); + let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir)); - let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); - let api_socket = temp_api_path(&tmp_dir); + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + let mut shared_dir = workload_path; + shared_dir.push("shared_dir"); - let mut child = GuestCommand::new(windows_guest.guest()) - .args(["--api-socket", &api_socket]) - .args(["--cpus", "boot=2,kvm_hyperv=on"]) - .args(["--memory", "size=4G"]) - .args(["--kernel", ovmf_path.to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) + let (mut daemon_child, virtiofsd_socket_path) = + prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); + + let event_path = temp_event_monitor_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_source]) + .args(["--event-monitor", format!("path={event_path}").as_str()]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=512M,shared=on"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) .default_disks() - // The multi net dev config is borrowed from test_multiple_network_interfaces + .default_net() .args([ - "--net", - windows_guest.guest().default_net_string().as_str(), - "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.0", - "tap=mytap42,mac=fe:1f:9e:e1:60:f2,ip=192.168.4.1,mask=255.255.255.0", + "--fs", + format!("socket={virtiofsd_socket_path},tag=myfs,num_queues=1,queue_size=1024") + .as_str(), ]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .capture_output() .spawn() .unwrap(); - let mut child_dnsmasq = windows_guest.run_dnsmasq(); + let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir); let r = std::panic::catch_unwind(|| { - // Wait to make sure Windows boots up - windows_guest.wait_for_boot().unwrap(); + guest.wait_vm_boot().unwrap(); - let netdev_num = 3; - assert_eq!(windows_guest.netdev_count(), netdev_num); - assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num); + // Mount virtiofs and write a test file + guest + .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") + .unwrap(); - let tap_count = exec_host_command_output("ip link | grep -c mytap42"); - assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1"); + // Verify the shared directory is accessible + assert_eq!( + guest.ssh_command("cat mount_dir/file1").unwrap().trim(), + "foo" + ); - windows_guest.shutdown(); + // Write a file from the guest + guest + .ssh_command( + "sudo bash -c 'echo snapshot_test_data > mount_dir/snapshot_test_file'", + ) + .unwrap(); + snapshot_restore_common::snapshot_and_check_events( + &api_socket_source, + &snapshot_dir, + &event_path, + ); }); - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); + // Shutdown the source VM + kill_child(&mut child); let output = child.wait_with_output().unwrap(); - - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); - handle_child_output(r, &output); - } - #[test] - fn test_windows_guest_qcow2_backing_direct() { - let windows_guest = WindowsGuest::new(); + // Kill the old virtiofsd + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); - let qcow2_path = windows_guest.guest().disk_config.qcow2_disk().unwrap(); + // Start a fresh virtiofsd (reusing the same socket path) + let (mut daemon_child, _) = prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); - let mut child = GuestCommand::new(windows_guest.guest()) - .args(["--cpus", "boot=2,kvm_hyperv=on"]) - .args(["--memory", "size=4G"]) - .args(["--kernel", edk2_path().to_str().unwrap()]) - .args(["--serial", "tty"]) - .args(["--console", "off"]) + let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir)); + let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir)); + + // Restore the VM from the snapshot + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket_restored]) .args([ - "--disk", - format!("path={qcow2_path},image_type=qcow2,backing_files=on,direct=on").as_str(), + "--event-monitor", + format!("path={event_path_restored}").as_str(), + ]) + .args([ + "--restore", + format!("source_url=file://{snapshot_dir}").as_str(), ]) - .default_net() .capture_output() .spawn() .unwrap(); - let fd = child.stdout.as_ref().unwrap().as_raw_fd(); - let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - let fd = child.stderr.as_ref().unwrap().as_raw_fd(); - let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; + // Wait for the VM to be restored + assert!(wait_until(Duration::from_secs(30), || { + remote_command(&api_socket_restored, "info", None) + })); - assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE); + let latest_events = [&MetaEvent { + event: "restored".to_string(), + device_id: None, + }]; + assert!(check_latest_events_exact( + &latest_events, + &event_path_restored + )); - let mut child_dnsmasq = windows_guest.run_dnsmasq(); + // Remove the snapshot dir + let _ = remove_dir_all(snapshot_dir.as_str()); let r = std::panic::catch_unwind(|| { - windows_guest.wait_for_boot().unwrap(); + // Resume the VM + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); + assert!(remote_command(&api_socket_restored, "resume", None)); + thread::sleep(std::time::Duration::new(5, 0)); - // Write and read back files through qcow2 + direct I/O. - for i in 0..5 { - let fname = format!("c:\\test-dio-{i}.bin"); - let fname2 = format!("c:\\test-dio-{i}-copy.bin"); - let size = (i + 1) * 4 * 1024 * 1024; - windows_guest.ssh_cmd(&format!( - "powershell -Command \"\ - $r = New-Object byte[] {size}; \ - (New-Object Random {i}).NextBytes($r); \ - [IO.File]::WriteAllBytes('{fname}', $r)\"" - )); - let hash_write = windows_guest.ssh_cmd(&format!( - "powershell -Command \"(Get-FileHash '{fname}' -Algorithm SHA256).Hash\"" - )); - windows_guest.ssh_cmd(&format!("copy {fname} {fname2}")); - let hash_read = windows_guest.ssh_cmd(&format!( - "powershell -Command \"(Get-FileHash '{fname2}' -Algorithm SHA256).Hash\"" - )); - assert_eq!(hash_write.trim(), hash_read.trim()); - } + // Verify virtiofs still works after restore + // Read the file written before snapshot + assert_eq!( + guest + .ssh_command("cat mount_dir/snapshot_test_file") + .unwrap() + .trim(), + "snapshot_test_data" + ); - windows_guest.shutdown(); - }); + // Read the pre-existing shared file + assert_eq!( + guest.ssh_command("cat mount_dir/file1").unwrap().trim(), + "foo" + ); - let _ = child.wait_timeout(std::time::Duration::from_secs(60)); - let _ = child.kill(); - let output = child.wait_with_output().unwrap(); + // Write a new file after restore + guest + .ssh_command("sudo bash -c 'echo post_restore_data > mount_dir/post_restore_file'") + .unwrap(); - let _ = child_dnsmasq.kill(); - let _ = child_dnsmasq.wait(); + // Verify the new file exists on the host + let post_restore_content = + std::fs::read_to_string(shared_dir.join("post_restore_file")).unwrap(); + assert_eq!(post_restore_content.trim(), "post_restore_data"); + }); + // Shutdown the target VM + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); handle_child_output(r, &output); + + // Clean up virtiofsd and test files + let _ = daemon_child.kill(); + let _ = daemon_child.wait(); + let _ = std::fs::remove_file(shared_dir.join("snapshot_test_file")); + let _ = std::fs::remove_file(shared_dir.join("post_restore_file")); } -} -#[cfg(target_arch = "x86_64")] -mod vfio { - use crate::*; - const NVIDIA_VFIO_DEVICE: &str = "/sys/bus/pci/devices/0002:00:01.0"; + #[cfg(not(feature = "mshv"))] + fn _test_live_migration_balloon(upgrade_test: bool, local: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + let console_text = String::from("On a branch floating down river a cricket, singing."); + let net_id = "net123"; + let net_params = format!( + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 + ); - fn platform_cfg(iommufd: bool) -> String { - if iommufd { - "iommufd=on,vfio_p2p_dma=off".to_string() + let memory_param: &[&str] = if local { + &[ + "--memory", + "size=4G,hotplug_method=virtio-mem,hotplug_size=8G,shared=on", + "--balloon", + "size=0", + ] } else { - "iommufd=off".to_string() - } - } + &[ + "--memory", + "size=4G,hotplug_method=virtio-mem,hotplug_size=8G", + "--balloon", + "size=0", + ] + }; - fn test_nvidia_card_memory_hotplug(hotplug_method: &str, iommufd: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); + let boot_vcpus = 2; + let max_vcpus = 4; - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=4"]) + let pmem_temp_file = TempFile::new().unwrap(); + pmem_temp_file.as_file().set_len(128 << 20).unwrap(); + std::process::Command::new("mkfs.ext4") + .arg(pmem_temp_file.as_path()) + .output() + .expect("Expect creating disk image to succeed"); + let pmem_path = String::from("/dev/pmem0"); + + // Start the source VM + let src_vm_path = if upgrade_test { + cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") + }; + let src_api_socket = temp_api_path(&guest.tmp_dir); + let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); + src_vm_cmd .args([ - "--memory", - format!("size=4G,hotplug_size=4G,hotplug_method={hotplug_method}").as_str(), + "--cpus", + format!("boot={boot_vcpus},max={max_vcpus}").as_str(), ]) - .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) - .args(["--platform", &platform_cfg(iommufd)]) - .args(["--device", format!("path={NVIDIA_VFIO_DEVICE}").as_str()]) - .args(["--api-socket", &api_socket]) + .args(memory_param) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() - .default_net() + .args(["--net", net_params.as_str()]) + .args(["--api-socket", &src_api_socket]) + .args([ + "--pmem", + format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), + ]); + let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); + + // Start the destination VM + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) .capture_output() .spawn() .unwrap(); @@ -8835,66 +8735,208 @@ mod vfio { let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); - - // Verify the VFIO device works before memory hotplug - guest.check_nvidia_gpu(); - - guest.enable_memory_hotplug(); + // Make sure the source VM is functional + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - // Add RAM to the VM - let desired_ram = 6 << 30; - resize_command(&api_socket, None, Some(desired_ram), None, None); - assert!(wait_until(Duration::from_secs(5), || { + // Check the guest RAM + assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + // Increase the guest RAM + resize_command(&src_api_socket, None, Some(6 << 30), None, None); + assert!(wait_until(Duration::from_secs(30), || { guest.get_total_memory().unwrap_or_default() > 5_760_000 })); assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); + // Use balloon to remove RAM from the VM + resize_command(&src_api_socket, None, None, Some(1 << 30), None); + assert!(wait_until(Duration::from_secs(5), || { + let total_memory = guest.get_total_memory().unwrap_or_default(); + total_memory > 4_800_000 && total_memory < 5_760_000 + })); + let total_memory = guest.get_total_memory().unwrap_or_default(); + assert!(total_memory > 4_800_000); + assert!(total_memory < 5_760_000); - // Check the VFIO device works when RAM is increased to 6GiB. - // After guest memory hotplug, the VMM must refresh VFIO/iommufd DMA - // mappings for the passthrough GPU. - assert!(wait_until(Duration::from_secs(10), || guest.check_nvidia_gpu())); + // Check the guest virtio-devices, e.g. block, rng, console, and net + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + + // x86_64: Following what's done in the `test_snapshot_restore`, we need + // to make sure that removing and adding back the virtio-net device does + // not break the live-migration support for virtio-pci. + #[cfg(target_arch = "x86_64")] + { + assert!(remote_command( + &src_api_socket, + "remove-device", + Some(net_id), + )); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); + + // Plug the virtio-net device again + assert!(remote_command( + &src_api_socket, + "add-net", + Some(net_params.as_str()), + )); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); + } + + // Start the live-migration + let migration_socket = String::from( + guest + .tmp_dir + .as_path() + .join("live-migration.sock") + .to_str() + .unwrap(), + ); + + assert!( + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), + "Unsuccessful command: 'send-migration' or 'receive-migration'." + ); }); - let _ = child.kill(); - let output = child.wait_with_output().unwrap(); + // Check and report any errors occurred during the live-migration + if r.is_err() { + print_and_panic( + src_child, + dest_child, + None, + "Error occurred during live-migration", + ); + } - handle_child_output(r, &output); - } + // Check the source vm has been terminated successful (give it '3s' to settle) + thread::sleep(std::time::Duration::new(3, 0)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + print_and_panic( + src_child, + dest_child, + None, + "source VM was not terminated successfully.", + ); + } - #[test] - fn test_nvidia_card_memory_hotplug_acpi() { - test_nvidia_card_memory_hotplug("acpi", false); - } + // Post live-migration check to make sure the destination VM is functional + let r = std::panic::catch_unwind(|| { + // Perform same checks to validate VM has been properly migrated + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); - #[test] - fn test_nvidia_card_memory_hotplug_virtio_mem() { - test_nvidia_card_memory_hotplug("virtio-mem", false); - } + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); - #[test] - fn test_iommufd_nvidia_card_memory_hotplug_acpi() { - test_nvidia_card_memory_hotplug("acpi", true); - } + // Perform checks on guest RAM using balloon + let total_memory = guest.get_total_memory().unwrap_or_default(); + assert!(total_memory > 4_800_000); + assert!(total_memory < 5_760_000); + // Deflate balloon to restore entire RAM to the VM + resize_command(&dest_api_socket, None, None, Some(0), None); + thread::sleep(std::time::Duration::new(5, 0)); + assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); + // Decrease guest RAM with virtio-mem + resize_command(&dest_api_socket, None, Some(5 << 30), None, None); + thread::sleep(std::time::Duration::new(5, 0)); + let total_memory = guest.get_total_memory().unwrap_or_default(); + assert!(total_memory > 4_800_000); + assert!(total_memory < 5_760_000); + }); - #[test] - fn test_iommufd_nvidia_card_memory_hotplug_virtio_mem() { - test_nvidia_card_memory_hotplug("virtio-mem", true); + // Clean-up the destination VM and make sure it terminated correctly + let _ = dest_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + handle_child_output(r, &dest_output); + + // Check the destination VM has the expected 'console_text' from its output + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); + }); + handle_child_output(r, &dest_output); } - fn test_nvidia_card_pci_hotplug_common(iommufd: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + #[cfg(not(feature = "mshv"))] + fn _test_live_migration_numa(upgrade_test: bool, local: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); + let kernel_path = direct_kernel_boot_path(); + let console_text = String::from("On a branch floating down river a cricket, singing."); + let net_id = "net123"; + let net_params = format!( + "id={},tap=,mac={},ip={},mask=255.255.255.128", + net_id, guest.network.guest_mac0, guest.network.host_ip0 + ); - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=4"]) - .args(["--memory", "size=1G"]) - .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) - .args(["--platform", &platform_cfg(iommufd)]) - .args(["--api-socket", &api_socket]) + let memory_param: &[&str] = if local { + &[ + "--memory", + "size=0,hotplug_method=virtio-mem,shared=on", + "--memory-zone", + "id=mem0,size=1G,hotplug_size=4G,shared=on", + "id=mem1,size=1G,hotplug_size=4G,shared=on", + "id=mem2,size=2G,hotplug_size=4G,shared=on", + "--numa", + "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0", + "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1", + "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2", + ] + } else { + &[ + "--memory", + "size=0,hotplug_method=virtio-mem", + "--memory-zone", + "id=mem0,size=1G,hotplug_size=4G", + "id=mem1,size=1G,hotplug_size=4G", + "id=mem2,size=2G,hotplug_size=4G", + "--numa", + "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0", + "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1", + "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2", + ] + }; + + let boot_vcpus = 6; + let max_vcpus = 12; + + let pmem_temp_file = TempFile::new().unwrap(); + pmem_temp_file.as_file().set_len(128 << 20).unwrap(); + std::process::Command::new("mkfs.ext4") + .arg(pmem_temp_file.as_path()) + .output() + .expect("Expect creating disk image to succeed"); + let pmem_path = String::from("/dev/pmem0"); + + // Start the source VM + let src_vm_path = if upgrade_test { + cloud_hypervisor_release_path() + } else { + clh_command("cloud-hypervisor") + }; + let src_api_socket = temp_api_path(&guest.tmp_dir); + let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); + src_vm_cmd + .args([ + "--cpus", + format!("boot={boot_vcpus},max={max_vcpus}").as_str(), + ]) + .args(memory_param) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() - .default_net() + .args(["--net", net_params.as_str()]) + .args(["--api-socket", &src_api_socket]) + .args([ + "--pmem", + format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), + ]); + let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); + + // Start the destination VM + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) .capture_output() .spawn() .unwrap(); @@ -8902,329 +8944,300 @@ mod vfio { let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); - // Hotplug the card to the VM - let (cmd_success, cmd_output, _) = remote_command_w_output( - &api_socket, - "add-device", - Some(format!("id=vfio0,path={NVIDIA_VFIO_DEVICE}").as_str()), - ); - assert!(cmd_success); - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"vfio0\",\"bdf\":\"0000:00:06.0\"}") - ); - - // Check the VFIO device works after hotplug - assert!(wait_until(Duration::from_secs(10), || guest.check_nvidia_gpu())); - }); + // Make sure the source VM is functional + // Check the number of vCPUs + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - let _ = child.kill(); - let output = child.wait_with_output().unwrap(); + // Check the guest RAM + assert!(guest.get_total_memory().unwrap_or_default() > 2_880_000); - handle_child_output(r, &output); - } + // Check the guest virtio-devices, e.g. block, rng, console, and net + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); - #[test] - fn test_nvidia_card_pci_hotplug() { - test_nvidia_card_pci_hotplug_common(false); - } + // Check the NUMA parameters are applied correctly and resize + // each zone to test the case where we migrate a VM with the + // virtio-mem regions being used. + { + guest.check_numa_common( + Some(&[960_000, 960_000, 1_920_000]), + Some(&[&[0, 1, 2], &[3, 4], &[5]]), + Some(&["10 15 20", "20 10 25", "25 30 10"]), + ); - #[test] - fn test_iommufd_nvidia_card_pci_hotplug() { - test_nvidia_card_pci_hotplug_common(true); - } + // AArch64 currently does not support hotplug, and therefore we only + // test hotplug-related function on x86_64 here. + #[cfg(target_arch = "x86_64")] + { + guest.enable_memory_hotplug(); - fn test_nvidia_card_reboot_common(iommufd: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); + // Resize every memory zone and check each associated NUMA node + // has been assigned the right amount of memory. + resize_zone_command(&src_api_socket, "mem0", "2G"); + resize_zone_command(&src_api_socket, "mem1", "2G"); + resize_zone_command(&src_api_socket, "mem2", "3G"); + thread::sleep(std::time::Duration::new(5, 0)); - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=4"]) - .args(["--memory", "size=1G"]) - .args(["--platform", &platform_cfg(iommufd)]) - .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) - .args([ - "--device", - format!("path={NVIDIA_VFIO_DEVICE},iommu=on").as_str(), - ]) - .args(["--api-socket", &api_socket]) - .default_disks() - .default_net() - .capture_output() - .spawn() - .unwrap(); + guest.check_numa_common(Some(&[1_920_000, 1_920_000, 1_920_000]), None, None); + } + } - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); + // x86_64: Following what's done in the `test_snapshot_restore`, we need + // to make sure that removing and adding back the virtio-net device does + // not break the live-migration support for virtio-pci. + #[cfg(target_arch = "x86_64")] + { + assert!(remote_command( + &src_api_socket, + "remove-device", + Some(net_id), + )); + assert!(wait_until(Duration::from_secs(10), || { + guest.wait_for_ssh(Duration::from_secs(1)).is_err() + })); - // Check the VFIO device works after boot - assert!(guest.check_nvidia_gpu()); + // Plug the virtio-net device again + assert!(remote_command( + &src_api_socket, + "add-net", + Some(net_params.as_str()), + )); + guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); + } - guest.reboot_linux(0); + // Start the live-migration + let migration_socket = String::from( + guest + .tmp_dir + .as_path() + .join("live-migration.sock") + .to_str() + .unwrap(), + ); - // Check the VFIO device works after reboot - assert!(guest.check_nvidia_gpu()); + assert!( + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), + "Unsuccessful command: 'send-migration' or 'receive-migration'." + ); }); - let _ = child.kill(); - let output = child.wait_with_output().unwrap(); + // Check and report any errors occurred during the live-migration + if r.is_err() { + print_and_panic( + src_child, + dest_child, + None, + "Error occurred during live-migration", + ); + } - handle_child_output(r, &output); - } + // Check the source vm has been terminated successful (give it '3s' to settle) + thread::sleep(std::time::Duration::new(3, 0)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + print_and_panic( + src_child, + dest_child, + None, + "source VM was not terminated successfully.", + ); + } - #[test] - fn test_nvidia_card_reboot() { - test_nvidia_card_reboot_common(false); - } + // Post live-migration check to make sure the destination VM is functional + let r = std::panic::catch_unwind(|| { + // Perform same checks to validate VM has been properly migrated + assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + #[cfg(target_arch = "x86_64")] + assert!(guest.get_total_memory().unwrap_or_default() > 6_720_000); + #[cfg(target_arch = "aarch64")] + assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); - #[test] - fn test_iommufd_nvidia_card_reboot() { - test_nvidia_card_reboot_common(true); + guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + + // Perform NUMA related checks + { + #[cfg(target_arch = "aarch64")] + { + guest.check_numa_common( + Some(&[960_000, 960_000, 1_920_000]), + Some(&[&[0, 1, 2], &[3, 4], &[5]]), + Some(&["10 15 20", "20 10 25", "25 30 10"]), + ); + } + + // AArch64 currently does not support hotplug, and therefore we only + // test hotplug-related function on x86_64 here. + #[cfg(target_arch = "x86_64")] + { + guest.check_numa_common( + Some(&[1_920_000, 1_920_000, 2_880_000]), + Some(&[&[0, 1, 2], &[3, 4], &[5]]), + Some(&["10 15 20", "20 10 25", "25 30 10"]), + ); + + guest.enable_memory_hotplug(); + + // Resize every memory zone and check each associated NUMA node + // has been assigned the right amount of memory. + resize_zone_command(&dest_api_socket, "mem0", "4G"); + resize_zone_command(&dest_api_socket, "mem1", "4G"); + resize_zone_command(&dest_api_socket, "mem2", "4G"); + // Resize to the maximum amount of CPUs and check each NUMA + // node has been assigned the right CPUs set. + resize_command(&dest_api_socket, Some(max_vcpus), None, None, None); + thread::sleep(std::time::Duration::new(5, 0)); + + guest.check_numa_common( + Some(&[3_840_000, 3_840_000, 3_840_000]), + Some(&[&[0, 1, 2, 9], &[3, 4, 6, 7, 8], &[5, 10, 11]]), + None, + ); + } + } + }); + + // Clean-up the destination VM and make sure it terminated correctly + let _ = dest_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + handle_child_output(r, &dest_output); + + // Check the destination VM has the expected 'console_text' from its output + let r = std::panic::catch_unwind(|| { + assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); + }); + handle_child_output(r, &dest_output); } - fn test_nvidia_card_iommu_address_width_common(iommufd: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); + #[cfg(not(feature = "mshv"))] + fn _test_live_migration_ovs_dpdk(upgrade_test: bool, local: bool) { + let ovs_disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let ovs_guest = Guest::new(Box::new(ovs_disk_config)); - let platform = format!( - "num_pci_segments=2,iommu_segments=1,iommu_address_width=42,{}", - platform_cfg(iommufd) - ); + let migration_disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let migration_guest = Guest::new(Box::new(migration_disk_config)); + let src_api_socket = temp_api_path(&migration_guest.tmp_dir); - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=4"]) - .args(["--memory", "size=1G"]) - .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) - .args(["--device", format!("path={NVIDIA_VFIO_DEVICE}").as_str()]) - .args(["--platform", &platform]) - .args(["--api-socket", &api_socket]) - .default_disks() - .default_net() + // Start two VMs that are connected through ovs-dpdk and one of the VMs is the source VM for live-migration + let (mut ovs_child, mut src_child) = + setup_ovs_dpdk_guests(&ovs_guest, &migration_guest, &src_api_socket, upgrade_test); + + // Start the destination VM + let mut dest_api_socket = temp_api_path(&migration_guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&migration_guest) + .args(["--api-socket", &dest_api_socket]) .capture_output() .spawn() .unwrap(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); + // Give it '1s' to make sure the 'dest_api_socket' file is properly created + thread::sleep(std::time::Duration::new(1, 0)); - assert!( - guest - .ssh_command("sudo dmesg") - .unwrap() - .contains("input address: 42 bits") + // Start the live-migration + let migration_socket = String::from( + migration_guest + .tmp_dir + .as_path() + .join("live-migration.sock") + .to_str() + .unwrap(), ); - // Check the VFIO device works after boot - guest.check_nvidia_gpu(); + assert!( + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), + "Unsuccessful command: 'send-migration' or 'receive-migration'." + ); }); - let _ = child.kill(); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - } - - #[test] - fn test_nvidia_card_iommu_address_width() { - test_nvidia_card_iommu_address_width_common(false); - } + // Check and report any errors occurred during the live-migration + if r.is_err() { + print_and_panic( + src_child, + dest_child, + Some(ovs_child), + "Error occurred during live-migration", + ); + } - #[test] - fn test_iommufd_nvidia_card_iommu_address_width() { - test_nvidia_card_iommu_address_width_common(true); - } - - fn test_nvidia_guest_numa_generic_initiator_common(iommufd: bool) { - // Skip test if VFIO device is not available or not ready - if !std::path::Path::new(NVIDIA_VFIO_DEVICE).exists() { - println!("SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} not found"); - return; - } - - // Check if device is bound to vfio-pci driver - let driver_path = format!("{NVIDIA_VFIO_DEVICE}/driver"); - if let Ok(driver) = std::fs::read_link(&driver_path) { - let driver_name = driver.file_name().unwrap_or_default().to_string_lossy(); - if driver_name != "vfio-pci" { - println!( - "SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} bound to {driver_name}, not vfio-pci" - ); - return; - } - } else { - println!("SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} not bound to any driver"); - return; + // Check the source vm has been terminated successful (give it '3s' to settle) + thread::sleep(std::time::Duration::new(3, 0)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + print_and_panic( + src_child, + dest_child, + Some(ovs_child), + "source VM was not terminated successfully.", + ); } - let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let api_socket = temp_api_path(&guest.tmp_dir); - - // x86_64: Direct kernel boot - let mut child = GuestCommand::new(&guest) - .args(["--cpus", "boot=4"]) - .args(["--memory", "size=0"]) - .args(["--memory-zone", "id=mem0,size=1G", "id=mem1,size=1G"]) - .args([ - "--numa", - "guest_numa_id=0,cpus=[0-1],distances=[1@20,2@25],memory_zones=mem0", - "guest_numa_id=1,cpus=[2-3],distances=[0@20,2@30],memory_zones=mem1", - "guest_numa_id=2,device_id=vfio0,distances=[0@25,1@30]", - ]) - .args(["--platform", &platform_cfg(iommufd)]) - .args([ - "--device", - &format!("id=vfio0,path={NVIDIA_VFIO_DEVICE},iommu=on"), - ]) - .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(["--api-socket", &api_socket]) - .capture_output() - .default_disks() - .default_net() - .spawn() - .unwrap(); - + // Post live-migration check to make sure the destination VM is functional let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Verify NUMA topology is correct - guest.check_numa_common( - Some(&[960_000, 960_000]), - Some(&[&[0, 1], &[2, 3]]), - Some(&["10 20 25", "20 10 30", "25 30 10"]), - ); - - // Verify Generic Initiator support is present - // Linux kernel sets has_generic_initiator when it parses Type 5 SRAT entries - let has_gi = guest - .ssh_command( - "cat /sys/devices/system/node/has_generic_initiator 2>/dev/null || echo 0", - ) - .unwrap() - .trim() - .to_string(); - - assert_eq!( - has_gi, "2", - "Generic Initiator support should be detected by kernel" - ); - - // Verify SRAT table contains Generic Initiator entry (Type 5) - // We'll check that /sys/firmware/acpi/tables/SRAT exists and contains our entry - let srat_check = guest - .ssh_command( - "[ -f /sys/firmware/acpi/tables/SRAT ] && echo 'exists' || echo 'missing'", + // Perform same checks to validate VM has been properly migrated + // Spawn a new netcat listener in the OVS VM + let guest_ip = ovs_guest.network.guest_ip0.clone(); + thread::spawn(move || { + ssh_command_ip( + "nc -l 12345", + &guest_ip, + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT, ) - .unwrap() - .trim() - .to_string(); - - assert_eq!( - srat_check, "exists", - "SRAT table should exist in guest firmware" - ); + .unwrap(); + }); - // Use hexdump to verify Type 5 entry is present - // Type 5 (0x05) should appear in the SRAT table - let srat_has_type5 = guest - .ssh_command("sudo hexdump -C /sys/firmware/acpi/tables/SRAT | grep -q '05 20' && echo 'found' || echo 'not_found'") - .unwrap() - .trim() - .to_string(); + // Wait for the server to be listening + thread::sleep(std::time::Duration::new(5, 0)); - assert_eq!( - srat_has_type5, "found", - "SRAT table should contain Generic Initiator Affinity Structure (Type 5, Length 0x20/32)" - ); + // And check the connection is still functional after live-migration + migration_guest + .ssh_command("nc -vz 172.100.0.1 12345") + .unwrap(); }); - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); - } + // Clean-up the destination VM and OVS VM, and make sure they terminated correctly + let _ = dest_child.kill(); + let _ = ovs_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + let ovs_output = ovs_child.wait_with_output().unwrap(); - #[test] - fn test_nvidia_guest_numa_generic_initiator() { - test_nvidia_guest_numa_generic_initiator_common(false); - } + cleanup_ovs_dpdk(); - #[test] - fn test_iommufd_nvidia_guest_numa_generic_initiator() { - test_nvidia_guest_numa_generic_initiator_common(true); + handle_child_output(r, &dest_output); + handle_child_output(Ok(()), &ovs_output); } -} - -mod live_migration { - use std::num::NonZeroU32; - - use vmm::api::TimeoutStrategy; - - use crate::*; - // This test exercises the local live-migration between two Cloud Hypervisor VMs on the - // same host. It ensures the following behaviors: - // 1. The source VM is up and functional (including various virtio-devices are working properly); - // 2. The 'send-migration' and 'receive-migration' command finished successfully; - // 3. The source VM terminated gracefully after live migration; - // 4. The destination VM is functional (including various virtio-devices are working properly) after - // live migration; - // Note: This test does not use vsock as we can't create two identical vsock on the same host. - fn _test_live_migration(upgrade_test: bool, local: bool) { + #[cfg(not(feature = "mshv"))] + fn _test_live_migration_virtio_fs(local: bool) { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); let guest = Guest::new(Box::new(disk_config)); let kernel_path = direct_kernel_boot_path(); - let console_text = String::from("On a branch floating down river a cricket, singing."); - let net_id = "net123"; - let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.128", - net_id, guest.network.guest_mac0, guest.network.host_ip0 - ); - let memory_param: &[&str] = if local { - &["--memory", "size=1500M,shared=on"] - } else { - &["--memory", "size=1500M"] - }; + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + let mut shared_dir = workload_path; + shared_dir.push("shared_dir"); - let boot_vcpus = 2; - let max_vcpus = 4; + let (daemon_child, virtiofsd_socket_path) = + prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); - let pmem_temp_file = TempFile::new().unwrap(); - pmem_temp_file.as_file().set_len(128 << 20).unwrap(); - std::process::Command::new("mkfs.ext4") - .arg(pmem_temp_file.as_path()) - .output() - .expect("Expect creating disk image to succeed"); - let pmem_path = String::from("/dev/pmem0"); + let src_api_socket = temp_api_path(&guest.tmp_dir); // Start the source VM - let src_vm_path = if upgrade_test { - cloud_hypervisor_release_path() - } else { - clh_command("cloud-hypervisor") - }; - let src_api_socket = temp_api_path(&guest.tmp_dir); - let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); - src_vm_cmd - .args([ - "--cpus", - format!("boot={boot_vcpus},max={max_vcpus}").as_str(), - ]) - .args(memory_param) + let mut src_child = GuestCommand::new(&guest) + .args(["--api-socket", &src_api_socket]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=512M,shared=on"]) .args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() - .args(["--net", net_params.as_str()]) - .args(["--api-socket", &src_api_socket]) + .default_net() .args([ - "--pmem", - format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), - ]); - let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); + "--fs", + format!("socket={virtiofsd_socket_path},tag=myfs,num_queues=1,queue_size=1024") + .as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); // Start the destination VM let mut dest_api_socket = temp_api_path(&guest.tmp_dir); @@ -9235,43 +9248,56 @@ mod live_migration { .spawn() .unwrap(); + // Spawn a thread that waits for the old virtiofsd to exit then + // starts a replacement. During migration the source saves + // DEVICE_STATE then disconnects, causing virtiofsd to exit. + // The destination needs a fresh virtiofsd to load DEVICE_STATE. + // We remove the socket file first so the destination cannot + // accidentally connect to the old instance. + let virtiofsd_socket_clone = virtiofsd_socket_path.clone(); + let shared_dir_str = shared_dir.to_str().unwrap().to_string(); + let (restart_tx, restart_rx) = std::sync::mpsc::channel(); + let _monitor = thread::spawn(move || { + let mut child = daemon_child; + let _ = child.wait(); + let mut path = dirs::home_dir().unwrap(); + path.push("workloads"); + path.push("virtiofsd"); + let new_child = Command::new(path) + .args(["--shared-dir", &shared_dir_str]) + .args(["--socket-path", &virtiofsd_socket_clone]) + .args(["--cache", "never"]) + .args(["--tag", "myfs"]) + .spawn() + .unwrap(); + wait_for_virtiofsd_socket(&virtiofsd_socket_clone); + let _ = restart_tx.send(new_child); + }); + let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); - // Make sure the source VM is functional - // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + // Mount virtiofs and verify it works + guest + .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") + .unwrap(); - // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); - - // Check the guest virtio-devices, e.g. block, rng, console, and net - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); - - // x86_64: Following what's done in the `test_snapshot_restore`, we need - // to make sure that removing and adding back the virtio-net device does - // not break the live-migration support for virtio-pci. - #[cfg(target_arch = "x86_64")] - { - assert!(remote_command( - &src_api_socket, - "remove-device", - Some(net_id), - )); - assert!(wait_until(Duration::from_secs(10), || { - guest.wait_for_ssh(Duration::from_secs(1)).is_err() - })); + // Write a test file through virtiofs before migration + guest + .ssh_command( + "sudo bash -c 'echo pre_migration_data > mount_dir/migration_test_file'", + ) + .unwrap(); - // Plug the virtio-net device again - assert!(remote_command( - &src_api_socket, - "add-net", - Some(net_params.as_str()), - )); - guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); - } + // Verify the file is accessible + assert_eq!( + guest + .ssh_command("cat mount_dir/migration_test_file") + .unwrap() + .trim(), + "pre_migration_data" + ); - // Start the live-migration let migration_socket = String::from( guest .tmp_dir @@ -9281,6 +9307,11 @@ mod live_migration { .unwrap(), ); + // Remove the socket so the destination cannot connect to + // the old virtiofsd (which is still running). The source's + // existing connection uses an already-accepted fd. + let _ = std::fs::remove_file(&virtiofsd_socket_path); + assert!( start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), "Unsuccessful command: 'send-migration' or 'receive-migration'." @@ -9293,12 +9324,12 @@ mod live_migration { src_child, dest_child, None, - "Error occurred during live-migration", + "Error occurred during live-migration with virtio-fs", ); } - // Check the source vm has been terminated successful (give it '3s' to settle) - thread::sleep(std::time::Duration::new(3, 0)); + // Check the source vm has been terminated successfully (give it '3s' to settle) + thread::sleep(Duration::from_secs(3)); if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { print_and_panic( src_child, @@ -9308,1661 +9339,1602 @@ mod live_migration { ); } - // Post live-migration check to make sure the destination VM is functional + // Post live-migration checks let r = std::panic::catch_unwind(|| { - // Perform same checks to validate VM has been properly migrated - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); + // Verify virtiofs still works after migration + // Read the file written before migration + assert_eq!( + guest + .ssh_command("cat mount_dir/migration_test_file") + .unwrap() + .trim(), + "pre_migration_data" + ); - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + // Write a new file after migration + guest + .ssh_command( + "sudo bash -c 'echo post_migration_data > mount_dir/post_migration_file'", + ) + .unwrap(); + + // Verify the new file exists on the host + let post_content = + std::fs::read_to_string(shared_dir.join("post_migration_file")).unwrap(); + assert_eq!(post_content.trim(), "post_migration_data"); }); - // Clean-up the destination VM and make sure it terminated correctly + // Clean up let _ = dest_child.kill(); let dest_output = dest_child.wait_with_output().unwrap(); - handle_child_output(r, &dest_output); + if let Ok(mut new_daemon) = restart_rx.try_recv() { + let _ = new_daemon.kill(); + let _ = new_daemon.wait(); + } + let _ = std::fs::remove_file(shared_dir.join("migration_test_file")); + let _ = std::fs::remove_file(shared_dir.join("post_migration_file")); - // Check the destination VM has the expected 'console_text' from its output - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); - }); handle_child_output(r, &dest_output); } - fn _test_live_migration_balloon(upgrade_test: bool, local: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - let console_text = String::from("On a branch floating down river a cricket, singing."); - let net_id = "net123"; - let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.128", - net_id, guest.network.guest_mac0, guest.network.host_ip0 - ); - - let memory_param: &[&str] = if local { - &[ - "--memory", - "size=4G,hotplug_method=virtio-mem,hotplug_size=8G,shared=on", - "--balloon", - "size=0", - ] - } else { - &[ - "--memory", - "size=4G,hotplug_method=virtio-mem,hotplug_size=8G", - "--balloon", - "size=0", - ] - }; + // NUMA, balloon, and virtio-fs live migration tests run sequentially - let boot_vcpus = 2; - let max_vcpus = 4; + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_virtio_fs() { + _test_live_migration_virtio_fs(false); + } - let pmem_temp_file = TempFile::new().unwrap(); - pmem_temp_file.as_file().set_len(128 << 20).unwrap(); - std::process::Command::new("mkfs.ext4") - .arg(pmem_temp_file.as_path()) - .output() - .expect("Expect creating disk image to succeed"); - let pmem_path = String::from("/dev/pmem0"); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_virtio_fs_local() { + _test_live_migration_virtio_fs(true); + } - // Start the source VM - let src_vm_path = if upgrade_test { - cloud_hypervisor_release_path() - } else { - clh_command("cloud-hypervisor") - }; - let src_api_socket = temp_api_path(&guest.tmp_dir); - let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); - src_vm_cmd - .args([ - "--cpus", - format!("boot={boot_vcpus},max={max_vcpus}").as_str(), - ]) - .args(memory_param) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", net_params.as_str()]) - .args(["--api-socket", &src_api_socket]) - .args([ - "--pmem", - format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), - ]); - let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_balloon() { + _test_live_migration_balloon(false, false); + } - // Start the destination VM - let mut dest_api_socket = temp_api_path(&guest.tmp_dir); - dest_api_socket.push_str(".dest"); - let mut dest_child = GuestCommand::new(&guest) - .args(["--api-socket", &dest_api_socket]) - .capture_output() - .spawn() - .unwrap(); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_balloon_local() { + _test_live_migration_balloon(false, true); + } - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_upgrade_balloon() { + _test_live_migration_balloon(true, false); + } - // Make sure the source VM is functional - // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_upgrade_balloon_local() { + _test_live_migration_balloon(true, true); + } - // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); - // Increase the guest RAM - resize_command(&src_api_socket, None, Some(6 << 30), None, None); - assert!(wait_until(Duration::from_secs(30), || { - guest.get_total_memory().unwrap_or_default() > 5_760_000 - })); - assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); - // Use balloon to remove RAM from the VM - resize_command(&src_api_socket, None, None, Some(1 << 30), None); - assert!(wait_until(Duration::from_secs(5), || { - let total_memory = guest.get_total_memory().unwrap_or_default(); - total_memory > 4_800_000 && total_memory < 5_760_000 - })); - let total_memory = guest.get_total_memory().unwrap_or_default(); - assert!(total_memory > 4_800_000); - assert!(total_memory < 5_760_000); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_numa() { + _test_live_migration_numa(false, false); + } - // Check the guest virtio-devices, e.g. block, rng, console, and net - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_numa_local() { + _test_live_migration_numa(false, true); + } - // x86_64: Following what's done in the `test_snapshot_restore`, we need - // to make sure that removing and adding back the virtio-net device does - // not break the live-migration support for virtio-pci. - #[cfg(target_arch = "x86_64")] - { - assert!(remote_command( - &src_api_socket, - "remove-device", - Some(net_id), - )); - assert!(wait_until(Duration::from_secs(10), || { - guest.wait_for_ssh(Duration::from_secs(1)).is_err() - })); + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_upgrade_numa() { + _test_live_migration_numa(true, false); + } - // Plug the virtio-net device again - assert!(remote_command( - &src_api_socket, - "add-net", - Some(net_params.as_str()), - )); - guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); - } + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_upgrade_numa_local() { + _test_live_migration_numa(true, true); + } - // Start the live-migration - let migration_socket = String::from( - guest - .tmp_dir - .as_path() - .join("live-migration.sock") - .to_str() - .unwrap(), - ); + // Require to run ovs-dpdk tests sequentially because they rely on the same ovs-dpdk setup + #[test] + #[ignore = "See #5532"] + #[cfg(target_arch = "x86_64")] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_ovs_dpdk() { + _test_live_migration_ovs_dpdk(false, false); + } - assert!( - start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), - "Unsuccessful command: 'send-migration' or 'receive-migration'." - ); - }); + #[test] + #[ignore = "See #5532 and #7689"] + #[cfg(target_arch = "x86_64")] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_ovs_dpdk_local() { + _test_live_migration_ovs_dpdk(false, true); + } - // Check and report any errors occurred during the live-migration - if r.is_err() { - print_and_panic( - src_child, - dest_child, - None, - "Error occurred during live-migration", - ); - } - - // Check the source vm has been terminated successful (give it '3s' to settle) - thread::sleep(std::time::Duration::new(3, 0)); - if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - print_and_panic( - src_child, - dest_child, - None, - "source VM was not terminated successfully.", - ); - } + #[test] + #[ignore = "See #5532"] + #[cfg(target_arch = "x86_64")] + #[cfg(not(feature = "mshv"))] + fn test_live_upgrade_ovs_dpdk() { + _test_live_migration_ovs_dpdk(true, false); + } - // Post live-migration check to make sure the destination VM is functional - let r = std::panic::catch_unwind(|| { - // Perform same checks to validate VM has been properly migrated - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + #[test] + #[ignore = "See #5532"] + #[cfg(target_arch = "x86_64")] + #[cfg(not(feature = "mshv"))] + fn test_live_upgrade_ovs_dpdk_local() { + _test_live_migration_ovs_dpdk(true, true); + } +} - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); +mod windows { + use std::sync::LazyLock; - // Perform checks on guest RAM using balloon - let total_memory = guest.get_total_memory().unwrap_or_default(); - assert!(total_memory > 4_800_000); - assert!(total_memory < 5_760_000); - // Deflate balloon to restore entire RAM to the VM - resize_command(&dest_api_socket, None, None, Some(0), None); - thread::sleep(std::time::Duration::new(5, 0)); - assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); - // Decrease guest RAM with virtio-mem - resize_command(&dest_api_socket, None, Some(5 << 30), None, None); - thread::sleep(std::time::Duration::new(5, 0)); - let total_memory = guest.get_total_memory().unwrap_or_default(); - assert!(total_memory > 4_800_000); - assert!(total_memory < 5_760_000); - }); + use crate::*; - // Clean-up the destination VM and make sure it terminated correctly - let _ = dest_child.kill(); - let dest_output = dest_child.wait_with_output().unwrap(); - handle_child_output(r, &dest_output); + static NEXT_DISK_ID: LazyLock> = LazyLock::new(|| Mutex::new(1)); - // Check the destination VM has the expected 'console_text' from its output - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); - }); - handle_child_output(r, &dest_output); + struct WindowsGuest { + guest: Guest, + auth: PasswordAuth, } - fn _test_live_migration_numa(upgrade_test: bool, local: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - let console_text = String::from("On a branch floating down river a cricket, singing."); - let net_id = "net123"; - let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.128", - net_id, guest.network.guest_mac0, guest.network.host_ip0 - ); + trait FsType { + const FS_FAT: u8; + const FS_NTFS: u8; + } + impl FsType for WindowsGuest { + const FS_FAT: u8 = 0; + const FS_NTFS: u8 = 1; + } - let memory_param: &[&str] = if local { - &[ - "--memory", - "size=0,hotplug_method=virtio-mem,shared=on", - "--memory-zone", - "id=mem0,size=1G,hotplug_size=4G,shared=on", - "id=mem1,size=1G,hotplug_size=4G,shared=on", - "id=mem2,size=2G,hotplug_size=4G,shared=on", - "--numa", - "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0", - "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1", - "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2", - ] - } else { - &[ - "--memory", - "size=0,hotplug_method=virtio-mem", - "--memory-zone", - "id=mem0,size=1G,hotplug_size=4G", - "id=mem1,size=1G,hotplug_size=4G", - "id=mem2,size=2G,hotplug_size=4G", - "--numa", - "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0", - "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1", - "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2", - ] - }; + impl WindowsGuest { + fn new() -> Self { + let disk = WindowsDiskConfig::new(WINDOWS_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk)); + let auth = PasswordAuth { + username: String::from("administrator"), + password: String::from("Admin123"), + }; - let boot_vcpus = 6; - let max_vcpus = 12; + WindowsGuest { guest, auth } + } - let pmem_temp_file = TempFile::new().unwrap(); - pmem_temp_file.as_file().set_len(128 << 20).unwrap(); - std::process::Command::new("mkfs.ext4") - .arg(pmem_temp_file.as_path()) - .output() - .expect("Expect creating disk image to succeed"); - let pmem_path = String::from("/dev/pmem0"); + fn guest(&self) -> &Guest { + &self.guest + } - // Start the source VM - let src_vm_path = if upgrade_test { - cloud_hypervisor_release_path() - } else { - clh_command("cloud-hypervisor") - }; - let src_api_socket = temp_api_path(&guest.tmp_dir); - let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); - src_vm_cmd - .args([ - "--cpus", - format!("boot={boot_vcpus},max={max_vcpus}").as_str(), - ]) - .args(memory_param) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", net_params.as_str()]) - .args(["--api-socket", &src_api_socket]) - .args([ - "--pmem", - format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), - ]); - let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); + fn ssh_cmd(&self, cmd: &str) -> String { + ssh_command_ip_with_auth_retry( + cmd, + &self.auth, + &self.guest.network.guest_ip0, + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT, + ) + .unwrap() + } - // Start the destination VM - let mut dest_api_socket = temp_api_path(&guest.tmp_dir); - dest_api_socket.push_str(".dest"); - let mut dest_child = GuestCommand::new(&guest) - .args(["--api-socket", &dest_api_socket]) - .capture_output() - .spawn() - .unwrap(); + fn cpu_count(&self) -> u8 { + self.ssh_cmd("powershell -Command \"(Get-CimInstance win32_computersystem).NumberOfLogicalProcessors\"") + .trim() + .parse::() + .unwrap_or(0) + } - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); + fn ram_size(&self) -> usize { + self.ssh_cmd("powershell -Command \"(Get-CimInstance win32_computersystem).TotalPhysicalMemory\"") + .trim() + .parse::() + .unwrap_or(0) + } - // Make sure the source VM is functional - // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + fn netdev_count(&self) -> u8 { + self.ssh_cmd("powershell -Command \"netsh int ipv4 show interfaces | Select-String ethernet | Measure-Object -Line | Format-Table -HideTableHeaders\"") + .trim() + .parse::() + .unwrap_or(0) + } - // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 2_880_000); + fn disk_count(&self) -> u8 { + self.ssh_cmd("powershell -Command \"Get-Disk | Measure-Object -Line | Format-Table -HideTableHeaders\"") + .trim() + .parse::() + .unwrap_or(0) + } - // Check the guest virtio-devices, e.g. block, rng, console, and net - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + fn reboot(&self) { + let _ = self.ssh_cmd("shutdown /r /t 0"); + } - // Check the NUMA parameters are applied correctly and resize - // each zone to test the case where we migrate a VM with the - // virtio-mem regions being used. - { - guest.check_numa_common( - Some(&[960_000, 960_000, 1_920_000]), - Some(&[&[0, 1, 2], &[3, 4], &[5]]), - Some(&["10 15 20", "20 10 25", "25 30 10"]), - ); + fn shutdown(&self) { + let _ = self.ssh_cmd("shutdown /s /t 0"); + } - // AArch64 currently does not support hotplug, and therefore we only - // test hotplug-related function on x86_64 here. - #[cfg(target_arch = "x86_64")] - { - guest.enable_memory_hotplug(); + fn run_dnsmasq(&self) -> std::process::Child { + let listen_address = format!("--listen-address={}", self.guest.network.host_ip0); + let dhcp_host = format!( + "--dhcp-host={},{}", + self.guest.network.guest_mac0, self.guest.network.guest_ip0 + ); + let dhcp_range = format!( + "--dhcp-range=eth,{},{}", + self.guest.network.guest_ip0, self.guest.network.guest_ip0 + ); - // Resize every memory zone and check each associated NUMA node - // has been assigned the right amount of memory. - resize_zone_command(&src_api_socket, "mem0", "2G"); - resize_zone_command(&src_api_socket, "mem1", "2G"); - resize_zone_command(&src_api_socket, "mem2", "3G"); - thread::sleep(std::time::Duration::new(5, 0)); + Command::new("dnsmasq") + .arg("--no-daemon") + .arg("--log-queries") + .arg(listen_address.as_str()) + .arg("--except-interface=lo") + .arg("--bind-dynamic") // Allow listening to host_ip while the interface is not ready yet. + .arg("--conf-file=/dev/null") + .arg(dhcp_host.as_str()) + .arg(dhcp_range.as_str()) + .spawn() + .unwrap() + } - guest.check_numa_common(Some(&[1_920_000, 1_920_000, 1_920_000]), None, None); - } - } + // TODO Cleanup image file explicitly after test, if there's some space issues. + fn disk_new(&self, fs: u8, sz: usize) -> String { + let mut guard = NEXT_DISK_ID.lock().unwrap(); + let id = *guard; + *guard = id + 1; - // x86_64: Following what's done in the `test_snapshot_restore`, we need - // to make sure that removing and adding back the virtio-net device does - // not break the live-migration support for virtio-pci. - #[cfg(target_arch = "x86_64")] - { - assert!(remote_command( - &src_api_socket, - "remove-device", - Some(net_id), - )); - assert!(wait_until(Duration::from_secs(10), || { - guest.wait_for_ssh(Duration::from_secs(1)).is_err() - })); + let img = PathBuf::from(format!("/tmp/test-hotplug-{id}.raw")); + let _ = fs::remove_file(&img); - // Plug the virtio-net device again - assert!(remote_command( - &src_api_socket, - "add-net", - Some(net_params.as_str()), - )); - guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); - } + // Create an image file + let out = Command::new("qemu-img") + .args([ + "create", + "-f", + "raw", + img.to_str().unwrap(), + format!("{sz}m").as_str(), + ]) + .output() + .expect("qemu-img command failed") + .stdout; + println!("{out:?}"); - // Start the live-migration - let migration_socket = String::from( - guest - .tmp_dir - .as_path() - .join("live-migration.sock") - .to_str() - .unwrap(), - ); + // Associate image to a loop device + let out = Command::new("losetup") + .args(["--show", "-f", img.to_str().unwrap()]) + .output() + .expect("failed to create loop device") + .stdout; + let _tmp = String::from_utf8_lossy(&out); + let loop_dev = _tmp.trim(); + println!("{out:?}"); - assert!( - start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), - "Unsuccessful command: 'send-migration' or 'receive-migration'." - ); - }); + // Create a partition table + // echo 'type=7' | sudo sfdisk "${LOOP}" + let mut child = Command::new("sfdisk") + .args([loop_dev]) + .stdin(Stdio::piped()) + .spawn() + .unwrap(); + let stdin = child.stdin.as_mut().expect("failed to open stdin"); + stdin + .write_all("type=7".as_bytes()) + .expect("failed to write stdin"); + let out = child.wait_with_output().expect("sfdisk failed").stdout; + println!("{out:?}"); - // Check and report any errors occurred during the live-migration - if r.is_err() { - print_and_panic( - src_child, - dest_child, - None, - "Error occurred during live-migration", - ); - } + // Disengage the loop device + let out = Command::new("losetup") + .args(["-d", loop_dev]) + .output() + .expect("loop device not found") + .stdout; + println!("{out:?}"); - // Check the source vm has been terminated successful (give it '3s' to settle) - thread::sleep(std::time::Duration::new(3, 0)); - if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - print_and_panic( - src_child, - dest_child, - None, - "source VM was not terminated successfully.", - ); - } + // Re-associate loop device pointing to the partition only + let out = Command::new("losetup") + .args([ + "--show", + "--offset", + (512 * 2048).to_string().as_str(), + "-f", + img.to_str().unwrap(), + ]) + .output() + .expect("failed to create loop device") + .stdout; + let _tmp = String::from_utf8_lossy(&out); + let loop_dev = _tmp.trim(); + println!("{out:?}"); - // Post live-migration check to make sure the destination VM is functional - let r = std::panic::catch_unwind(|| { - // Perform same checks to validate VM has been properly migrated - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - #[cfg(target_arch = "x86_64")] - assert!(guest.get_total_memory().unwrap_or_default() > 6_720_000); - #[cfg(target_arch = "aarch64")] - assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); + // Create filesystem. + let fs_cmd = match fs { + WindowsGuest::FS_FAT => "mkfs.msdos", + WindowsGuest::FS_NTFS => "mkfs.ntfs", + _ => panic!("Unknown filesystem type '{fs}'"), + }; + let out = Command::new(fs_cmd) + .args([&loop_dev]) + .output() + .unwrap_or_else(|_| panic!("{fs_cmd} failed")) + .stdout; + println!("{out:?}"); - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + // Disengage the loop device + let out = Command::new("losetup") + .args(["-d", loop_dev]) + .output() + .unwrap_or_else(|_| panic!("loop device '{loop_dev}' not found")) + .stdout; + println!("{out:?}"); - // Perform NUMA related checks - { - #[cfg(target_arch = "aarch64")] - { - guest.check_numa_common( - Some(&[960_000, 960_000, 1_920_000]), - Some(&[&[0, 1, 2], &[3, 4], &[5]]), - Some(&["10 15 20", "20 10 25", "25 30 10"]), - ); - } + img.to_str().unwrap().to_string() + } - // AArch64 currently does not support hotplug, and therefore we only - // test hotplug-related function on x86_64 here. - #[cfg(target_arch = "x86_64")] - { - guest.check_numa_common( - Some(&[1_920_000, 1_920_000, 2_880_000]), - Some(&[&[0, 1, 2], &[3, 4], &[5]]), - Some(&["10 15 20", "20 10 25", "25 30 10"]), - ); + fn disks_set_rw(&self) { + let _ = self.ssh_cmd("powershell -Command \"Get-Disk | Where-Object IsOffline -eq $True | Set-Disk -IsReadOnly $False\""); + } - guest.enable_memory_hotplug(); + fn disks_online(&self) { + let _ = self.ssh_cmd("powershell -Command \"Get-Disk | Where-Object IsOffline -eq $True | Set-Disk -IsOffline $False\""); + } - // Resize every memory zone and check each associated NUMA node - // has been assigned the right amount of memory. - resize_zone_command(&dest_api_socket, "mem0", "4G"); - resize_zone_command(&dest_api_socket, "mem1", "4G"); - resize_zone_command(&dest_api_socket, "mem2", "4G"); - // Resize to the maximum amount of CPUs and check each NUMA - // node has been assigned the right CPUs set. - resize_command(&dest_api_socket, Some(max_vcpus), None, None, None); - thread::sleep(std::time::Duration::new(5, 0)); + fn disk_file_put(&self, fname: &str, data: &str) { + let _ = self.ssh_cmd(&format!( + "powershell -Command \"'{data}' | Set-Content -Path {fname}\"" + )); + } - guest.check_numa_common( - Some(&[3_840_000, 3_840_000, 3_840_000]), - Some(&[&[0, 1, 2, 9], &[3, 4, 6, 7, 8], &[5, 10, 11]]), - None, - ); - } - } - }); + fn disk_file_read(&self, fname: &str) -> String { + self.ssh_cmd(&format!( + "powershell -Command \"Get-Content -Path {fname}\"" + )) + } - // Clean-up the destination VM and make sure it terminated correctly - let _ = dest_child.kill(); - let dest_output = dest_child.wait_with_output().unwrap(); - handle_child_output(r, &dest_output); + fn wait_for_boot(&self) -> Result<(), WaitForSshError> { + let out = wait_for_ssh( + "dir /b c:\\ | find \"Windows\"", + &self.auth, + &self.guest.network.guest_ip0, + Duration::from_secs(180), + )?; - // Check the destination VM has the expected 'console_text' from its output - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); - }); - handle_child_output(r, &dest_output); + if out.trim() == "Windows" { + Ok(()) + } else { + panic!("Unexpected Windows boot probe output: {:?}", out.trim()); + } + } } - fn _test_live_migration_watchdog(upgrade_test: bool, local: bool) { - let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - let console_text = String::from("On a branch floating down river a cricket, singing."); - let net_id = "net123"; - let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.128", - net_id, guest.network.guest_mac0, guest.network.host_ip0 - ); - - let memory_param: &[&str] = if local { - &["--memory", "size=1500M,shared=on"] - } else { - &["--memory", "size=1500M"] - }; - - let boot_vcpus = 2; - let max_vcpus = 4; - - let pmem_temp_file = TempFile::new().unwrap(); - pmem_temp_file.as_file().set_len(128 << 20).unwrap(); - std::process::Command::new("mkfs.ext4") - .arg(pmem_temp_file.as_path()) + fn vcpu_threads_count(pid: u32) -> u8 { + // ps -T -p 12345 | grep vcpu | wc -l + let out = Command::new("ps") + .args(["-T", "-p", format!("{pid}").as_str()]) .output() - .expect("Expect creating disk image to succeed"); - let pmem_path = String::from("/dev/pmem0"); - - // Start the source VM - let src_vm_path = if upgrade_test { - cloud_hypervisor_release_path() - } else { - clh_command("cloud-hypervisor") - }; - let src_api_socket = temp_api_path(&guest.tmp_dir); - let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); - src_vm_cmd - .args([ - "--cpus", - format!("boot={boot_vcpus},max={max_vcpus}").as_str(), - ]) - .args(memory_param) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", net_params.as_str()]) - .args(["--api-socket", &src_api_socket]) - .args([ - "--pmem", - format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(), - ]) - .args(["--watchdog"]); - let mut src_child = src_vm_cmd.capture_output().spawn().unwrap(); + .expect("ps command failed") + .stdout; + String::from_utf8_lossy(&out).matches("vcpu").count() as u8 + } - // Start the destination VM - let mut dest_api_socket = temp_api_path(&guest.tmp_dir); - dest_api_socket.push_str(".dest"); - let mut dest_child = GuestCommand::new(&guest) - .args(["--api-socket", &dest_api_socket]) + fn netdev_ctrl_threads_count(pid: u32) -> u8 { + // ps -T -p 12345 | grep "_net[0-9]*_ctrl" | wc -l + let out = Command::new("ps") + .args(["-T", "-p", format!("{pid}").as_str()]) + .output() + .expect("ps command failed") + .stdout; + let mut n = 0; + String::from_utf8_lossy(&out) + .split_whitespace() + .for_each(|s| n += (s.starts_with("_net") && s.ends_with("_ctrl")) as u8); // _net1_ctrl + n + } + + fn disk_ctrl_threads_count(pid: u32) -> u8 { + // ps -T -p 15782 | grep "_disk[0-9]*_q0" | wc -l + let out = Command::new("ps") + .args(["-T", "-p", format!("{pid}").as_str()]) + .output() + .expect("ps command failed") + .stdout; + let mut n = 0; + String::from_utf8_lossy(&out) + .split_whitespace() + .for_each(|s| n += (s.starts_with("_disk") && s.ends_with("_q0")) as u8); // _disk0_q0, don't care about multiple queues as they're related to the same hdd + n + } + + #[test] + fn test_windows_guest() { + let windows_guest = WindowsGuest::new(); + + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--cpus", "boot=2,kvm_hyperv=on"]) + .args(["--memory", "size=4G"]) + .args(["--kernel", edk2_path().to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) + .default_disks() + .default_net() .capture_output() .spawn() .unwrap(); - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); + let fd = child.stdout.as_ref().unwrap().as_raw_fd(); + let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; + let fd = child.stderr.as_ref().unwrap().as_raw_fd(); + let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - // Make sure the source VM is functional - // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); - // Check the guest virtio-devices, e.g. block, rng, console, and net - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); - // x86_64: Following what's done in the `test_snapshot_restore`, we need - // to make sure that removing and adding back the virtio-net device does - // not break the live-migration support for virtio-pci. - #[cfg(target_arch = "x86_64")] - { - assert!(remote_command( - &src_api_socket, - "remove-device", - Some(net_id), - )); - assert!(wait_until(Duration::from_secs(10), || { - guest.wait_for_ssh(Duration::from_secs(1)).is_err() - })); + assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE); - // Plug the virtio-net device again - assert!(remote_command( - &src_api_socket, - "add-net", - Some(net_params.as_str()), - )); - guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); - } + let mut child_dnsmasq = windows_guest.run_dnsmasq(); - // Enable watchdog and ensure its functional - let expected_reboot_count = 1; - // Enable the watchdog with a 15s timeout - enable_guest_watchdog(&guest, 15); + let r = std::panic::catch_unwind(|| { + // Wait to make sure Windows boots up + windows_guest.wait_for_boot().unwrap(); - assert_eq!(get_reboot_count(&guest), expected_reboot_count); - assert_eq!( - guest - .ssh_command("sudo journalctl | grep -c -- \"Watchdog started\"") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 1 - ); - // Allow some normal time to elapse to check we don't get spurious reboots - thread::sleep(std::time::Duration::new(40, 0)); - // Check no reboot - assert_eq!(get_reboot_count(&guest), expected_reboot_count); + windows_guest.shutdown(); + }); - // Start the live-migration - let migration_socket = String::from( - guest - .tmp_dir - .as_path() - .join("live-migration.sock") - .to_str() - .unwrap(), - ); + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - assert!( - start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), - "Unsuccessful command: 'send-migration' or 'receive-migration'." - ); - }); + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); - // Check and report any errors occurred during the live-migration - if r.is_err() { - print_and_panic( - src_child, - dest_child, - None, - "Error occurred during live-migration", - ); - } + handle_child_output(r, &output); + } - // Check the source vm has been terminated successful (give it '3s' to settle) - thread::sleep(std::time::Duration::new(3, 0)); - if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - print_and_panic( - src_child, - dest_child, - None, - "source VM was not terminated successfully.", - ); - } + #[test] + fn test_windows_guest_multiple_queues() { + let windows_guest = WindowsGuest::new(); - // Post live-migration check to make sure the destination VM is functional - let r = std::panic::catch_unwind(|| { - // Perform same checks to validate VM has been properly migrated - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); + let mut ovmf_path = dirs::home_dir().unwrap(); + ovmf_path.push("workloads"); + ovmf_path.push(OVMF_NAME); - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--cpus", "boot=4,kvm_hyperv=on"]) + .args(["--memory", "size=4G"]) + .args(["--kernel", ovmf_path.to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) + .args([ + "--disk", + format!( + "path={},num_queues=4", + windows_guest + .guest() + .disk_config + .disk(DiskType::OperatingSystem) + .unwrap() + ) + .as_str(), + ]) + .args([ + "--net", + format!( + "tap=,mac={},ip={},mask=255.255.255.128,num_queues=8", + windows_guest.guest().network.guest_mac0, + windows_guest.guest().network.host_ip0 + ) + .as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); - // Perform checks on watchdog - let mut expected_reboot_count = 1; + let fd = child.stdout.as_ref().unwrap().as_raw_fd(); + let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; + let fd = child.stderr.as_ref().unwrap().as_raw_fd(); + let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - // Allow some normal time to elapse to check we don't get spurious reboots - thread::sleep(std::time::Duration::new(40, 0)); - // Check no reboot - assert_eq!(get_reboot_count(&guest), expected_reboot_count); + assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE); - // Trigger a panic (sync first). We need to do this inside a screen with a delay so the SSH command returns. - guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap(); - // Allow some time for the watchdog to trigger (max 30s) and reboot to happen - guest.wait_vm_boot_custom_timeout(50).unwrap(); - // Check a reboot is triggered by the watchdog - expected_reboot_count += 1; - assert_eq!(get_reboot_count(&guest), expected_reboot_count); + let mut child_dnsmasq = windows_guest.run_dnsmasq(); - #[cfg(target_arch = "x86_64")] - { - // Now pause the VM and remain offline for 30s - assert!(remote_command(&dest_api_socket, "pause", None)); - thread::sleep(std::time::Duration::new(30, 0)); - assert!(remote_command(&dest_api_socket, "resume", None)); + let r = std::panic::catch_unwind(|| { + // Wait to make sure Windows boots up + windows_guest.wait_for_boot().unwrap(); - // Check no reboot - assert_eq!(get_reboot_count(&guest), expected_reboot_count); - } + windows_guest.shutdown(); }); - // Clean-up the destination VM and make sure it terminated correctly - let _ = dest_child.kill(); - let dest_output = dest_child.wait_with_output().unwrap(); - handle_child_output(r, &dest_output); + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - // Check the destination VM has the expected 'console_text' from its output - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); - }); - handle_child_output(r, &dest_output); + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); + + handle_child_output(r, &output); } - fn _test_live_migration_ovs_dpdk(upgrade_test: bool, local: bool) { - let ovs_disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let ovs_guest = Guest::new(Box::new(ovs_disk_config)); + #[test] + #[cfg(not(feature = "mshv"))] + #[cfg_attr(target_arch = "aarch64", ignore = "See #4327")] + fn test_windows_guest_snapshot_restore() { + let windows_guest = WindowsGuest::new(); - let migration_disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let migration_guest = Guest::new(Box::new(migration_disk_config)); - let src_api_socket = temp_api_path(&migration_guest.tmp_dir); + let mut ovmf_path = dirs::home_dir().unwrap(); + ovmf_path.push("workloads"); + ovmf_path.push(OVMF_NAME); - // Start two VMs that are connected through ovs-dpdk and one of the VMs is the source VM for live-migration - let (mut ovs_child, mut src_child) = - setup_ovs_dpdk_guests(&ovs_guest, &migration_guest, &src_api_socket, upgrade_test); + let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); + let api_socket_source = format!("{}.1", temp_api_path(&tmp_dir)); - // Start the destination VM - let mut dest_api_socket = temp_api_path(&migration_guest.tmp_dir); - dest_api_socket.push_str(".dest"); - let mut dest_child = GuestCommand::new(&migration_guest) - .args(["--api-socket", &dest_api_socket]) + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--api-socket", &api_socket_source]) + .args(["--cpus", "boot=2,kvm_hyperv=on"]) + .args(["--memory", "size=4G"]) + .args(["--kernel", ovmf_path.to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) + .default_disks() + .default_net() .capture_output() .spawn() .unwrap(); - let r = std::panic::catch_unwind(|| { - // Give it '1s' to make sure the 'dest_api_socket' file is properly created - thread::sleep(std::time::Duration::new(1, 0)); + let fd = child.stdout.as_ref().unwrap().as_raw_fd(); + let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; + let fd = child.stderr.as_ref().unwrap().as_raw_fd(); + let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - // Start the live-migration - let migration_socket = String::from( - migration_guest - .tmp_dir - .as_path() - .join("live-migration.sock") - .to_str() - .unwrap(), - ); + assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE); - assert!( - start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), - "Unsuccessful command: 'send-migration' or 'receive-migration'." - ); - }); + let mut child_dnsmasq = windows_guest.run_dnsmasq(); - // Check and report any errors occurred during the live-migration - if r.is_err() { - print_and_panic( - src_child, - dest_child, - Some(ovs_child), - "Error occurred during live-migration", - ); - } + // Wait to make sure Windows boots up + windows_guest.wait_for_boot().unwrap(); - // Check the source vm has been terminated successful (give it '3s' to settle) - thread::sleep(std::time::Duration::new(3, 0)); - if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - print_and_panic( - src_child, - dest_child, - Some(ovs_child), - "source VM was not terminated successfully.", - ); - } + let snapshot_dir = temp_snapshot_dir_path(&tmp_dir); - // Post live-migration check to make sure the destination VM is functional - let r = std::panic::catch_unwind(|| { - // Perform same checks to validate VM has been properly migrated - // Spawn a new netcat listener in the OVS VM - let guest_ip = ovs_guest.network.guest_ip0.clone(); - thread::spawn(move || { - ssh_command_ip( - "nc -l 12345", - &guest_ip, - DEFAULT_SSH_RETRIES, - DEFAULT_SSH_TIMEOUT, - ) - .unwrap(); - }); + // Pause the VM + assert!(remote_command(&api_socket_source, "pause", None)); - // Wait for the server to be listening - thread::sleep(std::time::Duration::new(5, 0)); + // Take a snapshot from the VM + assert!(remote_command( + &api_socket_source, + "snapshot", + Some(format!("file://{snapshot_dir}").as_str()), + )); - // And check the connection is still functional after live-migration - migration_guest - .ssh_command("nc -vz 172.100.0.1 12345") - .unwrap(); + let snapshot_state_path = std::path::Path::new(&snapshot_dir).join("state.json"); + let snapshot_config_path = std::path::Path::new(&snapshot_dir).join("config.json"); + assert!(wait_until(Duration::from_secs(30), || { + snapshot_state_path.exists() && snapshot_config_path.exists() + })); + + let _ = child.kill(); + child.wait().unwrap(); + + let api_socket_restored = format!("{}.2", temp_api_path(&tmp_dir)); + + // Restore the VM from the snapshot + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--api-socket", &api_socket_restored]) + .args([ + "--restore", + format!("source_url=file://{snapshot_dir}").as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + // Wait for the VM to be restored + assert!(wait_until(Duration::from_secs(30), || { + remote_command(&api_socket_restored, "info", None) + })); + + let r = std::panic::catch_unwind(|| { + // Resume the VM + assert!(wait_until(Duration::from_secs(30), || remote_command( + &api_socket_restored, + "info", + None + ))); + assert!(remote_command(&api_socket_restored, "resume", None)); + + windows_guest.shutdown(); }); - // Clean-up the destination VM and OVS VM, and make sure they terminated correctly - let _ = dest_child.kill(); - let _ = ovs_child.kill(); - let dest_output = dest_child.wait_with_output().unwrap(); - let ovs_output = ovs_child.wait_with_output().unwrap(); + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - cleanup_ovs_dpdk(); + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); - handle_child_output(r, &dest_output); - handle_child_output(Ok(()), &ovs_output); + handle_child_output(r, &output); } - // This test exercises the local live-migration between two Cloud Hypervisor VMs on the - // same host with Landlock enabled on both VMs. The test validates the following: - // 1. The source VM is up and functional - // 2. Ensure Landlock is enabled on source VM by hotplugging a disk. As the path for this - // disk is not known to the source VM this step will fail. - // 3. The 'send-migration' and 'receive-migration' command finished successfully; - // 4. The source VM terminated gracefully after live migration; - // 5. The destination VM is functional after live migration; - // 6. Ensure Landlock is enabled on destination VM by hotplugging a disk. As the path for - // this disk is not known to the destination VM this step will fail. - fn _test_live_migration_with_landlock() { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - let net_id = "net123"; - let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.128", - net_id, guest.network.guest_mac0, guest.network.host_ip0 - ); + #[test] + #[cfg(not(feature = "mshv"))] + #[cfg(not(target_arch = "aarch64"))] + fn test_windows_guest_cpu_hotplug() { + let windows_guest = WindowsGuest::new(); - let boot_vcpus = 2; - let max_vcpus = 4; + let mut ovmf_path = dirs::home_dir().unwrap(); + ovmf_path.push("workloads"); + ovmf_path.push(OVMF_NAME); - let mut blk_file_path = dirs::home_dir().unwrap(); - blk_file_path.push("workloads"); - blk_file_path.push("blk.img"); + let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); + let api_socket = temp_api_path(&tmp_dir); - let src_api_socket = temp_api_path(&guest.tmp_dir); - let mut src_child = GuestCommand::new(&guest) - .args([ - "--cpus", - format!("boot={boot_vcpus},max={max_vcpus}").as_str(), - ]) - .args(["--memory", "size=1500M,shared=on"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--api-socket", &api_socket]) + .args(["--cpus", "boot=2,max=8,kvm_hyperv=on"]) + .args(["--memory", "size=4G"]) + .args(["--kernel", ovmf_path.to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) .default_disks() - .args(["--api-socket", &src_api_socket]) - .args(["--landlock"]) - .args(["--net", net_params.as_str()]) - .args([ - "--landlock-rules", - format!("path={:?},access=rw", guest.tmp_dir.as_path()).as_str(), - ]) + .default_net() .capture_output() .spawn() .unwrap(); - // Start the destination VM - let mut dest_api_socket = temp_api_path(&guest.tmp_dir); - dest_api_socket.push_str(".dest"); - let mut dest_child = GuestCommand::new(&guest) - .args(["--api-socket", &dest_api_socket]) - .capture_output() - .spawn() - .unwrap(); + let mut child_dnsmasq = windows_guest.run_dnsmasq(); let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); + // Wait to make sure Windows boots up + windows_guest.wait_for_boot().unwrap(); - // Make sure the source VM is functaionl - // Check the number of vCPUs - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + let vcpu_num = 2; + // Check the initial number of CPUs the guest sees + assert_eq!(windows_guest.cpu_count(), vcpu_num); + // Check the initial number of vcpu threads in the CH process + assert_eq!(vcpu_threads_count(child.id()), vcpu_num); - // Check the guest RAM - assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); + let vcpu_num = 6; + // Hotplug some CPUs + resize_command(&api_socket, Some(vcpu_num), None, None, None); + // Wait for Windows to report the hotplugged CPUs. + assert!(wait_until(Duration::from_secs(10), || windows_guest + .cpu_count() + == vcpu_num)); + // Check the guest sees the correct number + assert_eq!(windows_guest.cpu_count(), vcpu_num); + // Check the CH process has the correct number of vcpu threads + assert_eq!(vcpu_threads_count(child.id()), vcpu_num); - // Check Landlock is enabled by hot-plugging a disk. - assert!(!remote_command( - &src_api_socket, - "add-disk", - Some(format!("path={},id=test0", blk_file_path.to_str().unwrap()).as_str()), - )); + let vcpu_num = 4; + // Remove some CPUs. Note that Windows doesn't support hot-remove. + resize_command(&api_socket, Some(vcpu_num), None, None, None); + thread::sleep(std::time::Duration::new(10, 0)); - // Start the live-migration - let migration_socket = String::from( - guest - .tmp_dir - .as_path() - .join("live-migration.sock") - .to_str() - .unwrap(), - ); + // Reboot to let Windows catch up + windows_guest.reboot(); + // Wait for Windows to come back after the reboot. + windows_guest.wait_for_boot().unwrap(); + // Wait for Windows to reflect the unplugged CPU count. + assert!(wait_until(Duration::from_secs(60), || windows_guest + .cpu_count() + == vcpu_num)); + // Check the guest sees the correct number + assert_eq!(windows_guest.cpu_count(), vcpu_num); + // Check the CH process has the correct number of vcpu threads + assert_eq!(vcpu_threads_count(child.id()), vcpu_num); - assert!( - start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, true), - "Unsuccessful command: 'send-migration' or 'receive-migration'." - ); + windows_guest.shutdown(); }); - // Check and report any errors occurred during the live-migration - if r.is_err() { - print_and_panic( - src_child, - dest_child, - None, - "Error occurred during live-migration", - ); - } + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - // Check the source vm has been terminated successful (give it '3s' to settle) - thread::sleep(std::time::Duration::new(3, 0)); - if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - print_and_panic( - src_child, - dest_child, - None, - "source VM was not terminated successfully.", - ); - } + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); + + handle_child_output(r, &output); + } + + #[test] + #[cfg(not(feature = "mshv"))] + #[cfg(not(target_arch = "aarch64"))] + fn test_windows_guest_ram_hotplug() { + let windows_guest = WindowsGuest::new(); + + let mut ovmf_path = dirs::home_dir().unwrap(); + ovmf_path.push("workloads"); + ovmf_path.push(OVMF_NAME); + + let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); + let api_socket = temp_api_path(&tmp_dir); + + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--api-socket", &api_socket]) + .args(["--cpus", "boot=2,kvm_hyperv=on"]) + .args(["--memory", "size=2G,hotplug_size=5G"]) + .args(["--kernel", ovmf_path.to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let mut child_dnsmasq = windows_guest.run_dnsmasq(); - // Post live-migration check to make sure the destination VM is functioning let r = std::panic::catch_unwind(|| { - // Perform same checks to validate VM has been properly migrated - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); + // Wait to make sure Windows boots up + windows_guest.wait_for_boot().unwrap(); + + let ram_size = 2 * 1024 * 1024 * 1024; + // Check the initial number of RAM the guest sees + let current_ram_size = windows_guest.ram_size(); + // This size seems to be reserved by the system and thus the + // reported amount differs by this constant value. + let reserved_ram_size = ram_size - current_ram_size; + // Verify that there's not more than 4mb constant diff wasted + // by the reserved ram. + assert!(reserved_ram_size < 4 * 1024 * 1024); + + let ram_size = 4 * 1024 * 1024 * 1024; + // Hotplug some RAM + resize_command(&api_socket, None, Some(ram_size), None, None); + // Wait for Windows to report the hotplugged memory. + assert!(wait_until(Duration::from_secs(10), || windows_guest + .ram_size() + == ram_size - reserved_ram_size)); + + let ram_size = 3 * 1024 * 1024 * 1024; + // Unplug some RAM. Note that hot-remove most likely won't work. + resize_command(&api_socket, None, Some(ram_size), None, None); + // Reboot to let Windows catch up + windows_guest.reboot(); + // Wait for Windows to come back after the reboot. + windows_guest.wait_for_boot().unwrap(); + // Wait for Windows to reflect the unplugged RAM amount. + assert!(wait_until(Duration::from_secs(60), || windows_guest + .ram_size() + == ram_size - reserved_ram_size)); + // Check the guest sees the correct number + assert_eq!(windows_guest.ram_size(), ram_size - reserved_ram_size); + + windows_guest.shutdown(); }); - // Check Landlock is enabled on destination VM by hot-plugging a disk. - assert!(!remote_command( - &dest_api_socket, - "add-disk", - Some(format!("path={},id=test0", blk_file_path.to_str().unwrap()).as_str()), - )); + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - // Clean-up the destination VM and make sure it terminated correctly - let _ = dest_child.kill(); - let dest_output = dest_child.wait_with_output().unwrap(); - handle_child_output(r, &dest_output); - } + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); - // Function to get an available port - fn get_available_port() -> u16 { - TcpListener::bind("127.0.0.1:0") - .expect("Failed to bind to address") - .local_addr() - .unwrap() - .port() + handle_child_output(r, &output); } - fn start_live_migration_tcp( - src_api_socket: &str, - dest_api_socket: &str, - connections: NonZeroU32, - ) -> bool { - // Get an available TCP port - let migration_port = get_available_port(); - let host_ip = "127.0.0.1"; + #[test] + #[cfg(not(feature = "mshv"))] + fn test_windows_guest_netdev_hotplug() { + let windows_guest = WindowsGuest::new(); - // Start the 'receive-migration' command on the destination - let mut receive_migration = Command::new(clh_command("ch-remote")) - .args([ - &format!("--api-socket={dest_api_socket}"), - "receive-migration", - &format!("tcp:0.0.0.0:{migration_port}"), - ]) - .stdin(Stdio::null()) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); + let mut ovmf_path = dirs::home_dir().unwrap(); + ovmf_path.push("workloads"); + ovmf_path.push(OVMF_NAME); - // Give the destination some time to start listening - thread::sleep(Duration::from_secs(1)); + let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); + let api_socket = temp_api_path(&tmp_dir); - // Start the 'send-migration' command on the source - let connections = connections.get(); - let mut send_migration = Command::new(clh_command("ch-remote")) - .args([ - &format!("--api-socket={src_api_socket}"), - "send-migration", - &format!( - "destination_url=tcp:{host_ip}:{migration_port},connections={connections}" - ), - ]) - .stdin(Stdio::null()) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--api-socket", &api_socket]) + .args(["--cpus", "boot=2,kvm_hyperv=on"]) + .args(["--memory", "size=4G"]) + .args(["--kernel", ovmf_path.to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) + .default_disks() + .default_net() + .capture_output() .spawn() .unwrap(); - // Check if the 'send-migration' command executed successfully - let send_success = if let Some(status) = send_migration - .wait_timeout(Duration::from_secs(60)) - .unwrap() - { - status.success() - } else { - false - }; + let mut child_dnsmasq = windows_guest.run_dnsmasq(); - if !send_success { - let _ = send_migration.kill(); - let output = send_migration.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'send_migration' output ====\n\n---stdout---\n{}\n\n---stderr---\n{}\n\n==== End 'send_migration' output ====\n\n", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - } + let r = std::panic::catch_unwind(|| { + // Wait to make sure Windows boots up + windows_guest.wait_for_boot().unwrap(); - // Check if the 'receive-migration' command executed successfully - let receive_success = if let Some(status) = receive_migration - .wait_timeout(Duration::from_secs(60)) - .unwrap() - { - status.success() - } else { - false - }; + // Initially present network device + let netdev_num = 1; + assert_eq!(windows_guest.netdev_count(), netdev_num); + assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num); - if !receive_success { - let _ = receive_migration.kill(); - let output = receive_migration.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'receive_migration' output ====\n\n---stdout---\n{}\n\n---stderr---\n{}\n\n==== End 'receive_migration' output ====\n\n", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) + // Hotplug network device + let (cmd_success, cmd_output, _) = remote_command_w_output( + &api_socket, + "add-net", + Some(windows_guest.guest().default_net_string().as_str()), ); - } + assert!(cmd_success); + assert!(String::from_utf8_lossy(&cmd_output).contains("\"id\":\"_net2\"")); + // Wait for Windows to enumerate the added network device. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .netdev_count() + == 2 + && netdev_ctrl_threads_count(child.id()) == 2)); + // Verify the device is on the system + let netdev_num = 2; + assert_eq!(windows_guest.netdev_count(), netdev_num); + assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num); - send_success && receive_success + // Remove network device + let cmd_success = remote_command(&api_socket, "remove-device", Some("_net2")); + assert!(cmd_success); + // Wait for Windows to drop the removed network device. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .netdev_count() + == 1 + && netdev_ctrl_threads_count(child.id()) == 1)); + // Verify the device has been removed + let netdev_num = 1; + assert_eq!(windows_guest.netdev_count(), netdev_num); + assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num); + + windows_guest.shutdown(); + }); + + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); + + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); + + handle_child_output(r, &output); } - fn _test_live_migration_tcp(connections: NonZeroU32) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - let console_text = String::from("On a branch floating down river a cricket, singing."); - let net_id = "net123"; - let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.128", - net_id, guest.network.guest_mac0, guest.network.host_ip0 - ); - let memory_param: &[&str] = &["--memory", "size=1500M,shared=on"]; - let boot_vcpus = 2; - let max_vcpus = 4; - let pmem_temp_file = TempFile::new().unwrap(); - pmem_temp_file.as_file().set_len(128 << 20).unwrap(); - std::process::Command::new("mkfs.ext4") - .arg(pmem_temp_file.as_path()) - .output() - .expect("Expect creating disk image to succeed"); - let pmem_path = String::from("/dev/pmem0"); + #[test] + #[ignore = "See #6037"] + #[cfg(not(feature = "mshv"))] + #[cfg(not(target_arch = "aarch64"))] + fn test_windows_guest_disk_hotplug() { + let windows_guest = WindowsGuest::new(); - // Start the source VM - let src_vm_path = clh_command("cloud-hypervisor"); - let src_api_socket = temp_api_path(&guest.tmp_dir); - let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); - src_vm_cmd - .args([ - "--cpus", - format!("boot={boot_vcpus},max={max_vcpus}").as_str(), - ]) - .args(memory_param) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + let mut ovmf_path = dirs::home_dir().unwrap(); + ovmf_path.push("workloads"); + ovmf_path.push(OVMF_NAME); + + let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); + let api_socket = temp_api_path(&tmp_dir); + + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--api-socket", &api_socket]) + .args(["--cpus", "boot=2,kvm_hyperv=on"]) + .args(["--memory", "size=4G"]) + .args(["--kernel", ovmf_path.to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) .default_disks() - .args(["--net", net_params.as_str()]) - .args(["--api-socket", &src_api_socket]) - .args([ - "--pmem", - format!( - "file={},discard_writes=on", - pmem_temp_file.as_path().to_str().unwrap(), - ) - .as_str(), - ]) - .capture_output(); - let mut src_child = src_vm_cmd.spawn().unwrap(); + .default_net() + .capture_output() + .spawn() + .unwrap(); - // Start the destination VM - let mut dest_api_socket = temp_api_path(&guest.tmp_dir); - dest_api_socket.push_str(".dest"); - let mut dest_child = GuestCommand::new(&guest) - .args(["--api-socket", &dest_api_socket]) + let mut child_dnsmasq = windows_guest.run_dnsmasq(); + + let disk = windows_guest.disk_new(WindowsGuest::FS_FAT, 100); + + let r = std::panic::catch_unwind(|| { + // Wait to make sure Windows boots up + windows_guest.wait_for_boot().unwrap(); + + // Initially present disk device + let disk_num = 1; + assert_eq!(windows_guest.disk_count(), disk_num); + assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + + // Hotplug disk device + let (cmd_success, cmd_output, _) = remote_command_w_output( + &api_socket, + "add-disk", + Some(format!("path={disk},readonly=off").as_str()), + ); + assert!(cmd_success); + assert!(String::from_utf8_lossy(&cmd_output).contains("\"id\":\"_disk2\"")); + // Online disk device + windows_guest.disks_set_rw(); + windows_guest.disks_online(); + // Wait for Windows to enumerate the added disk. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .disk_count() + == 2 + && disk_ctrl_threads_count(child.id()) == 2)); + // Verify the device is on the system + let disk_num = 2; + assert_eq!(windows_guest.disk_count(), disk_num); + assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + + let data = "hello"; + let fname = "d:\\world"; + windows_guest.disk_file_put(fname, data); + + // Unmount disk device + let cmd_success = remote_command(&api_socket, "remove-device", Some("_disk2")); + assert!(cmd_success); + // Wait for Windows to drop the removed disk. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .disk_count() + == 1 + && disk_ctrl_threads_count(child.id()) == 1)); + // Verify the device has been removed + let disk_num = 1; + assert_eq!(windows_guest.disk_count(), disk_num); + assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + + // Remount and check the file exists with the expected contents + let (cmd_success, _cmd_output, _) = remote_command_w_output( + &api_socket, + "add-disk", + Some(format!("path={disk},readonly=off").as_str()), + ); + assert!(cmd_success); + // Wait for Windows to mount the re-added disk again. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .disk_file_read(fname) + .trim() + == data)); + let out = windows_guest.disk_file_read(fname); + assert_eq!(data, out.trim()); + + // Intentionally no unmount, it'll happen at shutdown. + + windows_guest.shutdown(); + }); + + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); + + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); + + handle_child_output(r, &output); + } + + #[test] + #[ignore = "See #6037"] + #[cfg(not(feature = "mshv"))] + #[cfg(not(target_arch = "aarch64"))] + fn test_windows_guest_disk_hotplug_multi() { + let windows_guest = WindowsGuest::new(); + + let mut ovmf_path = dirs::home_dir().unwrap(); + ovmf_path.push("workloads"); + ovmf_path.push(OVMF_NAME); + + let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); + let api_socket = temp_api_path(&tmp_dir); + + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--api-socket", &api_socket]) + .args(["--cpus", "boot=2,kvm_hyperv=on"]) + .args(["--memory", "size=2G"]) + .args(["--kernel", ovmf_path.to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) + .default_disks() + .default_net() .capture_output() .spawn() .unwrap(); + let mut child_dnsmasq = windows_guest.run_dnsmasq(); + + // Predefined data to used at various test stages + let disk_test_data: [[String; 4]; 2] = [ + [ + "_disk2".to_string(), + windows_guest.disk_new(WindowsGuest::FS_FAT, 123), + "d:\\world".to_string(), + "hello".to_string(), + ], + [ + "_disk3".to_string(), + windows_guest.disk_new(WindowsGuest::FS_NTFS, 333), + "e:\\hello".to_string(), + "world".to_string(), + ], + ]; + let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - // Ensure the source VM is running normally - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + // Wait to make sure Windows boots up + windows_guest.wait_for_boot().unwrap(); - // On x86_64 architecture, remove and re-add the virtio-net device - #[cfg(target_arch = "x86_64")] - { - assert!(remote_command( - &src_api_socket, - "remove-device", - Some(net_id), - )); - assert!(wait_until(Duration::from_secs(10), || { - guest.wait_for_ssh(Duration::from_secs(1)).is_err() + // Initially present disk device + let disk_num = 1; + assert_eq!(windows_guest.disk_count(), disk_num); + assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + + for it in &disk_test_data { + let disk_id = it[0].as_str(); + let disk = it[1].as_str(); + + let expected_disk_num = windows_guest.disk_count() + 1; + let expected_ctrl_threads = disk_ctrl_threads_count(child.id()) + 1; + + // Hotplug disk device + let (cmd_success, cmd_output, _) = remote_command_w_output( + &api_socket, + "add-disk", + Some(format!("path={disk},readonly=off").as_str()), + ); + assert!(cmd_success); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains(format!("\"id\":\"{disk_id}\"").as_str()) + ); + + // Wait for disk to appear + assert!(wait_until(Duration::from_secs(5), || { + windows_guest.disk_count() == expected_disk_num + && disk_ctrl_threads_count(child.id()) == expected_ctrl_threads })); - // Re-add the virtio-net device - assert!(remote_command( - &src_api_socket, - "add-net", - Some(net_params.as_str()), - )); - guest.wait_for_ssh(Duration::from_secs(10)).unwrap(); + + // Online disk devices + windows_guest.disks_set_rw(); + windows_guest.disks_online(); + } + // Verify the devices are on the system + let disk_num = (disk_test_data.len() + 1) as u8; + assert_eq!(windows_guest.disk_count(), disk_num); + assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + + // Put test data + for it in &disk_test_data { + let fname = it[2].as_str(); + let data = it[3].as_str(); + windows_guest.disk_file_put(fname, data); + } + + // Unmount disk devices + for it in &disk_test_data { + let disk_id = it[0].as_str(); + let cmd_success = remote_command(&api_socket, "remove-device", Some(disk_id)); + assert!(cmd_success); + } + + // Wait for Windows to drop all removed disks. + assert!(wait_until(Duration::from_secs(5), || windows_guest + .disk_count() + == 1 + && disk_ctrl_threads_count(child.id()) == 1)); + // Verify the devices have been removed + let disk_num = 1; + assert_eq!(windows_guest.disk_count(), disk_num); + assert_eq!(disk_ctrl_threads_count(child.id()), disk_num); + + // Remount + for it in &disk_test_data { + let disk = it[1].as_str(); + let (cmd_success, _cmd_output, _) = remote_command_w_output( + &api_socket, + "add-disk", + Some(format!("path={disk},readonly=off").as_str()), + ); + assert!(cmd_success); } - // Start TCP live migration - assert!( - start_live_migration_tcp(&src_api_socket, &dest_api_socket, connections), - "Unsuccessful command: 'send-migration' or 'receive-migration'." - ); - }); - // Check and report any errors that occurred during live migration - if r.is_err() { - print_and_panic( - src_child, - dest_child, - None, - "Error occurred during live-migration", - ); - } + // Wait for Windows to enumerate the re-added disks. + assert!(wait_until(Duration::from_secs(5), || { + windows_guest.disk_count() == 4 && disk_ctrl_threads_count(child.id()) == 4 + })); + // Check the files exists with the expected contents + for it in &disk_test_data { + let fname = it[2].as_str(); + let data = it[3].as_str(); + let out = windows_guest.disk_file_read(fname); + assert_eq!(data, out.trim()); + } - // Check the source vm has been terminated successful (give it '3s' to settle) - thread::sleep(std::time::Duration::new(3, 0)); - if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - print_and_panic( - src_child, - dest_child, - None, - "Source VM was not terminated successfully.", - ); - } + // Intentionally no unmount, it'll happen at shutdown. - // After live migration, ensure the destination VM is running normally - let r = std::panic::catch_unwind(|| { - // Perform the same checks to ensure the VM has migrated correctly - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - assert!(guest.get_total_memory().unwrap_or_default() > 1_400_000); - guest.check_devices_common(None, Some(&console_text), Some(&pmem_path)); + windows_guest.shutdown(); }); - // Clean up the destination VM and ensure it terminates properly - let _ = dest_child.kill(); - let dest_output = dest_child.wait_with_output().unwrap(); - handle_child_output(r, &dest_output); + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - // Check if the expected `console_text` is present in the destination VM's output - let r = std::panic::catch_unwind(|| { - assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text)); - }); - handle_child_output(r, &dest_output); + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); + + handle_child_output(r, &output); } - fn _test_live_migration_tcp_timeout(timeout_strategy: TimeoutStrategy) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - let net_id = "net1337"; - let net_params = format!( - "id={},tap=,mac={},ip={},mask=255.255.255.128", - net_id, guest.network.guest_mac0, guest.network.host_ip0 - ); - let memory_param: &[&str] = &["--memory", "size=1500M,shared=on"]; - let boot_vcpus = 2; + #[test] + #[cfg(not(feature = "mshv"))] + #[cfg(not(target_arch = "aarch64"))] + fn test_windows_guest_netdev_multi() { + let windows_guest = WindowsGuest::new(); - let src_vm_path = clh_command("cloud-hypervisor"); - let src_api_socket = temp_api_path(&guest.tmp_dir); - let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path); - src_vm_cmd - .args(["--cpus", format!("boot={boot_vcpus}").as_str()]) - .args(memory_param) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .args(["--net", net_params.as_str()]) - .args(["--api-socket", &src_api_socket]) - .capture_output(); - let mut src_child = src_vm_cmd.spawn().unwrap(); + let mut ovmf_path = dirs::home_dir().unwrap(); + ovmf_path.push("workloads"); + ovmf_path.push(OVMF_NAME); - let mut dest_api_socket = temp_api_path(&guest.tmp_dir); - dest_api_socket.push_str(".dest"); - let mut dest_child = GuestCommand::new(&guest) - .args(["--api-socket", &dest_api_socket]) + let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); + let api_socket = temp_api_path(&tmp_dir); + + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--api-socket", &api_socket]) + .args(["--cpus", "boot=2,kvm_hyperv=on"]) + .args(["--memory", "size=4G"]) + .args(["--kernel", ovmf_path.to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) + .default_disks() + // The multi net dev config is borrowed from test_multiple_network_interfaces + .args([ + "--net", + windows_guest.guest().default_net_string().as_str(), + "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.0", + "tap=mytap42,mac=fe:1f:9e:e1:60:f2,ip=192.168.4.1,mask=255.255.255.0", + ]) .capture_output() .spawn() .unwrap(); - let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - guest.wait_vm_boot().unwrap(); + let mut child_dnsmasq = windows_guest.run_dnsmasq(); - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); + let r = std::panic::catch_unwind(|| { + // Wait to make sure Windows boots up + windows_guest.wait_for_boot().unwrap(); - // Start a memory stressor in the background to keep pages dirty, - // ensuring the precopy loop cannot converge within the 1s timeout. - guest - .ssh_command("nohup stress --vm 2 --vm-bytes 220M --vm-keep &>/dev/null &") - .unwrap(); - // Give stress a moment to actually start dirtying memory - thread::sleep(Duration::from_secs(3)); + let netdev_num = 3; + assert_eq!(windows_guest.netdev_count(), netdev_num); + assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num); - let migration_port = get_available_port(); - let host_ip = "127.0.0.1"; + let tap_count = exec_host_command_output("ip link | grep -c mytap42"); + assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1"); - let mut receive_migration = Command::new(clh_command("ch-remote")) - .args([ - &format!("--api-socket={dest_api_socket}"), - "receive-migration", - &format!("tcp:0.0.0.0:{migration_port}"), - ]) - .stdin(Stdio::null()) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); + windows_guest.shutdown(); + }); - thread::sleep(Duration::from_secs(1)); + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - // Use a tight downtime budget (1ms) combined with a 1s timeout so the - // migration practically cannot converge regardless of strategy. - let mut send_migration = Command::new(clh_command("ch-remote")) - .args([ - &format!("--api-socket={src_api_socket}"), - "send-migration", - &format!( - "destination_url=tcp:{host_ip}:{migration_port},downtime_ms=1,timeout_s=1,timeout_strategy={timeout_strategy:?}" - ), - ]) - .stdin(Stdio::null()) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); - let send_status = send_migration - .wait_timeout(Duration::from_secs(60)) - .unwrap(); - let receive_status = receive_migration - .wait_timeout(Duration::from_secs(60)) - .unwrap(); + handle_child_output(r, &output); + } - // Clean up receive-migration regardless of its outcome - if receive_status.is_none() { - let _ = receive_migration.kill(); - } + #[test] + fn test_windows_guest_qcow2_backing_direct() { + let windows_guest = WindowsGuest::new(); - // Kill the stressor now that migration has completed or aborted, - // to reduce system load during post-migration checks. - let _ = guest.ssh_command("pkill -f 'stress --vm'"); + let qcow2_path = windows_guest.guest().disk_config.qcow2_disk().unwrap(); - match timeout_strategy { - TimeoutStrategy::Cancel => { - // With cancel strategy the send must fail and the source VM - // must keep running. - let send_failed = match send_status { - Some(status) => !status.success(), - None => { - let _ = send_migration.kill(); - false - } - }; - assert!( - send_failed, - "send-migration should have failed due to 1s timeout with cancel strategy" - ); + let mut child = GuestCommand::new(windows_guest.guest()) + .args(["--cpus", "boot=2,kvm_hyperv=on"]) + .args(["--memory", "size=4G"]) + .args(["--kernel", edk2_path().to_str().unwrap()]) + .args(["--serial", "tty"]) + .args(["--console", "off"]) + .args([ + "--disk", + format!("path={qcow2_path},image_type=qcow2,backing_files=on,direct=on").as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); - thread::sleep(Duration::from_secs(2)); - assert!( - src_child.try_wait().unwrap().is_none(), - "Source VM should still be running after a cancelled migration" - ); + let fd = child.stdout.as_ref().unwrap().as_raw_fd(); + let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; + let fd = child.stderr.as_ref().unwrap().as_raw_fd(); + let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) }; - // Confirm the source VM is still responsive over SSH - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - } - TimeoutStrategy::Ignore => { - // With Ignore strategy the send must succeed despite the timeout - // being reached, and the source VM must have terminated. - let send_succeeded = match send_status { - Some(status) => status.success(), - None => { - let _ = send_migration.kill(); - false - } - }; - assert!( - send_succeeded, - "send-migration should have succeeded with timeout_strategy=ignore" - ); + assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE); - thread::sleep(Duration::from_secs(3)); - assert!( - src_child.try_wait().unwrap().is_some(), - "Source VM should have terminated after a forced migration" - ); + let mut child_dnsmasq = windows_guest.run_dnsmasq(); + + let r = std::panic::catch_unwind(|| { + windows_guest.wait_for_boot().unwrap(); - // Confirm the VM is still responsive over SSH on the new host - assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus); - } + // Write and read back files through qcow2 + direct I/O. + for i in 0..5 { + let fname = format!("c:\\test-dio-{i}.bin"); + let fname2 = format!("c:\\test-dio-{i}-copy.bin"); + let size = (i + 1) * 4 * 1024 * 1024; + windows_guest.ssh_cmd(&format!( + "powershell -Command \"\ + $r = New-Object byte[] {size}; \ + (New-Object Random {i}).NextBytes($r); \ + [IO.File]::WriteAllBytes('{fname}', $r)\"" + )); + let hash_write = windows_guest.ssh_cmd(&format!( + "powershell -Command \"(Get-FileHash '{fname}' -Algorithm SHA256).Hash\"" + )); + windows_guest.ssh_cmd(&format!("copy {fname} {fname2}")); + let hash_read = windows_guest.ssh_cmd(&format!( + "powershell -Command \"(Get-FileHash '{fname2}' -Algorithm SHA256).Hash\"" + )); + assert_eq!(hash_write.trim(), hash_read.trim()); } - })); - let _ = src_child.kill(); - let src_output = src_child.wait_with_output().unwrap(); - let _ = dest_child.kill(); - let _dest_output = dest_child.wait_with_output().unwrap(); + windows_guest.shutdown(); + }); - handle_child_output(r, &src_output); - } + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - fn _test_live_migration_virtio_fs(local: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); + let _ = child_dnsmasq.kill(); + let _ = child_dnsmasq.wait(); - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - let mut shared_dir = workload_path; - shared_dir.push("shared_dir"); + handle_child_output(r, &output); + } +} - let (daemon_child, virtiofsd_socket_path) = - prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); +#[cfg(target_arch = "x86_64")] +mod vfio { + use crate::*; + const NVIDIA_VFIO_DEVICE: &str = "/sys/bus/pci/devices/0002:00:01.0"; - let src_api_socket = temp_api_path(&guest.tmp_dir); + fn platform_cfg(iommufd: bool) -> String { + if iommufd { + "iommufd=on,vfio_p2p_dma=off".to_string() + } else { + "iommufd=off".to_string() + } + } - // Start the source VM - let mut src_child = GuestCommand::new(&guest) - .args(["--api-socket", &src_api_socket]) - .args(["--cpus", "boot=2"]) - .args(["--memory", "size=512M,shared=on"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() + fn test_nvidia_card_memory_hotplug(hotplug_method: &str, iommufd: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) .args([ - "--fs", - format!("socket={virtiofsd_socket_path},tag=myfs,num_queues=1,queue_size=1024") - .as_str(), + "--memory", + format!("size=4G,hotplug_size=4G,hotplug_method={hotplug_method}").as_str(), ]) + .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) + .args(["--platform", &platform_cfg(iommufd)]) + .args(["--device", format!("path={NVIDIA_VFIO_DEVICE}").as_str()]) + .args(["--api-socket", &api_socket]) + .default_disks() + .default_net() .capture_output() .spawn() .unwrap(); - // Start the destination VM - let mut dest_api_socket = temp_api_path(&guest.tmp_dir); - dest_api_socket.push_str(".dest"); - let mut dest_child = GuestCommand::new(&guest) - .args(["--api-socket", &dest_api_socket]) - .capture_output() - .spawn() - .unwrap(); - - // Spawn a thread that waits for the old virtiofsd to exit then - // starts a replacement. During migration the source saves - // DEVICE_STATE then disconnects, causing virtiofsd to exit. - // The destination needs a fresh virtiofsd to load DEVICE_STATE. - // We remove the socket file first so the destination cannot - // accidentally connect to the old instance. - let virtiofsd_socket_clone = virtiofsd_socket_path.clone(); - let shared_dir_str = shared_dir.to_str().unwrap().to_string(); - let (restart_tx, restart_rx) = std::sync::mpsc::channel(); - let _monitor = thread::spawn(move || { - let mut child = daemon_child; - let _ = child.wait(); - let mut path = dirs::home_dir().unwrap(); - path.push("workloads"); - path.push("virtiofsd"); - let new_child = Command::new(path) - .args(["--shared-dir", &shared_dir_str]) - .args(["--socket-path", &virtiofsd_socket_clone]) - .args(["--cache", "never"]) - .args(["--tag", "myfs"]) - .spawn() - .unwrap(); - wait_for_virtiofsd_socket(&virtiofsd_socket_clone); - let _ = restart_tx.send(new_child); - }); - let r = std::panic::catch_unwind(|| { guest.wait_vm_boot().unwrap(); - // Mount virtiofs and verify it works - guest - .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") - .unwrap(); - - // Write a test file through virtiofs before migration - guest - .ssh_command( - "sudo bash -c 'echo pre_migration_data > mount_dir/migration_test_file'", - ) - .unwrap(); + assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000); - // Verify the file is accessible - assert_eq!( - guest - .ssh_command("cat mount_dir/migration_test_file") - .unwrap() - .trim(), - "pre_migration_data" - ); + // Verify the VFIO device works before memory hotplug + guest.check_nvidia_gpu(); - let migration_socket = String::from( - guest - .tmp_dir - .as_path() - .join("live-migration.sock") - .to_str() - .unwrap(), - ); + guest.enable_memory_hotplug(); - // Remove the socket so the destination cannot connect to - // the old virtiofsd (which is still running). The source's - // existing connection uses an already-accepted fd. - let _ = std::fs::remove_file(&virtiofsd_socket_path); + // Add RAM to the VM + let desired_ram = 6 << 30; + resize_command(&api_socket, None, Some(desired_ram), None, None); + assert!(wait_until(Duration::from_secs(5), || { + guest.get_total_memory().unwrap_or_default() > 5_760_000 + })); + assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000); - assert!( - start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), - "Unsuccessful command: 'send-migration' or 'receive-migration'." - ); + // Check the VFIO device works when RAM is increased to 6GiB. + // After guest memory hotplug, the VMM must refresh VFIO/iommufd DMA + // mappings for the passthrough GPU. + assert!(wait_until(Duration::from_secs(10), || guest.check_nvidia_gpu())); }); - // Check and report any errors occurred during the live-migration - if r.is_err() { - print_and_panic( - src_child, - dest_child, - None, - "Error occurred during live-migration with virtio-fs", - ); - } + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - // Check the source vm has been terminated successfully (give it '3s' to settle) - thread::sleep(Duration::from_secs(3)); - if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - print_and_panic( - src_child, - dest_child, - None, - "source VM was not terminated successfully.", - ); - } + handle_child_output(r, &output); + } + + #[test] + fn test_nvidia_card_memory_hotplug_acpi() { + test_nvidia_card_memory_hotplug("acpi", false); + } + + #[test] + fn test_nvidia_card_memory_hotplug_virtio_mem() { + test_nvidia_card_memory_hotplug("virtio-mem", false); + } + + #[test] + fn test_iommufd_nvidia_card_memory_hotplug_acpi() { + test_nvidia_card_memory_hotplug("acpi", true); + } + + #[test] + fn test_iommufd_nvidia_card_memory_hotplug_virtio_mem() { + test_nvidia_card_memory_hotplug("virtio-mem", true); + } + + fn test_nvidia_card_pci_hotplug_common(iommufd: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) + .args(["--memory", "size=1G"]) + .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) + .args(["--platform", &platform_cfg(iommufd)]) + .args(["--api-socket", &api_socket]) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); - // Post live-migration checks let r = std::panic::catch_unwind(|| { - // Verify virtiofs still works after migration - // Read the file written before migration - assert_eq!( - guest - .ssh_command("cat mount_dir/migration_test_file") - .unwrap() - .trim(), - "pre_migration_data" + guest.wait_vm_boot().unwrap(); + + // Hotplug the card to the VM + let (cmd_success, cmd_output, _) = remote_command_w_output( + &api_socket, + "add-device", + Some(format!("id=vfio0,path={NVIDIA_VFIO_DEVICE}").as_str()), + ); + assert!(cmd_success); + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"vfio0\",\"bdf\":\"0000:00:06.0\"}") ); - // Write a new file after migration - guest - .ssh_command( - "sudo bash -c 'echo post_migration_data > mount_dir/post_migration_file'", - ) - .unwrap(); - - // Verify the new file exists on the host - let post_content = - std::fs::read_to_string(shared_dir.join("post_migration_file")).unwrap(); - assert_eq!(post_content.trim(), "post_migration_data"); + // Check the VFIO device works after hotplug + assert!(wait_until(Duration::from_secs(10), || guest.check_nvidia_gpu())); }); - // Clean up - let _ = dest_child.kill(); - let dest_output = dest_child.wait_with_output().unwrap(); - if let Ok(mut new_daemon) = restart_rx.try_recv() { - let _ = new_daemon.kill(); - let _ = new_daemon.wait(); - } - let _ = std::fs::remove_file(shared_dir.join("migration_test_file")); - let _ = std::fs::remove_file(shared_dir.join("post_migration_file")); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - handle_child_output(r, &dest_output); + handle_child_output(r, &output); } - mod live_migration_parallel { - use vmm::api::TimeoutStrategy; + #[test] + fn test_nvidia_card_pci_hotplug() { + test_nvidia_card_pci_hotplug_common(false); + } - use super::*; - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_basic() { - _test_live_migration(false, false); - } + #[test] + fn test_iommufd_nvidia_card_pci_hotplug() { + test_nvidia_card_pci_hotplug_common(true); + } - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_local() { - _test_live_migration(false, true); - } + fn test_nvidia_card_reboot_common(iommufd: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_tcp() { - _test_live_migration_tcp(NonZeroU32::new(1).unwrap()); - } + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) + .args(["--memory", "size=1G"]) + .args(["--platform", &platform_cfg(iommufd)]) + .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) + .args([ + "--device", + format!("path={NVIDIA_VFIO_DEVICE},iommu=on").as_str(), + ]) + .args(["--api-socket", &api_socket]) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_tcp_parallel_connections() { - _test_live_migration_tcp(NonZeroU32::new(8).unwrap()); - } + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_tcp_timeout_cancel() { - _test_live_migration_tcp_timeout(TimeoutStrategy::Cancel); - } + // Check the VFIO device works after boot + assert!(guest.check_nvidia_gpu()); - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_tcp_timeout_ignore() { - _test_live_migration_tcp_timeout(TimeoutStrategy::Ignore); - } + guest.reboot_linux(0); - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_watchdog() { - _test_live_migration_watchdog(false, false); - } + // Check the VFIO device works after reboot + assert!(guest.check_nvidia_gpu()); + }); - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_watchdog_local() { - _test_live_migration_watchdog(false, true); - } + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_upgrade_basic() { - _test_live_migration(true, false); - } + handle_child_output(r, &output); + } - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_upgrade_local() { - _test_live_migration(true, true); - } + #[test] + fn test_nvidia_card_reboot() { + test_nvidia_card_reboot_common(false); + } - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_upgrade_watchdog() { - _test_live_migration_watchdog(true, false); - } + #[test] + fn test_iommufd_nvidia_card_reboot() { + test_nvidia_card_reboot_common(true); + } - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_upgrade_watchdog_local() { - _test_live_migration_watchdog(true, true); - } - #[test] - #[cfg(target_arch = "x86_64")] - fn test_live_migration_with_landlock() { - _test_live_migration_with_landlock(); - } + fn test_nvidia_card_iommu_address_width_common(iommufd: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + + let platform = format!( + "num_pci_segments=2,iommu_segments=1,iommu_address_width=42,{}", + platform_cfg(iommufd) + ); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) + .args(["--memory", "size=1G"]) + .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) + .args(["--device", format!("path={NVIDIA_VFIO_DEVICE}").as_str()]) + .args(["--platform", &platform]) + .args(["--api-socket", &api_socket]) + .default_disks() + .default_net() + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + assert!( + guest + .ssh_command("sudo dmesg") + .unwrap() + .contains("input address: 42 bits") + ); + + // Check the VFIO device works after boot + guest.check_nvidia_gpu(); + }); + + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); } - mod live_migration_sequential { - use super::*; + #[test] + fn test_nvidia_card_iommu_address_width() { + test_nvidia_card_iommu_address_width_common(false); + } - // NUMA, balloon, and virtio-fs live migration tests run sequentially + #[test] + fn test_iommufd_nvidia_card_iommu_address_width() { + test_nvidia_card_iommu_address_width_common(true); + } - #[test] - #[cfg(not(feature = "mshv"))] - fn test_live_migration_virtio_fs() { - _test_live_migration_virtio_fs(false); + fn test_nvidia_guest_numa_generic_initiator_common(iommufd: bool) { + // Skip test if VFIO device is not available or not ready + if !std::path::Path::new(NVIDIA_VFIO_DEVICE).exists() { + println!("SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} not found"); + return; } - #[test] - #[cfg(not(feature = "mshv"))] - fn test_live_migration_virtio_fs_local() { - _test_live_migration_virtio_fs(true); + // Check if device is bound to vfio-pci driver + let driver_path = format!("{NVIDIA_VFIO_DEVICE}/driver"); + if let Ok(driver) = std::fs::read_link(&driver_path) { + let driver_name = driver.file_name().unwrap_or_default().to_string_lossy(); + if driver_name != "vfio-pci" { + println!( + "SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} bound to {driver_name}, not vfio-pci" + ); + return; + } + } else { + println!("SKIPPED: VFIO device {NVIDIA_VFIO_DEVICE} not bound to any driver"); + return; } - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_balloon() { - _test_live_migration_balloon(false, false); - } + let disk_config = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); - #[test] - #[cfg_attr( - all(feature = "mshv", target_arch = "aarch64"), - ignore = "live migration not yet supported on mshv arm64" - )] - fn test_live_migration_balloon_local() { - _test_live_migration_balloon(false, true); - } + // x86_64: Direct kernel boot + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) + .args(["--memory", "size=0"]) + .args(["--memory-zone", "id=mem0,size=1G", "id=mem1,size=1G"]) + .args([ + "--numa", + "guest_numa_id=0,cpus=[0-1],distances=[1@20,2@25],memory_zones=mem0", + "guest_numa_id=1,cpus=[2-3],distances=[0@20,2@30],memory_zones=mem1", + "guest_numa_id=2,device_id=vfio0,distances=[0@25,1@30]", + ]) + .args(["--platform", &platform_cfg(iommufd)]) + .args([ + "--device", + &format!("id=vfio0,path={NVIDIA_VFIO_DEVICE},iommu=on"), + ]) + .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args(["--api-socket", &api_socket]) + .capture_output() + .default_disks() + .default_net() + .spawn() + .unwrap(); - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_upgrade_balloon() { - _test_live_migration_balloon(true, false); - } + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_upgrade_balloon_local() { - _test_live_migration_balloon(true, true); - } + // Verify NUMA topology is correct + guest.check_numa_common( + Some(&[960_000, 960_000]), + Some(&[&[0, 1], &[2, 3]]), + Some(&["10 20 25", "20 10 30", "25 30 10"]), + ); - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_migration_numa() { - _test_live_migration_numa(false, false); - } + // Verify Generic Initiator support is present + // Linux kernel sets has_generic_initiator when it parses Type 5 SRAT entries + let has_gi = guest + .ssh_command( + "cat /sys/devices/system/node/has_generic_initiator 2>/dev/null || echo 0", + ) + .unwrap() + .trim() + .to_string(); - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_migration_numa_local() { - _test_live_migration_numa(false, true); - } + assert_eq!( + has_gi, "2", + "Generic Initiator support should be detected by kernel" + ); - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_upgrade_numa() { - _test_live_migration_numa(true, false); - } + // Verify SRAT table contains Generic Initiator entry (Type 5) + // We'll check that /sys/firmware/acpi/tables/SRAT exists and contains our entry + let srat_check = guest + .ssh_command( + "[ -f /sys/firmware/acpi/tables/SRAT ] && echo 'exists' || echo 'missing'", + ) + .unwrap() + .trim() + .to_string(); - #[test] - #[cfg_attr(feature = "mshv", ignore = "See #7542")] - fn test_live_upgrade_numa_local() { - _test_live_migration_numa(true, true); - } + assert_eq!( + srat_check, "exists", + "SRAT table should exist in guest firmware" + ); - // Require to run ovs-dpdk tests sequentially because they rely on the same ovs-dpdk setup - #[test] - #[ignore = "See #5532"] - #[cfg(target_arch = "x86_64")] - #[cfg(not(feature = "mshv"))] - fn test_live_migration_ovs_dpdk() { - _test_live_migration_ovs_dpdk(false, false); - } + // Use hexdump to verify Type 5 entry is present + // Type 5 (0x05) should appear in the SRAT table + let srat_has_type5 = guest + .ssh_command("sudo hexdump -C /sys/firmware/acpi/tables/SRAT | grep -q '05 20' && echo 'found' || echo 'not_found'") + .unwrap() + .trim() + .to_string(); - #[test] - #[ignore = "See #5532 and #7689"] - #[cfg(target_arch = "x86_64")] - #[cfg(not(feature = "mshv"))] - fn test_live_migration_ovs_dpdk_local() { - _test_live_migration_ovs_dpdk(false, true); - } + assert_eq!( + srat_has_type5, "found", + "SRAT table should contain Generic Initiator Affinity Structure (Type 5, Length 0x20/32)" + ); + }); - #[test] - #[ignore = "See #5532"] - #[cfg(target_arch = "x86_64")] - #[cfg(not(feature = "mshv"))] - fn test_live_upgrade_ovs_dpdk() { - _test_live_migration_ovs_dpdk(true, false); - } + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); - #[test] - #[ignore = "See #5532"] - #[cfg(target_arch = "x86_64")] - #[cfg(not(feature = "mshv"))] - fn test_live_upgrade_ovs_dpdk_local() { - _test_live_migration_ovs_dpdk(true, true); - } + handle_child_output(r, &output); + } + + #[test] + fn test_nvidia_guest_numa_generic_initiator() { + test_nvidia_guest_numa_generic_initiator_common(false); + } + + #[test] + fn test_iommufd_nvidia_guest_numa_generic_initiator() { + test_nvidia_guest_numa_generic_initiator_common(true); } } diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 128ec31c5a..a9fc458377 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -283,21 +283,6 @@ else exit $RES fi -# Run all test cases related to live migration -if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="$TEST_THREADS_DEFAULT" "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} - RES=$? -else - exit $RES -fi - -if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} - RES=$? -else - exit $RES -fi - # Run tests on dbus_api if [ $RES -eq 0 ]; then cargo build --features "mshv,dbus_api" --all --release --target "$BUILD_TARGET" diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index eab2508099..b9cb7babbd 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -223,17 +223,6 @@ if [ $RES -eq 0 ]; then RES=$? fi -# Run all live-migration test cases -if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads="${PARALLEL_INTEGRATION_TESTS_NUM}" "live_migration_parallel::$test_filter" -- ${test_binary_args[*]} - RES=$? -fi - -if [ $RES -eq 0 ]; then - time cargo nextest run $test_features --retries 3 --no-fail-fast --no-tests=pass --test-threads=1 "live_migration_sequential::$test_filter" -- ${test_binary_args[*]} - RES=$? -fi - # Run tests on dbus_api if [ $RES -eq 0 ]; then cargo build --features "mshv,dbus_api" --all --release --target "$BUILD_TARGET" From 5b2a5e639c40856b9a12080b32c804cb73d22f3b Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 25 Apr 2026 20:28:51 +0100 Subject: [PATCH 1400/1893] 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 --- scripts/run_integration_tests_aarch64.sh | 2 +- scripts/run_integration_tests_x86_64.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index a9fc458377..320536b922 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -253,7 +253,7 @@ sudo bash -c "echo 1 > /sys/kernel/mm/ksm/run" # Both test_vfio and ovs-dpdk rely on hugepages HUGEPAGESIZE=$(grep Hugepagesize /proc/meminfo | awk '{print $2}') -PAGE_NUM=$((12288 * 1024 / HUGEPAGESIZE)) +PAGE_NUM=$((6144 * 1024 / HUGEPAGESIZE)) echo "$PAGE_NUM" | sudo tee /proc/sys/vm/nr_hugepages sudo chmod a+rwX /dev/hugepages diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index b9cb7babbd..f847cd57bc 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -194,7 +194,7 @@ sudo bash -c "echo 1 > /sys/kernel/mm/ksm/run" # Both test_vfio, ovs-dpdk and vDPA tests rely on hugepages HUGEPAGESIZE=$(grep Hugepagesize /proc/meminfo | awk '{print $2}') -PAGE_NUM=$((12288 * 1024 / HUGEPAGESIZE)) +PAGE_NUM=$((6144 * 1024 / HUGEPAGESIZE)) echo "$PAGE_NUM" | sudo tee /proc/sys/vm/nr_hugepages sudo chmod a+rwX /dev/hugepages From 466f50e72cb88f3dc2d310b685d0264ef08663f2 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 25 Apr 2026 22:44:56 +0100 Subject: [PATCH 1401/1893] 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 --- cloud-hypervisor/tests/integration.rs | 368 +++++++++++++------------- 1 file changed, 183 insertions(+), 185 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index bafcd7f43d..f12b4a08ab 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -7059,6 +7059,188 @@ mod common_parallel { fn test_live_migration_with_landlock() { _test_live_migration_with_landlock(); } + + #[cfg(not(feature = "mshv"))] + fn _test_live_migration_virtio_fs(local: bool) { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + let shared_dir = guest.tmp_dir.as_path().join("virtiofs_shared"); + std::fs::create_dir(&shared_dir).unwrap(); + + let (daemon_child, virtiofsd_socket_path) = + prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); + + let src_api_socket = temp_api_path(&guest.tmp_dir); + + // Start the source VM + let mut src_child = GuestCommand::new(&guest) + .args(["--api-socket", &src_api_socket]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=512M,shared=on"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_disks() + .default_net() + .args([ + "--fs", + format!("socket={virtiofsd_socket_path},tag=myfs,num_queues=1,queue_size=1024") + .as_str(), + ]) + .capture_output() + .spawn() + .unwrap(); + + // Start the destination VM + let mut dest_api_socket = temp_api_path(&guest.tmp_dir); + dest_api_socket.push_str(".dest"); + let mut dest_child = GuestCommand::new(&guest) + .args(["--api-socket", &dest_api_socket]) + .capture_output() + .spawn() + .unwrap(); + + // Spawn a thread that waits for the old virtiofsd to exit then + // starts a replacement. During migration the source saves + // DEVICE_STATE then disconnects, causing virtiofsd to exit. + // The destination needs a fresh virtiofsd to load DEVICE_STATE. + // We remove the socket file first so the destination cannot + // accidentally connect to the old instance. + let virtiofsd_socket_clone = virtiofsd_socket_path.clone(); + let shared_dir_str = shared_dir.to_str().unwrap().to_string(); + let (restart_tx, restart_rx) = std::sync::mpsc::channel(); + let _monitor = thread::spawn(move || { + let mut child = daemon_child; + let _ = child.wait(); + let mut path = dirs::home_dir().unwrap(); + path.push("workloads"); + path.push("virtiofsd"); + let new_child = Command::new(path) + .args(["--shared-dir", &shared_dir_str]) + .args(["--socket-path", &virtiofsd_socket_clone]) + .args(["--cache", "never"]) + .args(["--tag", "myfs"]) + .spawn() + .unwrap(); + wait_for_virtiofsd_socket(&virtiofsd_socket_clone); + let _ = restart_tx.send(new_child); + }); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Mount virtiofs and verify it works + guest + .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") + .unwrap(); + + // Write a test file through virtiofs before migration + guest + .ssh_command( + "sudo bash -c 'echo pre_migration_data > mount_dir/migration_test_file'", + ) + .unwrap(); + + // Verify the file is accessible + assert_eq!( + guest + .ssh_command("cat mount_dir/migration_test_file") + .unwrap() + .trim(), + "pre_migration_data" + ); + + let migration_socket = String::from( + guest + .tmp_dir + .as_path() + .join("live-migration.sock") + .to_str() + .unwrap(), + ); + + // Remove the socket so the destination cannot connect to + // the old virtiofsd (which is still running). The source's + // existing connection uses an already-accepted fd. + let _ = std::fs::remove_file(&virtiofsd_socket_path); + + assert!( + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), + "Unsuccessful command: 'send-migration' or 'receive-migration'." + ); + }); + + // Check and report any errors occurred during the live-migration + if r.is_err() { + print_and_panic( + src_child, + dest_child, + None, + "Error occurred during live-migration with virtio-fs", + ); + } + + // Check the source vm has been terminated successfully (give it '3s' to settle) + thread::sleep(Duration::from_secs(3)); + if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { + print_and_panic( + src_child, + dest_child, + None, + "source VM was not terminated successfully.", + ); + } + + // Post live-migration checks + let r = std::panic::catch_unwind(|| { + // Verify virtiofs still works after migration + // Read the file written before migration + assert_eq!( + guest + .ssh_command("cat mount_dir/migration_test_file") + .unwrap() + .trim(), + "pre_migration_data" + ); + + // Write a new file after migration + guest + .ssh_command( + "sudo bash -c 'echo post_migration_data > mount_dir/post_migration_file'", + ) + .unwrap(); + + // Verify the new file exists on the host + let post_content = + std::fs::read_to_string(shared_dir.join("post_migration_file")).unwrap(); + assert_eq!(post_content.trim(), "post_migration_data"); + }); + + // Clean up + let _ = dest_child.kill(); + let dest_output = dest_child.wait_with_output().unwrap(); + if let Ok(mut new_daemon) = restart_rx.try_recv() { + let _ = new_daemon.kill(); + let _ = new_daemon.wait(); + } + let _ = std::fs::remove_file(shared_dir.join("migration_test_file")); + let _ = std::fs::remove_file(shared_dir.join("post_migration_file")); + + handle_child_output(r, &dest_output); + } + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_virtio_fs() { + _test_live_migration_virtio_fs(false); + } + + #[test] + #[cfg(not(feature = "mshv"))] + fn test_live_migration_virtio_fs_local() { + _test_live_migration_virtio_fs(true); + } } mod dbus_api { @@ -9205,191 +9387,7 @@ mod common_sequential { handle_child_output(Ok(()), &ovs_output); } - #[cfg(not(feature = "mshv"))] - fn _test_live_migration_virtio_fs(local: bool) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - let kernel_path = direct_kernel_boot_path(); - - let mut workload_path = dirs::home_dir().unwrap(); - workload_path.push("workloads"); - let mut shared_dir = workload_path; - shared_dir.push("shared_dir"); - - let (daemon_child, virtiofsd_socket_path) = - prepare_virtiofsd(&guest.tmp_dir, shared_dir.to_str().unwrap()); - - let src_api_socket = temp_api_path(&guest.tmp_dir); - - // Start the source VM - let mut src_child = GuestCommand::new(&guest) - .args(["--api-socket", &src_api_socket]) - .args(["--cpus", "boot=2"]) - .args(["--memory", "size=512M,shared=on"]) - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_disks() - .default_net() - .args([ - "--fs", - format!("socket={virtiofsd_socket_path},tag=myfs,num_queues=1,queue_size=1024") - .as_str(), - ]) - .capture_output() - .spawn() - .unwrap(); - - // Start the destination VM - let mut dest_api_socket = temp_api_path(&guest.tmp_dir); - dest_api_socket.push_str(".dest"); - let mut dest_child = GuestCommand::new(&guest) - .args(["--api-socket", &dest_api_socket]) - .capture_output() - .spawn() - .unwrap(); - - // Spawn a thread that waits for the old virtiofsd to exit then - // starts a replacement. During migration the source saves - // DEVICE_STATE then disconnects, causing virtiofsd to exit. - // The destination needs a fresh virtiofsd to load DEVICE_STATE. - // We remove the socket file first so the destination cannot - // accidentally connect to the old instance. - let virtiofsd_socket_clone = virtiofsd_socket_path.clone(); - let shared_dir_str = shared_dir.to_str().unwrap().to_string(); - let (restart_tx, restart_rx) = std::sync::mpsc::channel(); - let _monitor = thread::spawn(move || { - let mut child = daemon_child; - let _ = child.wait(); - let mut path = dirs::home_dir().unwrap(); - path.push("workloads"); - path.push("virtiofsd"); - let new_child = Command::new(path) - .args(["--shared-dir", &shared_dir_str]) - .args(["--socket-path", &virtiofsd_socket_clone]) - .args(["--cache", "never"]) - .args(["--tag", "myfs"]) - .spawn() - .unwrap(); - wait_for_virtiofsd_socket(&virtiofsd_socket_clone); - let _ = restart_tx.send(new_child); - }); - - let r = std::panic::catch_unwind(|| { - guest.wait_vm_boot().unwrap(); - - // Mount virtiofs and verify it works - guest - .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/") - .unwrap(); - - // Write a test file through virtiofs before migration - guest - .ssh_command( - "sudo bash -c 'echo pre_migration_data > mount_dir/migration_test_file'", - ) - .unwrap(); - - // Verify the file is accessible - assert_eq!( - guest - .ssh_command("cat mount_dir/migration_test_file") - .unwrap() - .trim(), - "pre_migration_data" - ); - - let migration_socket = String::from( - guest - .tmp_dir - .as_path() - .join("live-migration.sock") - .to_str() - .unwrap(), - ); - - // Remove the socket so the destination cannot connect to - // the old virtiofsd (which is still running). The source's - // existing connection uses an already-accepted fd. - let _ = std::fs::remove_file(&virtiofsd_socket_path); - - assert!( - start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), - "Unsuccessful command: 'send-migration' or 'receive-migration'." - ); - }); - - // Check and report any errors occurred during the live-migration - if r.is_err() { - print_and_panic( - src_child, - dest_child, - None, - "Error occurred during live-migration with virtio-fs", - ); - } - - // Check the source vm has been terminated successfully (give it '3s' to settle) - thread::sleep(Duration::from_secs(3)); - if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - print_and_panic( - src_child, - dest_child, - None, - "source VM was not terminated successfully.", - ); - } - - // Post live-migration checks - let r = std::panic::catch_unwind(|| { - // Verify virtiofs still works after migration - // Read the file written before migration - assert_eq!( - guest - .ssh_command("cat mount_dir/migration_test_file") - .unwrap() - .trim(), - "pre_migration_data" - ); - - // Write a new file after migration - guest - .ssh_command( - "sudo bash -c 'echo post_migration_data > mount_dir/post_migration_file'", - ) - .unwrap(); - - // Verify the new file exists on the host - let post_content = - std::fs::read_to_string(shared_dir.join("post_migration_file")).unwrap(); - assert_eq!(post_content.trim(), "post_migration_data"); - }); - - // Clean up - let _ = dest_child.kill(); - let dest_output = dest_child.wait_with_output().unwrap(); - if let Ok(mut new_daemon) = restart_rx.try_recv() { - let _ = new_daemon.kill(); - let _ = new_daemon.wait(); - } - let _ = std::fs::remove_file(shared_dir.join("migration_test_file")); - let _ = std::fs::remove_file(shared_dir.join("post_migration_file")); - - handle_child_output(r, &dest_output); - } - - // NUMA, balloon, and virtio-fs live migration tests run sequentially - - #[test] - #[cfg(not(feature = "mshv"))] - fn test_live_migration_virtio_fs() { - _test_live_migration_virtio_fs(false); - } - - #[test] - #[cfg(not(feature = "mshv"))] - fn test_live_migration_virtio_fs_local() { - _test_live_migration_virtio_fs(true); - } + // NUMA and balloon live migration tests run sequentially #[test] #[cfg(not(feature = "mshv"))] From 92229a60eda74e07c3c579b9f7e65ba6c654bf9c Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 20 Apr 2026 13:04:59 +0100 Subject: [PATCH 1402/1893] 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 --- cloud-hypervisor/tests/integration.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index f12b4a08ab..3a7d4cd714 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -5398,10 +5398,21 @@ mod common_parallel { kill_child(&mut child2); let output = child1.wait_with_output().unwrap(); - child2.wait().unwrap(); + let output2 = child2.wait_with_output().unwrap(); cleanup_ovs_dpdk(); + if r.is_err() { + eprintln!( + "\n\n==== Start restored VM stdout ====\n\n{}\n\n==== End restored VM stdout ====", + String::from_utf8_lossy(&output2.stdout) + ); + eprintln!( + "\n\n==== Start restored VM stderr ====\n\n{}\n\n==== End restored VM stderr ====", + String::from_utf8_lossy(&output2.stderr) + ); + } + handle_child_output(r, &output); } From 733d1fe55338232b9469a1c52cfda442e3db594a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 26 Apr 2026 13:10:54 +0100 Subject: [PATCH 1403/1893] 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 Assisted-by: Claude:claude-opus-4-7 --- virtio-devices/src/vhost_user/blk.rs | 26 +--------------- virtio-devices/src/vhost_user/fs.rs | 25 +--------------- .../src/vhost_user/generic_vhost_user.rs | 25 +--------------- virtio-devices/src/vhost_user/mod.rs | 30 ++++++++++++++++++- virtio-devices/src/vhost_user/net.rs | 26 +--------------- 5 files changed, 33 insertions(+), 99 deletions(-) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 2526b36f6b..7e434dd9fb 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -6,7 +6,6 @@ use std::sync::{Arc, Barrier, Mutex}; use std::{mem, result}; use block::VirtioBlockConfig; -use event_monitor::event; use log::{error, info}; use seccompiler::SeccompAction; use vhost::vhost_user::message::{ @@ -309,30 +308,7 @@ impl VirtioDevice for Blk { } fn reset(&mut self) -> Option> { - // We first must resume the virtio thread if it was paused. - if self.vu_common.virtio_common.pause_evt.take().is_some() { - self.vu_common.virtio_common.resume().ok()?; - } - - if let Some(vu) = &self.vu_common.vu - && let Err(e) = vu.lock().unwrap().reset_vhost_user() - { - error!( - "Failed to reset vhost-user daemon for socket {}: {e:?}", - self.vu_common.socket_path - ); - return None; - } - - if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { - // Ignore the result because there is nothing we can do about it. - let _ = kill_evt.write(1); - } - - event!("virtio-device", "reset", "id", &self.id); - - // Return the interrupt - Some(self.vu_common.virtio_common.interrupt_cb.take().unwrap()) + self.vu_common.reset(&self.id) } fn shutdown(&mut self) { diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index c062edf19b..60a60a86ce 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -286,30 +286,7 @@ impl VirtioDevice for Fs { } fn reset(&mut self) -> Option> { - // We first must resume the virtio thread if it was paused. - if self.vu_common.virtio_common.pause_evt.take().is_some() { - self.vu_common.virtio_common.resume().ok()?; - } - - if let Some(vu) = &self.vu_common.vu - && let Err(e) = vu.lock().unwrap().reset_vhost_user() - { - error!( - "Failed to reset vhost-user daemon for socket {}: {e:?}", - self.vu_common.socket_path - ); - return None; - } - - if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { - // Ignore the result because there is nothing we can do about it. - let _ = kill_evt.write(1); - } - - event!("virtio-device", "reset", "id", &self.id); - - // Return the interrupt - Some(self.vu_common.virtio_common.interrupt_cb.take().unwrap()) + self.vu_common.reset(&self.id) } fn shutdown(&mut self) { diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index dda891e912..0d2aaf682f 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -309,30 +309,7 @@ impl VirtioDevice for GenericVhostUser { } fn reset(&mut self) -> Option> { - // We first must resume the virtio thread if it was paused. - if self.vu_common.virtio_common.pause_evt.take().is_some() { - self.vu_common.virtio_common.resume().ok()?; - } - - if let Some(vu) = &self.vu_common.vu - && let Err(e) = vu.lock().unwrap().reset_vhost_user() - { - error!( - "Failed to reset vhost-user daemon for socket {}: {e:?}", - self.vu_common.socket_path - ); - return None; - } - - if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { - // Ignore the result because there is nothing we can do about it. - let _ = kill_evt.write(1); - } - - event!("virtio-device", "reset", "id", &self.id); - - // Return the interrupt - Some(self.vu_common.virtio_common.interrupt_cb.take().unwrap()) + self.vu_common.reset(&self.id) } fn shutdown(&mut self) { diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index abca12c058..9f2eea4231 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -8,6 +8,7 @@ use std::sync::{Arc, Barrier, Mutex}; use std::{io, thread}; use anyhow::anyhow; +use event_monitor::event; use log::error; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -21,7 +22,7 @@ use vm_memory::guest_memory::Error as MmapError; use vm_memory::mmap::MmapRegionError; use vm_memory::{Address, GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; -use vm_migration::{MigratableError, Snapshot}; +use vm_migration::{MigratableError, Pausable, Snapshot}; use vmm_sys_util::eventfd::EventFd; use vu_common_ctrl::VhostUserHandle; @@ -427,6 +428,33 @@ impl VhostUserCommon { Ok(()) } + pub fn reset(&mut self, id: &str) -> Option> { + // We first must resume the virtio thread if it was paused. + if self.virtio_common.pause_evt.take().is_some() { + self.virtio_common.resume().ok()?; + } + + if let Some(vu) = &self.vu + && let Err(e) = vu.lock().unwrap().reset_vhost_user() + { + error!( + "Failed to reset vhost-user daemon for socket {}: {e:?}", + self.socket_path + ); + return None; + } + + if let Some(kill_evt) = self.virtio_common.kill_evt.take() { + // Ignore the result because there is nothing we can do about it. + let _ = kill_evt.write(1); + } + + event!("virtio-device", "reset", "id", id); + + // Return the interrupt + Some(self.virtio_common.interrupt_cb.take().unwrap()) + } + pub fn shutdown(&mut self) { // Signal the epoll thread to exit, unpause it (it may be parked // if the VM was paused for migration), then wait for it to finish. diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index e5f9eda7d8..8563a21240 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -5,7 +5,6 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier, Mutex}; use std::{result, thread}; -use event_monitor::event; use log::{error, info}; use net_util::{CtrlQueue, MacAddr, VirtioNetConfig, build_net_config_space}; use seccompiler::SeccompAction; @@ -366,30 +365,7 @@ impl VirtioDevice for Net { } fn reset(&mut self) -> Option> { - // We first must resume the virtio thread if it was paused. - if self.vu_common.virtio_common.pause_evt.take().is_some() { - self.vu_common.virtio_common.resume().ok()?; - } - - if let Some(vu) = &self.vu_common.vu - && let Err(e) = vu.lock().unwrap().reset_vhost_user() - { - error!( - "Failed to reset vhost-user daemon for socket {}: {e:?}", - self.vu_common.socket_path - ); - return None; - } - - if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { - // Ignore the result because there is nothing we can do about it. - let _ = kill_evt.write(1); - } - - event!("virtio-device", "reset", "id", &self.id); - - // Return the interrupt - Some(self.vu_common.virtio_common.interrupt_cb.take().unwrap()) + self.vu_common.reset(&self.id) } fn shutdown(&mut self) { From d573f1bf9619419feceef4bee3a4a18b735b71af Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 26 Apr 2026 13:11:47 +0100 Subject: [PATCH 1404/1893] 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 Assisted-by: Claude:claude-opus-4-7 --- virtio-devices/src/device.rs | 10 +++++++--- virtio-devices/src/vdpa.rs | 3 ++- virtio-devices/src/vhost_user/mod.rs | 11 +++++++---- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 89e1ee2eaf..84f0642594 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -293,9 +293,13 @@ impl VirtioCommon { pub fn reset(&mut self) -> Option> { self.queue_evts.clear(); - // We first must resume the virtio thread if it was paused. - if self.pause_evt.take().is_some() { - self.resume().ok()?; + // Resume the virtio thread if it was paused. Reset must always + // converge to fresh state, so a resume failure is logged but doesn't + // skip the rest of the teardown. + if self.pause_evt.take().is_some() + && let Err(e) = self.resume() + { + error!("Failed to resume paused device during reset: {e:?}"); } if let Some(kill_evt) = self.kill_evt.take() { diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index f9bf7a39de..e1cf48d9a3 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -451,9 +451,10 @@ impl VirtioDevice for Vdpa { } fn reset(&mut self) -> Option> { + // Backend reset failures are logged but don't skip local cleanup: + // reset must converge to fresh state regardless of backend state. if let Err(e) = self.reset_vdpa() { error!("Failed to reset vhost-vdpa: {e:?}"); - return None; } event!("vdpa", "reset", "id", &self.id); diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 9f2eea4231..07c8d4555a 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -429,9 +429,13 @@ impl VhostUserCommon { } pub fn reset(&mut self, id: &str) -> Option> { - // We first must resume the virtio thread if it was paused. - if self.virtio_common.pause_evt.take().is_some() { - self.virtio_common.resume().ok()?; + // Resume the virtio thread if it was paused. Reset must always + // converge to fresh state, so backend resume / reset failures are + // logged but don't skip the rest of the teardown. + if self.virtio_common.pause_evt.take().is_some() + && let Err(e) = self.virtio_common.resume() + { + error!("Failed to resume paused device during reset: {e:?}"); } if let Some(vu) = &self.vu @@ -441,7 +445,6 @@ impl VhostUserCommon { "Failed to reset vhost-user daemon for socket {}: {e:?}", self.socket_path ); - return None; } if let Some(kill_evt) = self.virtio_common.kill_evt.take() { From fe6bcd03769feddff24f50e386d8115d76a1470e Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 18 Apr 2026 12:21:39 +0100 Subject: [PATCH 1405/1893] 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 --- .../src/transport/pci_common_config.rs | 47 +++++++++++++++++++ virtio-devices/src/transport/pci_device.rs | 30 +++++------- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 379622bd97..1ca86a2203 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -160,6 +160,19 @@ impl VirtioPciCommonConfig { } } + /// Returns the common configuration to its power-on state. Per the virtio + /// spec a device reset must restore the values that a fresh driver would + /// observe. + pub fn reset(&mut self) { + self.driver_status.store(0, Ordering::Release); + self.device_feature_select = 0; + self.driver_feature_select = 0; + self.queue_select = 0; + self.msix_config + .store(VIRTQ_MSI_NO_VECTOR, Ordering::Release); + self.msix_queues.lock().unwrap().fill(VIRTQ_MSI_NO_VECTOR); + } + pub fn read(&mut self, offset: u64, data: &mut [u8], queues: &[Queue]) { assert!(data.len() <= 8); @@ -521,4 +534,38 @@ mod unit_tests { // Write queue_msix_vector — must not panic. regs.write(0x1a, &[0xAB, 0xCD], &mut queues); } + + #[test] + fn reset_returns_initial_state() { + let dev: Arc> = Arc::new(Mutex::new(DummyDevice(0))); + let mut regs = VirtioPciCommonConfig { + device: dev, + driver_status: Arc::new(AtomicU8::new(0x55)), + config_generation: 0xab, + device_feature_select: 1, + driver_feature_select: 1, + queue_select: 7, + msix_config: Arc::new(AtomicU16::new(3)), + msix_queues: Arc::new(Mutex::new(vec![1, 2, 3])), + }; + + regs.reset(); + + assert_eq!(regs.driver_status.load(Ordering::Acquire), 0); + assert_eq!(regs.config_generation, 0xab); // unchanged across reset + assert_eq!(regs.device_feature_select, 0); + assert_eq!(regs.driver_feature_select, 0); + assert_eq!(regs.queue_select, 0); + assert_eq!( + regs.msix_config.load(Ordering::Acquire), + VIRTQ_MSI_NO_VECTOR + ); + assert!( + regs.msix_queues + .lock() + .unwrap() + .iter() + .all(|v| *v == VIRTQ_MSI_NO_VECTOR) + ); + } } diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 98abb04935..fe3cf71054 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -1245,24 +1245,20 @@ impl PciDevice for VirtioPciDevice { return Some(barrier); } - // Device has been reset by the driver - if self.device_activated.load(Ordering::SeqCst) && self.is_driver_init() { - let mut device = self.device.lock().unwrap(); - if let Some(virtio_interrupt) = device.reset() { - // Upon reset the device returns its interrupt EventFD - self.virtio_interrupt = Some(virtio_interrupt); - self.device_activated.store(false, Ordering::SeqCst); - - // Reset queue readiness (changes queue_enable), queue sizes - // and selected_queue as per spec for reset - self.queues.iter_mut().for_each(Queue::reset); - self.common_config.queue_select = 0; - } else { - error!("Attempt to reset device when not implemented in underlying device"); - self.common_config - .driver_status - .store(crate::DEVICE_FAILED as u8, Ordering::SeqCst); + // The driver requested a reset by writing 0 to device_status. Per the + // virtio spec this is permitted at any point in initialisation. + if self.is_driver_init() { + if self.device_activated.swap(false, Ordering::SeqCst) { + let mut device = self.device.lock().unwrap(); + if let Some(virtio_interrupt) = device.reset() { + // Upon reset the device returns its interrupt EventFD + self.virtio_interrupt = Some(virtio_interrupt); + } } + + // Reset queue readiness and the common configuration + self.queues.iter_mut().for_each(Queue::reset); + self.common_config.reset(); } None From e11ff541da7a520a02acf2128ba84e63da80d895 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 26 Apr 2026 10:57:49 +0100 Subject: [PATCH 1406/1893] 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 --- virtio-devices/src/balloon.rs | 19 ++++-------- virtio-devices/src/block.rs | 15 ++++------ virtio-devices/src/console.rs | 5 ++-- virtio-devices/src/device.rs | 30 ++++++++++--------- virtio-devices/src/iommu.rs | 5 ++-- virtio-devices/src/mem.rs | 23 +++++--------- virtio-devices/src/net.rs | 5 ++-- virtio-devices/src/pmem.rs | 5 ++-- virtio-devices/src/rng.rs | 5 ++-- virtio-devices/src/transport/pci_device.rs | 11 +++---- virtio-devices/src/vdpa.rs | 7 ++--- virtio-devices/src/vhost_user/blk.rs | 6 ++-- virtio-devices/src/vhost_user/fs.rs | 6 ++-- .../src/vhost_user/generic_vhost_user.rs | 6 ++-- virtio-devices/src/vhost_user/mod.rs | 19 +++++------- virtio-devices/src/vhost_user/net.rs | 6 ++-- virtio-devices/src/vsock/device.rs | 5 ++-- virtio-devices/src/watchdog.rs | 5 ++-- 18 files changed, 73 insertions(+), 110 deletions(-) diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index 37ebb056fc..74b83dff21 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -458,7 +458,6 @@ pub struct Balloon { config: VirtioBalloonConfig, seccomp_action: SeccompAction, exit_evt: EventFd, - interrupt_cb: Option>, } impl Balloon { @@ -523,20 +522,15 @@ impl Balloon { config, seccomp_action, exit_evt, - interrupt_cb: None, }) } pub fn resize(&mut self, size: u64) -> Result<(), Error> { self.config.num_pages = (size >> VIRTIO_BALLOON_PFN_SHIFT) as u32; - if let Some(interrupt_cb) = &self.interrupt_cb { - interrupt_cb - .trigger(VirtioInterruptType::Config) - .map_err(Error::FailedSignal) - } else { - Ok(()) - } + self.common + .trigger_interrupt(VirtioInterruptType::Config) + .map_err(Error::FailedSignal) } // Get the actual size of the virtio-balloon. @@ -647,8 +641,6 @@ impl VirtioDevice for Balloon { None }; - self.interrupt_cb = Some(interrupt_cb.clone()); - let mut handler = BalloonEpollHandler { mem, queues: virtqueues, @@ -688,10 +680,9 @@ impl VirtioDevice for Balloon { self.common.access_platform() } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); event!("virtio-device", "reset", "id", &self.id); - result } } diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 5765d5a398..d37db6cd85 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -1025,13 +1025,9 @@ impl Block { self.common.resume().map_err(Error::ResumeVcpus)?; - if let Some(interrupt_cb) = self.common.interrupt_cb.as_ref() { - interrupt_cb - .trigger(VirtioInterruptType::Config) - .map_err(Error::ConfigChange) - } else { - Ok(()) - } + self.common + .trigger_interrupt(VirtioInterruptType::Config) + .map_err(Error::ConfigChange) } #[cfg(fuzzing)] @@ -1178,11 +1174,10 @@ impl VirtioDevice for Block { Ok(()) } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); self.set_writeback_mode(true); event!("virtio-device", "reset", "id", &self.id); - result } fn counters(&self) -> Option>> { diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index 0a4cf65cff..180be8dfaf 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -773,10 +773,9 @@ impl VirtioDevice for Console { Ok(()) } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); event!("virtio-device", "reset", "id", &self.id); - result } fn set_access_platform(&mut self, access_platform: Arc) { diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 84f0642594..2349db6b54 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -123,11 +123,8 @@ pub trait VirtioDevice: Send { /// Activates this device for real usage. fn activate(&mut self, context: ActivationContext) -> ActivateResult; - /// Optionally deactivates this device and returns ownership of the guest memory map, interrupt - /// event, and queue events. - fn reset(&mut self) -> Option> { - None - } + /// Optionally deactivates this device. + fn reset(&mut self) {} /// Returns the list of shared memory regions required by the device. fn get_shm_regions(&self) -> Option { @@ -290,7 +287,7 @@ impl VirtioCommon { Ok(()) } - pub fn reset(&mut self) -> Option> { + pub fn reset(&mut self) { self.queue_evts.clear(); // Resume the virtio thread if it was paused. Reset must always @@ -315,8 +312,16 @@ impl VirtioCommon { } } - // Return the interrupt - Some(self.interrupt_cb.take().unwrap()) + // Drop the interrupt callback clone + self.interrupt_cb = None; + } + + pub fn trigger_interrupt(&self, int_type: VirtioInterruptType) -> std::io::Result<()> { + if let Some(interrupt_cb) = &self.interrupt_cb { + interrupt_cb.trigger(int_type) + } else { + Ok(()) + } } // Wait for the worker thread to finish and return @@ -406,12 +411,9 @@ impl Pausable for VirtioCommon { } // Also trigger interrupts into the guest to wake up the driver to avoid a "livelock" - if let Some(interrupt_cb) = &self.interrupt_cb { - for i in 0..self.queue_evts.len() { - interrupt_cb - .trigger(crate::VirtioInterruptType::Queue(i as u16)) - .ok(); - } + for i in 0..self.queue_evts.len() { + self.trigger_interrupt(crate::VirtioInterruptType::Queue(i as u16)) + .ok(); } Ok(()) diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index 513d510b56..bc03e6775f 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -1120,10 +1120,9 @@ impl VirtioDevice for Iommu { Ok(()) } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); event!("virtio-device", "reset", "id", &self.id); - result } } diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 727fd72c01..afc168a6ed 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -737,7 +737,6 @@ pub struct Mem { dma_mapping_handlers: Arc>>>, blocks_state: Arc>, exit_evt: EventFd, - interrupt_cb: Option>, } impl Mem { @@ -830,7 +829,6 @@ impl Mem { dma_mapping_handlers: Arc::new(Mutex::new(BTreeMap::new())), blocks_state, exit_evt, - interrupt_cb: None, }) } @@ -844,15 +842,11 @@ impl Mem { Error::ResizeError(anyhow!("Failed to update virtio configuration: {e:?}")) })?; - if let Some(interrupt_cb) = self.interrupt_cb.as_ref() { - interrupt_cb - .trigger(VirtioInterruptType::Config) - .map_err(|e| { - Error::ResizeError(anyhow!("Failed to signal the guest about resize: {e:?}")) - }) - } else { - Ok(()) - } + self.common + .trigger_interrupt(VirtioInterruptType::Config) + .map_err(|e| { + Error::ResizeError(anyhow!("Failed to signal the guest about resize: {e:?}")) + }) } pub fn add_dma_mapping_handler( @@ -966,8 +960,6 @@ impl VirtioDevice for Mem { let (_, queue, queue_evt) = queues.remove(0); - self.interrupt_cb = Some(interrupt_cb.clone()); - let mut handler = MemEpollHandler { mem, region: self.region.clone(), @@ -1016,10 +1008,9 @@ impl VirtioDevice for Mem { Ok(()) } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); event!("virtio-device", "reset", "id", &self.id); - result } } diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index d7e1d1f361..13c64b270e 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -868,10 +868,9 @@ impl VirtioDevice for Net { Ok(()) } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); event!("virtio-device", "reset", "id", &self.id); - result } fn counters(&self) -> Option>> { diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 80d41ad5d7..e0ec265311 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -438,10 +438,9 @@ impl VirtioDevice for Pmem { Err(ActivateError::BadActivate) } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); event!("virtio-device", "reset", "id", &self.id); - result } fn userspace_mappings(&self) -> Vec { diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 9d86cf7d8b..b639cc1926 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -311,10 +311,9 @@ impl VirtioDevice for Rng { Err(ActivateError::BadActivate) } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); event!("virtio-device", "reset", "id", &self.id); - result } fn set_access_platform(&mut self, access_platform: Arc) { diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index fe3cf71054..41743e419e 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -300,7 +300,7 @@ pub struct VirtioPciDeviceState { } pub struct VirtioPciDeviceActivator { - interrupt: Option>, + interrupt: Arc, memory: Option>, device: Arc>, device_activated: Arc, @@ -315,7 +315,7 @@ impl VirtioPciDeviceActivator { let mut locked_device = self.device.lock().unwrap(); locked_device.activate(crate::device::ActivationContext { mem: self.memory.take().unwrap(), - interrupt_cb: self.interrupt.take().unwrap(), + interrupt_cb: self.interrupt, queues: self.queues.take().unwrap(), device_status: self.status, })?; @@ -822,7 +822,7 @@ impl VirtioPciDevice { } VirtioPciDeviceActivator { - interrupt: self.virtio_interrupt.take(), + interrupt: self.virtio_interrupt.as_ref().unwrap().clone(), memory: Some(self.memory.clone()), device: self.device.clone(), queues: Some(queues), @@ -1250,10 +1250,7 @@ impl PciDevice for VirtioPciDevice { if self.is_driver_init() { if self.device_activated.swap(false, Ordering::SeqCst) { let mut device = self.device.lock().unwrap(); - if let Some(virtio_interrupt) = device.reset() { - // Upon reset the device returns its interrupt EventFD - self.virtio_interrupt = Some(virtio_interrupt); - } + device.reset(); } // Reset queue readiness and the common configuration diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index e1cf48d9a3..44b5cfde8f 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -443,14 +443,13 @@ impl VirtioDevice for Vdpa { self.activate_vdpa(&mem.memory(), virtio_interrupt.as_ref(), &queues) .map_err(ActivateError::ActivateVdpa)?; - // Store the virtio interrupt handler as we need to return it on reset self.common.interrupt_cb = Some(virtio_interrupt); event!("vdpa", "activated", "id", &self.id); Ok(()) } - fn reset(&mut self) -> Option> { + fn reset(&mut self) { // Backend reset failures are logged but don't skip local cleanup: // reset must converge to fresh state regardless of backend state. if let Err(e) = self.reset_vdpa() { @@ -459,8 +458,8 @@ impl VirtioDevice for Vdpa { event!("vdpa", "reset", "id", &self.id); - // Return the virtio interrupt handler - self.common.interrupt_cb.take() + // Drop the interrupt callback clone + self.common.interrupt_cb = None; } fn set_access_platform(&mut self, access_platform: Arc) { diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 7e434dd9fb..5b64bbb5ab 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -28,7 +28,7 @@ use super::{DEFAULT_VIRTIO_FEATURES, Error, Result}; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::{VhostUserCommon, VhostUserState}; -use crate::{GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_ACCESS_PLATFORM, VirtioInterrupt}; +use crate::{GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_ACCESS_PLATFORM}; const DEFAULT_QUEUE_NUMBER: usize = 1; @@ -307,8 +307,8 @@ impl VirtioDevice for Blk { Ok(()) } - fn reset(&mut self) -> Option> { - self.vu_common.reset(&self.id) + fn reset(&mut self) { + self.vu_common.reset(&self.id); } fn shutdown(&mut self) { diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index 60a60a86ce..37c7c420e0 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -25,7 +25,7 @@ use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::{VhostUserCommon, VhostUserState}; use crate::{ ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_ACCESS_PLATFORM, - VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioSharedMemoryList, + VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioSharedMemoryList, }; const NUM_QUEUE_OFFSET: usize = 1; @@ -285,8 +285,8 @@ impl VirtioDevice for Fs { Ok(()) } - fn reset(&mut self) -> Option> { - self.vu_common.reset(&self.id) + fn reset(&mut self) { + self.vu_common.reset(&self.id); } fn shutdown(&mut self) { diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 0d2aaf682f..3ac01406bf 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -26,7 +26,7 @@ use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::{VhostUserCommon, VhostUserState}; use crate::{ ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_ACCESS_PLATFORM, - VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioSharedMemoryList, + VirtioCommon, VirtioDevice, VirtioSharedMemoryList, }; pub type State = VhostUserState<()>; @@ -308,8 +308,8 @@ impl VirtioDevice for GenericVhostUser { Ok(()) } - fn reset(&mut self) -> Option> { - self.vu_common.reset(&self.id) + fn reset(&mut self) { + self.vu_common.reset(&self.id); } fn shutdown(&mut self) { diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 07c8d4555a..9dd3d7f79a 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -347,7 +347,6 @@ pub struct VhostUserCommon { pub vu_num_queues: usize, pub migration_started: bool, pub server: bool, - pub interrupt_cb: Option>, pub vring_bases: Option>, pub epoll_thread: Option>, } @@ -395,8 +394,6 @@ impl VhostUserCommon { ) .map_err(ActivateError::VhostUserSetup)?; - self.interrupt_cb = Some(interrupt_cb.clone()); - Ok(VhostUserEpollHandler { vu: vu.clone(), mem, @@ -428,7 +425,7 @@ impl VhostUserCommon { Ok(()) } - pub fn reset(&mut self, id: &str) -> Option> { + pub fn reset(&mut self, id: &str) { // Resume the virtio thread if it was paused. Reset must always // converge to fresh state, so backend resume / reset failures are // logged but don't skip the rest of the teardown. @@ -454,8 +451,8 @@ impl VhostUserCommon { event!("virtio-device", "reset", "id", id); - // Return the interrupt - Some(self.virtio_common.interrupt_cb.take().unwrap()) + // Drop the interrupt callback clone + self.virtio_common.interrupt_cb = None; } pub fn shutdown(&mut self) { @@ -525,12 +522,10 @@ impl VhostUserCommon { MigratableError::Resume(anyhow!("Error resuming vhost-user backend: {e:?}")) })?; } - if let Some(interrupt_cb) = &self.interrupt_cb { - for i in 0..self.vu_num_queues { - interrupt_cb - .trigger(crate::VirtioInterruptType::Queue(i as u16)) - .ok(); - } + for i in 0..self.vu_num_queues { + self.virtio_common + .trigger_interrupt(crate::VirtioInterruptType::Queue(i as u16)) + .ok(); } Ok(()) } diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 8563a21240..d77c80a513 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -29,7 +29,7 @@ use crate::vhost_user::vu_common_ctrl::{VhostUserConfig, VhostUserHandle}; use crate::vhost_user::{DEFAULT_VIRTIO_FEATURES, Error, Result, VhostUserCommon, VhostUserState}; use crate::{ ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, - VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, + VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioDeviceType, }; const DEFAULT_QUEUE_NUMBER: usize = 2; @@ -364,8 +364,8 @@ impl VirtioDevice for Net { Ok(()) } - fn reset(&mut self) -> Option> { - self.vu_common.reset(&self.id) + fn reset(&mut self) { + self.vu_common.reset(&self.id); } fn shutdown(&mut self) { diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index c20288c2dc..1d5985037a 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -488,10 +488,9 @@ where Ok(()) } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); event!("virtio-device", "reset", "id", &self.id); - result } fn shutdown(&mut self) { diff --git a/virtio-devices/src/watchdog.rs b/virtio-devices/src/watchdog.rs index 742a2e0241..e362532e64 100644 --- a/virtio-devices/src/watchdog.rs +++ b/virtio-devices/src/watchdog.rs @@ -379,10 +379,9 @@ impl VirtioDevice for Watchdog { Ok(()) } - fn reset(&mut self) -> Option> { - let result = self.common.reset(); + fn reset(&mut self) { + self.common.reset(); event!("virtio-device", "reset", "id", &self.id); - result } } From 85012fbe5cb85aa793e23f75d2590c824f5fe0e0 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 26 Apr 2026 11:39:06 +0100 Subject: [PATCH 1407/1893] 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 --- virtio-devices/src/transport/pci_device.rs | 6 +++++- vmm/src/device_manager.rs | 6 ------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 41743e419e..12f689ab7c 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -403,7 +403,6 @@ impl VirtioPciDevice { id: String, memory: GuestMemoryAtomic, device: Arc>, - msix_num: u16, access_platform: Option<&Arc>, interrupt_manager: &dyn InterruptManager, pci_device_bdf: u32, @@ -434,6 +433,11 @@ impl VirtioPciDevice { let pci_device_id = VIRTIO_PCI_DEVICE_ID_BASE + locked_device.device_type() as u16; + // Allows support for one MSI-X vector per interrupt needed by the device. + // It also adds 1 as we need to take into account the dedicated vector to notify + // about a virtio config change. + let msix_num = (locked_device.queue_max_sizes().len() + 1) as u16; + let interrupt_source_group: MaybeMutInterruptSourceGroup = { let config = MsiIrqGroupConfig { base: 0, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 8a693c08ff..45dc9a2eff 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4172,11 +4172,6 @@ impl DeviceManager { return Err(DeviceManagerError::MissingNode); } - // Allows support for one MSI-X vector per interrupt needed by the device. - // It also adds 1 as we need to take into account the dedicated vector to notify - // about a virtio config change. - let msix_num = (virtio_device.lock().unwrap().queue_max_sizes().len() + 1) as u16; - // Create the AccessPlatform trait from the implementation IommuMapping. // This will provide address translation for any virtio device sitting // behind a vIOMMU. @@ -4245,7 +4240,6 @@ impl DeviceManager { id.clone(), memory, virtio_device, - msix_num, access_platform.as_ref(), self.msi_interrupt_manager.as_ref(), pci_device_bdf.into(), From 02b9b67fc2b4eab1ac5db6addf91b1b4b42086d0 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 26 Apr 2026 11:41:52 +0100 Subject: [PATCH 1408/1893] 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 --- virtio-devices/src/transport/pci_device.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 12f689ab7c..2c2620940c 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -465,7 +465,7 @@ impl VirtioPciDevice { )) })?; - let (msix_config, msix_config_clone) = if msix_num > 0 { + let (msix_config, msix_config_clone) = { let interrupt_source_group: MaybeMutInterruptSourceGroup = interrupt_source_group.clone(); let msix_config = Arc::new(Mutex::new( @@ -473,9 +473,7 @@ impl VirtioPciDevice { .unwrap(), )); let msix_config_clone = msix_config.clone(); - (Some(msix_config), Some(msix_config_clone)) - } else { - (None, None) + (msix_config, msix_config_clone) }; let (class, subclass) = match VirtioDeviceType::from(locked_device.device_type()) { @@ -510,7 +508,7 @@ impl VirtioPciDevice { PciHeaderType::Device, VIRTIO_PCI_VENDOR_ID, pci_device_id, - msix_config_clone, + Some(msix_config_clone), pci_configuration_state, ); @@ -598,7 +596,7 @@ impl VirtioPciDevice { id, configuration, common_config, - msix_config, + msix_config: Some(msix_config), msix_num, device, device_activated: Arc::new(AtomicBool::new(device_activated)), From 760610a4328cfb94586ce6bda5a44090b9a769bb Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 26 Apr 2026 11:51:34 +0100 Subject: [PATCH 1409/1893] 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 --- virtio-devices/src/transport/pci_device.rs | 84 ++++++++++------------ 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 2c2620940c..be8d61138f 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -348,7 +348,7 @@ pub struct VirtioPciDevice { common_config: VirtioPciCommonConfig, // MSI-X config - msix_config: Option>>, + msix_config: Arc>, // Number of MSI-X vectors msix_num: u16, @@ -596,7 +596,7 @@ impl VirtioPciDevice { id, configuration, common_config, - msix_config: Some(msix_config), + msix_config, msix_num, device, device_activated: Arc::new(AtomicBool::new(device_activated)), @@ -615,14 +615,12 @@ impl VirtioPciDevice { pending_activations, }; - if let Some(msix_config) = &virtio_pci_device.msix_config { - virtio_pci_device.virtio_interrupt = Some(Arc::new(VirtioInterruptMsix::new( - msix_config.clone(), - virtio_pci_device.common_config.msix_config.clone(), - virtio_pci_device.common_config.msix_queues.clone(), - virtio_pci_device.interrupt_source_group.clone(), - ))); - } + virtio_pci_device.virtio_interrupt = Some(Arc::new(VirtioInterruptMsix::new( + virtio_pci_device.msix_config.clone(), + virtio_pci_device.common_config.msix_config.clone(), + virtio_pci_device.common_config.msix_queues.clone(), + virtio_pci_device.interrupt_source_group.clone(), + ))); // In case of a restore, we can activate the device, as we know at // this point the virtqueues are in the right state and the device is @@ -739,18 +737,16 @@ impl VirtioPciDevice { + VIRTIO_PCI_CAP_OFFSET; self.cap_pci_cfg_info.cap = configuration_cap; - if self.msix_config.is_some() { - let msix_cap = MsixCap::new( - settings_bar, - self.msix_num, - MSIX_TABLE_BAR_OFFSET as u32, - settings_bar, - MSIX_PBA_BAR_OFFSET as u32, - ); - self.configuration - .add_capability(&msix_cap) - .map_err(PciDeviceError::CapabilitiesSetup)?; - } + let msix_cap = MsixCap::new( + settings_bar, + self.msix_num, + MSIX_TABLE_BAR_OFFSET as u32, + settings_bar, + MSIX_PBA_BAR_OFFSET as u32, + ); + self.configuration + .add_capability(&msix_cap) + .map_err(PciDeviceError::CapabilitiesSetup)?; self.settings_bar = settings_bar; Ok(()) @@ -1163,20 +1159,16 @@ impl PciDevice for VirtioPciDevice { // Handled with ioeventfds. } o if (MSIX_TABLE_BAR_OFFSET..MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE).contains(&o) => { - if let Some(msix_config) = &self.msix_config { - msix_config - .lock() - .unwrap() - .read_table(o - MSIX_TABLE_BAR_OFFSET, data); - } + self.msix_config + .lock() + .unwrap() + .read_table(o - MSIX_TABLE_BAR_OFFSET, data); } o if (MSIX_PBA_BAR_OFFSET..MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE).contains(&o) => { - if let Some(msix_config) = &self.msix_config { - msix_config - .lock() - .unwrap() - .read_pba(o - MSIX_PBA_BAR_OFFSET, data); - } + self.msix_config + .lock() + .unwrap() + .read_pba(o - MSIX_PBA_BAR_OFFSET, data); } _ => (), } @@ -1215,20 +1207,16 @@ impl PciDevice for VirtioPciDevice { error!("Unexpected write to notification BAR: offset = 0x{o:x}"); } o if (MSIX_TABLE_BAR_OFFSET..MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE).contains(&o) => { - if let Some(msix_config) = &self.msix_config { - msix_config - .lock() - .unwrap() - .write_table(o - MSIX_TABLE_BAR_OFFSET, data); - } + self.msix_config + .lock() + .unwrap() + .write_table(o - MSIX_TABLE_BAR_OFFSET, data); } o if (MSIX_PBA_BAR_OFFSET..MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE).contains(&o) => { - if let Some(msix_config) = &self.msix_config { - msix_config - .lock() - .unwrap() - .write_pba(o - MSIX_PBA_BAR_OFFSET, data); - } + self.msix_config + .lock() + .unwrap() + .write_pba(o - MSIX_PBA_BAR_OFFSET, data); } _ => (), } @@ -1309,8 +1297,8 @@ impl Snapshottable for VirtioPciDevice { .add_snapshot(self.common_config.id(), self.common_config.snapshot()?); // Snapshot MSI-X - if let Some(msix_config) = &self.msix_config { - let mut msix_config = msix_config.lock().unwrap(); + { + let mut msix_config = self.msix_config.lock().unwrap(); virtio_pci_dev_snapshot.add_snapshot(msix_config.id(), msix_config.snapshot()?); } From b59501e0414740eefd632b9b2d67f6059d956436 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 26 Apr 2026 11:55:06 +0100 Subject: [PATCH 1410/1893] 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 --- virtio-devices/src/transport/pci_device.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index be8d61138f..833836d774 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -359,8 +359,7 @@ pub struct VirtioPciDevice { // PCI interrupts. interrupt_status: Arc, - virtio_interrupt: Option>, - interrupt_source_group: MaybeMutInterruptSourceGroup, + virtio_interrupt: Arc, // virtio queues queues: Vec, @@ -592,6 +591,13 @@ impl VirtioPciDevice { // prevents from a subtle deadlock. std::mem::drop(locked_device); + let virtio_interrupt = Arc::new(VirtioInterruptMsix::new( + msix_config.clone(), + common_config.msix_config.clone(), + common_config.msix_queues.clone(), + interrupt_source_group.clone(), + )); + let mut virtio_pci_device = VirtioPciDevice { id, configuration, @@ -601,13 +607,12 @@ impl VirtioPciDevice { device, device_activated: Arc::new(AtomicBool::new(device_activated)), interrupt_status: Arc::new(AtomicUsize::new(interrupt_status)), - virtio_interrupt: None, + virtio_interrupt, queues, queue_evts, memory, settings_bar: 0, use_64bit_bar, - interrupt_source_group: interrupt_source_group.clone(), cap_pci_cfg_info, bar_regions: vec![], activate_evt, @@ -615,13 +620,6 @@ impl VirtioPciDevice { pending_activations, }; - virtio_pci_device.virtio_interrupt = Some(Arc::new(VirtioInterruptMsix::new( - virtio_pci_device.msix_config.clone(), - virtio_pci_device.common_config.msix_config.clone(), - virtio_pci_device.common_config.msix_queues.clone(), - virtio_pci_device.interrupt_source_group.clone(), - ))); - // In case of a restore, we can activate the device, as we know at // this point the virtqueues are in the right state and the device is // ready to be activated, which will spawn each virtio worker thread. @@ -820,7 +818,7 @@ impl VirtioPciDevice { } VirtioPciDeviceActivator { - interrupt: self.virtio_interrupt.as_ref().unwrap().clone(), + interrupt: self.virtio_interrupt.clone(), memory: Some(self.memory.clone()), device: self.device.clone(), queues: Some(queues), From 27ee36449cc86dcae3dca16f2df51ed56153807b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 16:40:49 +0200 Subject: [PATCH 1411/1893] 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 --- block/src/lib.rs | 1 + block/src/qcow_async.rs | 2 +- block/src/qcow_disk.rs | 188 ++++++++++++++++++++++++++++++++++++++++ block/src/qcow_sync.rs | 2 +- 4 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 block/src/qcow_disk.rs diff --git a/block/src/lib.rs b/block/src/lib.rs index cd87875d52..d86b40129c 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -23,6 +23,7 @@ pub mod qcow; #[cfg(feature = "io_uring")] pub mod qcow_async; pub(crate) mod qcow_common; +pub mod qcow_disk; pub mod qcow_sync; #[cfg(feature = "io_uring")] pub(crate) mod raw_async; diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 8f58b4ba1d..47e15eabe4 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -186,7 +186,7 @@ pub struct QcowAsync { } impl QcowAsync { - fn new( + pub(crate) fn new( metadata: Arc, data_file: QcowRawFile, backing_file: Option>, diff --git a/block/src/qcow_disk.rs b/block/src/qcow_disk.rs new file mode 100644 index 0000000000..effea82af7 --- /dev/null +++ b/block/src/qcow_disk.rs @@ -0,0 +1,188 @@ +// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use std::fs::File; +use std::os::unix::io::AsRawFd; +use std::sync::Arc; +use std::{fmt, io}; + +use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError}; +use crate::disk_file; +use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; +use crate::qcow::backing::shared_backing_from; +use crate::qcow::metadata::{BackingRead, QcowMetadata}; +use crate::qcow::qcow_raw_file::QcowRawFile; +use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; +#[cfg(feature = "io_uring")] +use crate::qcow_async::QcowAsync; +use crate::qcow_sync::QcowSync; + +/// 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, + backing_file: Option>, + 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 { + #[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 = RawFile::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, + }) + } +} + +impl Drop for QcowDisk { + fn drop(&mut self) { + self.metadata.shutdown(); + } +} + +impl disk_file::DiskSize for QcowDisk { + fn logical_size(&self) -> BlockResult { + Ok(self.metadata.virtual_size()) + } +} + +impl disk_file::PhysicalSize for QcowDisk { + fn physical_size(&self) -> BlockResult { + 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> { + 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> { + 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, + ))) + } +} diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 4d722728ca..8bbe01a97f 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -165,7 +165,7 @@ pub struct QcowSync { } impl QcowSync { - fn new( + pub(crate) fn new( metadata: Arc, data_file: QcowRawFile, backing_file: Option>, From 65c98ca157a95eb1455cd8cd5d423b3b5108d100 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 17:05:18 +0200 Subject: [PATCH 1412/1893] 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 --- performance-metrics/src/micro_bench_block.rs | 4 +- performance-metrics/src/util.rs | 115 ++++++++++++------- 2 files changed, 75 insertions(+), 44 deletions(-) diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 6dbdee92fb..604e953f69 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -334,8 +334,8 @@ pub fn micro_bench_qcow_l2_cache_miss(control: &PerformanceTestControl) -> f64 { /// Read num_ops clusters from a prepopulated qcow2 image through the /// QcowAsync io_uring path and time the total wall clock. /// -/// Unlike micro_bench_qcow_read which uses QcowDiskSync (blocking), -/// this uses QcowDiskAsync where single-allocated-cluster reads go +/// Unlike micro_bench_qcow_read which uses the synchronous backend, +/// this uses the io_uring backend where single allocated cluster reads go /// through io_uring for true asynchronous completion. /// /// Returns the total read wall clock time in seconds. diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 3f68fdd7eb..0f1d4e1b24 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -13,8 +13,7 @@ use std::time::Duration; use block::async_io::AsyncIo; use block::qcow::{BackingFileConfig, ImageType, QcowFile, RawFile}; -use block::qcow_async::QcowDiskAsync; -use block::qcow_sync::QcowDiskSync; +use block::qcow_disk::QcowDisk; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::tempfile::TempFile; @@ -52,20 +51,26 @@ fn create_qcow_tempfile(num_clusters: usize) -> TempFile { } /// Create a QCOW2 image with `num_clusters` allocated clusters opened -/// via `QcowDiskSync` (blocking I/O backend). -pub fn qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) { +/// via QcowDisk with synchronous backend. +pub fn qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDisk) { let tmp = create_qcow_tempfile(num_clusters); - let disk = QcowDiskSync::new(tmp.as_file().try_clone().unwrap(), false, false, true) - .expect("failed to open QCOW2 via QcowDiskSync"); + let disk = QcowDisk::new( + tmp.as_file().try_clone().unwrap(), + false, + false, + true, + false, + ) + .expect("failed to open QCOW2 via QcowDisk"); (tmp, disk) } /// Create a QCOW2 image with `num_clusters` allocated clusters opened -/// via `QcowDiskAsync` (io_uring backend). -pub fn qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDiskAsync) { +/// via QcowDisk with io_uring backend. +pub fn qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDisk) { let tmp = create_qcow_tempfile(num_clusters); - let disk = QcowDiskAsync::new(tmp.as_file().try_clone().unwrap(), false, false, true) - .expect("failed to open QCOW2 via QcowDiskAsync"); + let disk = QcowDisk::new(tmp.as_file().try_clone().unwrap(), false, false, true, true) + .expect("failed to open QCOW2 via QcowDisk"); (tmp, disk) } @@ -150,19 +155,25 @@ fn create_empty_qcow_tempfile(num_clusters: usize) -> TempFile { tmp } -/// Empty QCOW2 opened via QcowDiskSync. -pub fn empty_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) { +/// Empty QCOW2 opened via QcowDisk with synchronous backend. +pub fn empty_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDisk) { let tmp = create_empty_qcow_tempfile(num_clusters); - let disk = QcowDiskSync::new(tmp.as_file().try_clone().unwrap(), false, false, true) - .expect("failed to open qcow2 via QcowDiskSync"); + let disk = QcowDisk::new( + tmp.as_file().try_clone().unwrap(), + false, + false, + true, + false, + ) + .expect("failed to open QCOW2 via QcowDisk"); (tmp, disk) } -/// Empty QCOW2 opened via QcowDiskAsync. -pub fn empty_qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDiskAsync) { +/// Empty QCOW2 opened via QcowDisk with io_uring backend. +pub fn empty_qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDisk) { let tmp = create_empty_qcow_tempfile(num_clusters); - let disk = QcowDiskAsync::new(tmp.as_file().try_clone().unwrap(), false, false, true) - .expect("failed to open qcow2 via QcowDiskAsync"); + let disk = QcowDisk::new(tmp.as_file().try_clone().unwrap(), false, false, true, true) + .expect("failed to open QCOW2 via QcowDisk"); (tmp, disk) } @@ -196,19 +207,31 @@ fn create_overlay_tempfiles(num_clusters: usize) -> (TempFile, TempFile) { (backing, overlay) } -/// QCOW2 overlay with raw backing opened via QcowDiskSync. -pub fn qcow_overlay_tempfile(num_clusters: usize) -> (TempFile, TempFile, QcowDiskSync) { +/// QCOW2 overlay with raw backing opened via QcowDisk with synchronous backend. +pub fn qcow_overlay_tempfile(num_clusters: usize) -> (TempFile, TempFile, QcowDisk) { let (backing, overlay) = create_overlay_tempfiles(num_clusters); - let disk = QcowDiskSync::new(overlay.as_file().try_clone().unwrap(), false, true, true) - .expect("failed to open overlay qcow2 via QcowDiskSync"); + let disk = QcowDisk::new( + overlay.as_file().try_clone().unwrap(), + false, + true, + true, + false, + ) + .expect("failed to open overlay QCOW2 via QcowDisk"); (backing, overlay, disk) } -/// QCOW2 overlay with raw backing opened via QcowDiskAsync. -pub fn qcow_async_overlay_tempfile(num_clusters: usize) -> (TempFile, TempFile, QcowDiskAsync) { +/// QCOW2 overlay with raw backing opened via QcowDisk with io_uring backend. +pub fn qcow_async_overlay_tempfile(num_clusters: usize) -> (TempFile, TempFile, QcowDisk) { let (backing, overlay) = create_overlay_tempfiles(num_clusters); - let disk = QcowDiskAsync::new(overlay.as_file().try_clone().unwrap(), false, true, true) - .expect("failed to open overlay qcow2 via QcowDiskAsync"); + let disk = QcowDisk::new( + overlay.as_file().try_clone().unwrap(), + false, + true, + true, + true, + ) + .expect("failed to open overlay QCOW2 via QcowDisk"); (backing, overlay, disk) } @@ -251,31 +274,33 @@ fn create_compressed_qcow_tempfile(num_clusters: usize) -> TempFile { qcow_tmp } -/// Compressed QCOW2 opened via QcowDiskSync. -pub fn compressed_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDiskSync) { +/// Compressed QCOW2 opened via QcowDisk with synchronous backend. +pub fn compressed_qcow_tempfile(num_clusters: usize) -> (TempFile, QcowDisk) { let tmp = create_compressed_qcow_tempfile(num_clusters); let path = tmp.as_path().to_str().unwrap().to_string(); - let disk = QcowDiskSync::new( + let disk = QcowDisk::new( File::open(&path).expect("failed to open compressed qcow2"), false, false, true, + false, ) - .expect("failed to open compressed qcow2 via QcowDiskSync"); + .expect("failed to open compressed QCOW2 via QcowDisk"); (tmp, disk) } -/// Compressed QCOW2 opened via QcowDiskAsync. -pub fn compressed_qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDiskAsync) { +/// Compressed QCOW2 opened via QcowDisk with io_uring backend. +pub fn compressed_qcow_async_tempfile(num_clusters: usize) -> (TempFile, QcowDisk) { let tmp = create_compressed_qcow_tempfile(num_clusters); let path = tmp.as_path().to_str().unwrap().to_string(); - let disk = QcowDiskAsync::new( + let disk = QcowDisk::new( File::open(&path).expect("failed to open compressed qcow2"), false, false, true, + true, ) - .expect("failed to open compressed qcow2 via QcowDiskAsync"); + .expect("failed to open compressed QCOW2 via QcowDisk"); (tmp, disk) } @@ -300,19 +325,25 @@ fn create_sparse_qcow_tempfile(num_l2_tables: usize) -> TempFile { tmp } -/// Sparse QCOW2 opened via QcowDiskSync. -pub fn sparse_qcow_tempfile(num_l2_tables: usize) -> (TempFile, QcowDiskSync) { +/// Sparse QCOW2 opened via QcowDisk with synchronous backend. +pub fn sparse_qcow_tempfile(num_l2_tables: usize) -> (TempFile, QcowDisk) { let tmp = create_sparse_qcow_tempfile(num_l2_tables); - let disk = QcowDiskSync::new(tmp.as_file().try_clone().unwrap(), false, false, true) - .expect("failed to open qcow2 via QcowDiskSync"); + let disk = QcowDisk::new( + tmp.as_file().try_clone().unwrap(), + false, + false, + true, + false, + ) + .expect("failed to open QCOW2 via QcowDisk"); (tmp, disk) } -/// Sparse QCOW2 opened via QcowDiskAsync. -pub fn sparse_qcow_async_tempfile(num_l2_tables: usize) -> (TempFile, QcowDiskAsync) { +/// Sparse QCOW2 opened via QcowDisk with io_uring backend. +pub fn sparse_qcow_async_tempfile(num_l2_tables: usize) -> (TempFile, QcowDisk) { let tmp = create_sparse_qcow_tempfile(num_l2_tables); - let disk = QcowDiskAsync::new(tmp.as_file().try_clone().unwrap(), false, false, true) - .expect("failed to open qcow2 via QcowDiskAsync"); + let disk = QcowDisk::new(tmp.as_file().try_clone().unwrap(), false, false, true, true) + .expect("failed to open QCOW2 via QcowDisk"); (tmp, disk) } From 1e4516fa5d0ec0bcf04ba1ea6d66ee3c924cd3a9 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 24 Apr 2026 10:24:05 +0200 Subject: [PATCH 1413/1893] 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 --- block/src/factory.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/block/src/factory.rs b/block/src/factory.rs index 2d59f68724..ffe65f7d9f 100644 --- a/block/src/factory.rs +++ b/block/src/factory.rs @@ -21,9 +21,7 @@ use crate::block_io_uring_is_supported; use crate::disk_file::AsyncFullDiskFile; use crate::error::{BlockError, BlockErrorKind, BlockResult}; use crate::fixed_vhd_disk::FixedVhdDisk; -#[cfg(feature = "io_uring")] -use crate::qcow_async::QcowDiskAsync; -use crate::qcow_sync::QcowDiskSync; +use crate::qcow_disk::QcowDisk; use crate::raw_disk::{RawBackend, RawDisk}; use crate::vhdx_sync::VhdxDiskSync; use crate::{ @@ -179,8 +177,14 @@ fn open_qcow2( if io_uring_supported() { info!("Opening QCOW2 disk file with io_uring backend"); return Ok(Box::new( - QcowDiskAsync::new(file, options.direct, options.backing_files, options.sparse) - .map_err(|e| e.with_path(options.path))?, + 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"); @@ -188,8 +192,14 @@ fn open_qcow2( info!("Opening QCOW2 disk file with synchronous backend"); Ok(Box::new( - QcowDiskSync::new(file, options.direct, options.backing_files, options.sparse) - .map_err(|e| e.with_path(options.path))?, + QcowDisk::new( + file, + options.direct, + options.backing_files, + options.sparse, + false, + ) + .map_err(|e| e.with_path(options.path))?, )) } From 8a77feb813ca49566f964eb989d746e70755ed73 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 24 Apr 2026 10:29:19 +0200 Subject: [PATCH 1414/1893] 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 --- block/src/qcow_async.rs | 80 ++++++++++++++++++++++++++++++++--------- block/src/qcow_sync.rs | 54 +++++++++++++++++----------- 2 files changed, 97 insertions(+), 37 deletions(-) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 47e15eabe4..8da8b1ebee 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -689,6 +689,7 @@ mod unit_tests { use crate::disk_file::AsyncDiskFile; use crate::qcow::{QcowFile, RawFile}; use crate::qcow_common::unit_tests::compress_allocated_clusters; + use crate::qcow_disk::QcowDisk; use crate::{BatchRequest, RequestType, SECTOR_SIZE}; fn create_disk_with_data( @@ -696,7 +697,7 @@ mod unit_tests { data: &[u8], offset: u64, sparse: bool, - ) -> (TempFile, QcowDiskAsync) { + ) -> (TempFile, QcowDisk) { let temp_file = TempFile::new().unwrap(); { let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); @@ -705,11 +706,12 @@ mod unit_tests { qcow_file.write_all(data).unwrap(); qcow_file.flush().unwrap(); } - let disk = QcowDiskAsync::new( + let disk = QcowDisk::new( temp_file.as_file().try_clone().unwrap(), false, false, sparse, + true, ) .unwrap(); (temp_file, disk) @@ -730,7 +732,7 @@ mod unit_tests { } } - fn async_write(disk: &QcowDiskAsync, offset: u64, data: &[u8]) { + fn async_write(disk: &QcowDisk, offset: u64, data: &[u8]) { let mut async_io = disk.create_async_io(1).unwrap(); let iovec = libc::iovec { iov_base: data.as_ptr() as *mut libc::c_void, @@ -748,7 +750,7 @@ mod unit_tests { ); } - fn async_read(disk: &QcowDiskAsync, offset: u64, len: usize) -> Vec { + fn async_read(disk: &QcowDisk, offset: u64, len: usize) -> Vec { let mut async_io = disk.create_async_io(1).unwrap(); let mut buf = vec![0xFFu8; len]; let iovec = libc::iovec { @@ -814,8 +816,14 @@ mod unit_tests { let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); QcowFile::new(raw_file, 3, file_size, true).unwrap(); } - let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) - .unwrap(); + let disk = QcowDisk::new( + temp_file.as_file().try_clone().unwrap(), + false, + false, + true, + true, + ) + .unwrap(); let pattern: Vec = (0..128 * 1024).map(|i| (i % 251) as u8).collect(); let offset = 64 * 1024; @@ -859,8 +867,14 @@ mod unit_tests { let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); QcowFile::new(raw_file, 3, file_size, true).unwrap(); } - let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) - .unwrap(); + let disk = QcowDisk::new( + temp_file.as_file().try_clone().unwrap(), + false, + false, + true, + true, + ) + .unwrap(); let mut async_io = disk.create_async_io(8).unwrap(); @@ -955,8 +969,14 @@ mod unit_tests { let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); QcowFile::new(raw_file, 3, file_size, true).unwrap(); } - let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) - .unwrap(); + let disk = QcowDisk::new( + temp_file.as_file().try_clone().unwrap(), + false, + false, + true, + true, + ) + .unwrap(); let buf = async_read(&disk, 0, 128 * 1024); assert!( @@ -974,8 +994,14 @@ mod unit_tests { let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); QcowFile::new(raw_file, 3, file_size, true).unwrap(); } - let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) - .unwrap(); + let disk = QcowDisk::new( + temp_file.as_file().try_clone().unwrap(), + false, + false, + true, + true, + ) + .unwrap(); // Write 4K into the middle of a cluster. let write_offset = 4096u64; @@ -1063,19 +1089,32 @@ mod unit_tests { let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); QcowFile::new(raw_file, 3, file_size, true).unwrap(); } - let disk = QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), false, false, true) - .unwrap(); + let disk = QcowDisk::new( + temp_file.as_file().try_clone().unwrap(), + false, + false, + true, + true, + ) + .unwrap(); let async_io = disk.create_async_io(1).unwrap(); assert_eq!(async_io.alignment(), SECTOR_SIZE); } /// Returns None if O_DIRECT is not supported (e.g. tmpfs). - fn try_create_direct_io_disk(temp_file: &TempFile, file_size: u64) -> Option { + fn try_create_direct_io_disk(temp_file: &TempFile, file_size: u64) -> Option { { let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); QcowFile::new(raw_file, 3, file_size, true).unwrap(); } - QcowDiskAsync::new(temp_file.as_file().try_clone().unwrap(), true, false, true).ok() + QcowDisk::new( + temp_file.as_file().try_clone().unwrap(), + true, + false, + true, + true, + ) + .ok() } #[test] @@ -1141,7 +1180,14 @@ mod unit_tests { compress_allocated_clusters(&mut temp.as_file().try_clone().unwrap()); let disk = Arc::new( - QcowDiskAsync::new(temp.as_file().try_clone().unwrap(), false, false, false).unwrap(), + QcowDisk::new( + temp.as_file().try_clone().unwrap(), + false, + false, + false, + true, + ) + .unwrap(), ); let handles: Vec<_> = (0..4) diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 8bbe01a97f..53701cfd4d 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -449,6 +449,7 @@ mod unit_tests { use crate::disk_file::{AsyncDiskFile, DiskSize, Resizable}; use crate::qcow::{BackingFileConfig, ImageType, QcowFile, RawFile}; use crate::qcow_common::unit_tests::compress_allocated_clusters; + use crate::qcow_disk::QcowDisk; fn create_disk_with_data( file_size: u64, @@ -456,7 +457,7 @@ mod unit_tests { offset: u64, sparse: bool, direct_io: bool, - ) -> (TempFile, QcowDiskSync) { + ) -> (TempFile, QcowDisk) { let temp_file = TempFile::new().unwrap(); { let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); @@ -465,17 +466,18 @@ mod unit_tests { qcow_file.write_all(data).unwrap(); qcow_file.flush().unwrap(); } - let disk = QcowDiskSync::new( + let disk = QcowDisk::new( temp_file.as_file().try_clone().unwrap(), direct_io, false, sparse, + false, ) .unwrap(); (temp_file, disk) } - fn async_read(disk: &QcowDiskSync, offset: u64, len: usize) -> Vec { + fn async_read(disk: &QcowDisk, offset: u64, len: usize) -> Vec { let mut async_io = disk.create_async_io(1).unwrap(); let mut buf = vec![0xFFu8; len]; let iovec = libc::iovec { @@ -491,7 +493,7 @@ mod unit_tests { buf } - fn async_write(disk: &QcowDiskSync, offset: u64, data: &[u8]) { + fn async_write(disk: &QcowDisk, offset: u64, data: &[u8]) { let mut async_io = disk.create_async_io(1).unwrap(); let iovec = libc::iovec { iov_base: data.as_ptr() as *mut libc::c_void, @@ -565,8 +567,14 @@ mod unit_tests { qcow_file.flush().unwrap(); } - let disk = - QcowDiskSync::new(_temp.as_file().try_clone().unwrap(), false, false, true).unwrap(); + let disk = QcowDisk::new( + _temp.as_file().try_clone().unwrap(), + false, + false, + true, + false, + ) + .unwrap(); let mut async_io = disk.create_async_io(1).unwrap(); @@ -733,7 +741,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); // Read first cluster - should come from backing file let buf = async_read(&disk, 0, cluster_size as usize); @@ -804,7 +812,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); // Read first cluster - should come from QCOW2 backing let buf = async_read(&disk, 0, cluster_size as usize); @@ -927,7 +935,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = Arc::new(QcowDiskSync::new(file, direct_io, true, true).unwrap()); + let disk = Arc::new(QcowDisk::new(file, direct_io, true, true, false).unwrap()); let threads: Vec<_> = (0..8) .map(|t| { @@ -1010,7 +1018,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); // Cluster 0: mid wrote 0xBB let buf = async_read(&disk, 0, cluster_size as usize); @@ -1085,7 +1093,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); let written = vec![0xFFu8; cluster_size as usize]; for &idx in &[0u64, 3, 7] { @@ -1157,7 +1165,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); // Read cluster 2 (past backing virtual_size) - should be zeros let buf = async_read(&disk, backing_size, cluster_size as usize); @@ -1207,7 +1215,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); // Read 2 clusters starting at cluster 1 (spans backing boundary) let read_len = cluster_size as usize * 2; @@ -1260,7 +1268,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); // Read cluster 2 (past backing size) - should be zeros let buf = async_read(&disk, backing_size, cluster_size as usize); @@ -1321,7 +1329,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); // Read spanning clusters 1-2 boundary: 512 bytes before + 512 after let mid = cluster_size - 512; @@ -1378,7 +1386,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); let written = vec![0xFFu8; cluster_size as usize]; async_write(&disk, 0, &written); @@ -1488,7 +1496,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let disk = QcowDiskSync::new(file, direct_io, true, true).unwrap(); + let disk = QcowDisk::new(file, direct_io, true, true, false).unwrap(); // Write 4KB at offset 4KB within cluster 0 (partial cluster) let write_offset = 4096u64; @@ -1648,7 +1656,7 @@ mod unit_tests { } let file = overlay_temp.as_file().try_clone().unwrap(); - let mut disk = QcowDiskSync::new(file, false, true, true).unwrap(); + let mut disk = QcowDisk::new(file, false, true, true, false).unwrap(); assert_eq!(disk.logical_size().unwrap(), file_size); let result = disk.resize(file_size * 2); @@ -1944,8 +1952,14 @@ mod unit_tests { compress_allocated_clusters(&mut temp.as_file().try_clone().unwrap()); - let disk = - QcowDiskSync::new(temp.as_file().try_clone().unwrap(), false, false, false).unwrap(); + let disk = QcowDisk::new( + temp.as_file().try_clone().unwrap(), + false, + false, + false, + false, + ) + .unwrap(); let buf = async_read(&disk, 0, cluster_size); assert_eq!(buf, data); From 587093ddfd400767a3183b12ab471983bd10896d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 24 Apr 2026 10:32:05 +0200 Subject: [PATCH 1415/1893] 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 --- block/src/lib.rs | 4 +- block/src/qcow/mod.rs | 3 +- block/src/qcow_async.rs | 146 +++------------------------------------- block/src/qcow_sync.rs | 134 +----------------------------------- 4 files changed, 14 insertions(+), 273 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index d86b40129c..a58c445ef9 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -21,10 +21,10 @@ pub mod fixed_vhd_disk; pub mod fixed_vhd_sync; pub mod qcow; #[cfg(feature = "io_uring")] -pub mod qcow_async; +pub(crate) mod qcow_async; pub(crate) mod qcow_common; pub mod qcow_disk; -pub mod qcow_sync; +pub(crate) mod qcow_sync; #[cfg(feature = "io_uring")] pub(crate) mod raw_async; pub(crate) mod raw_async_aio; diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 32c2b06487..b9a63d1f95 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -372,8 +372,7 @@ impl Debug for BackingFile { /// Parses and validates a QCOW2 image file, returning the metadata, backing /// file and sparse flag. /// -/// This shared constructor is used by both QcowFile for sequential I/O -/// and QcowDiskSync for lock based parallel I/O. +/// Used by [`QcowFile`] and [`QcowDisk`] constructors. pub(crate) fn parse_qcow( mut file: RawFile, max_nesting_depth: u32, diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index 8da8b1ebee..bf435105f1 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -8,157 +8,25 @@ use std::cmp::{max, min}; use std::collections::VecDeque; -use std::fs::File; -use std::io::Error; -use std::os::fd::{AsFd, AsRawFd}; +use std::io; +use std::os::unix::io::AsRawFd; use std::sync::Arc; -use std::{fmt, io}; use io_uring::{IoUring, opcode, types}; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; -use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; -use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; -use crate::qcow::backing::shared_backing_from; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; use crate::qcow::decoder::Decoder; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, }; use crate::qcow::qcow_raw_file::QcowRawFile; -use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; use crate::qcow_common::{ AlignedBuf, aligned_pread, aligned_pwrite, decompress_cluster, gather_from_iovecs_into, pread_alloc, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; -use crate::{BatchRequest, RequestType, SECTOR_SIZE, disk_file}; - -/// Device level handle for a QCOW2 image. -/// -/// Owns the parsed metadata and backing file chain. One instance is -/// created per disk and shared across virtio queues. -pub struct QcowDiskAsync { - metadata: Arc, - backing_file: Option>, - sparse: bool, - data_raw_file: QcowRawFile, -} - -impl fmt::Debug for QcowDiskAsync { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("QcowDiskAsync") - .field("sparse", &self.sparse) - .field("has_backing", &self.backing_file.is_some()) - .finish_non_exhaustive() - } -} - -impl QcowDiskAsync { - pub fn new( - file: File, - direct_io: bool, - backing_files: bool, - sparse: bool, - ) -> BlockResult { - let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 }; - let (inner, backing_file, sparse) = - parse_qcow(RawFile::new(file, direct_io), 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(QcowDiskAsync { - metadata: Arc::new(QcowMetadata::new(inner)), - backing_file: backing_file.map(shared_backing_from).transpose()?, - sparse, - data_raw_file, - }) - } -} - -impl Drop for QcowDiskAsync { - fn drop(&mut self) { - self.metadata.shutdown(); - } -} - -impl disk_file::DiskSize for QcowDiskAsync { - fn logical_size(&self) -> BlockResult { - Ok(self.metadata.virtual_size()) - } -} - -impl disk_file::PhysicalSize for QcowDiskAsync { - fn physical_size(&self) -> BlockResult { - Ok(self.data_raw_file.physical_size()?) - } -} - -impl disk_file::DiskFd for QcowDiskAsync { - fn fd(&self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.data_raw_file.as_fd().as_raw_fd()) - } -} - -impl disk_file::Geometry for QcowDiskAsync {} - -impl disk_file::SparseCapable for QcowDiskAsync { - fn supports_sparse_operations(&self) -> bool { - true - } - - fn supports_zero_flag(&self) -> bool { - true - } -} - -impl disk_file::Resizable for QcowDiskAsync { - 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 file", - )), - ) - .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 QcowDiskAsync {} - -impl disk_file::AsyncDiskFile for QcowDiskAsync { - fn try_clone(&self) -> BlockResult> { - Ok(Box::new(QcowDiskAsync { - 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(), - })) - } - - fn create_async_io(&self, ring_depth: u32) -> BlockResult> { - 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)))?, - )) - } -} +use crate::{BatchRequest, RequestType, SECTOR_SIZE}; /// Per queue QCOW2 I/O worker using io_uring. /// @@ -271,7 +139,7 @@ impl AsyncIo for QcowAsync { .user_data(user_data), ) .map_err(|_| { - AsyncIoError::ReadVectored(Error::other("Submission queue is full")) + AsyncIoError::ReadVectored(io::Error::other("Submission queue is full")) })?; }; @@ -418,7 +286,9 @@ impl AsyncIo for QcowAsync { .user_data(req.user_data), ) .map_err(|_| { - AsyncIoError::ReadVectored(Error::other("Submission queue is full")) + AsyncIoError::ReadVectored(io::Error::other( + "Submission queue is full", + )) })?; } needs_submit = true; diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 53701cfd4d..9e183dee14 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -4,156 +4,28 @@ use std::cmp::min; use std::collections::VecDeque; -use std::fs::File; -use std::os::fd::{AsFd, AsRawFd}; +use std::os::unix::io::AsRawFd; use std::sync::Arc; -use std::{fmt, io}; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; -use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; -use crate::disk_file; -use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; -use crate::qcow::backing::shared_backing_from; +use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult}; use crate::qcow::decoder::Decoder; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, }; use crate::qcow::qcow_raw_file::QcowRawFile; -use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow}; use crate::qcow_common::{ AlignedBuf, aligned_pread, aligned_pwrite, decompress_cluster, gather_from_iovecs, gather_from_iovecs_into, pread_alloc, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; -pub struct QcowDiskSync { - metadata: Arc, - /// Shared across queues, resolved once at construction. - backing_file: Option>, - sparse: bool, - data_raw_file: QcowRawFile, -} - -impl fmt::Debug for QcowDiskSync { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("QcowDiskSync") - .field("sparse", &self.sparse) - .field("has_backing", &self.backing_file.is_some()) - .finish_non_exhaustive() - } -} - -impl QcowDiskSync { - pub fn new( - file: File, - direct_io: bool, - backing_files: bool, - sparse: bool, - ) -> BlockResult { - let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 }; - let (inner, backing_file, sparse) = - parse_qcow(RawFile::new(file, direct_io), 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(QcowDiskSync { - metadata: Arc::new(QcowMetadata::new(inner)), - backing_file: backing_file.map(shared_backing_from).transpose()?, - sparse, - data_raw_file, - }) - } -} - -impl Drop for QcowDiskSync { - fn drop(&mut self) { - self.metadata.shutdown(); - } -} - -impl disk_file::DiskSize for QcowDiskSync { - fn logical_size(&self) -> BlockResult { - Ok(self.metadata.virtual_size()) - } -} - -impl disk_file::PhysicalSize for QcowDiskSync { - fn physical_size(&self) -> BlockResult { - Ok(self.data_raw_file.physical_size()?) - } -} - -impl disk_file::DiskFd for QcowDiskSync { - fn fd(&self) -> BorrowedDiskFd<'_> { - BorrowedDiskFd::new(self.data_raw_file.as_fd().as_raw_fd()) - } -} - -impl disk_file::Geometry for QcowDiskSync {} - -impl disk_file::SparseCapable for QcowDiskSync { - fn supports_sparse_operations(&self) -> bool { - true - } - - fn supports_zero_flag(&self) -> bool { - true - } -} - -impl disk_file::Resizable for QcowDiskSync { - 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 file", - )), - ) - .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 QcowDiskSync {} - -impl disk_file::AsyncDiskFile for QcowDiskSync { - fn try_clone(&self) -> BlockResult> { - Ok(Box::new(QcowDiskSync { - 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(), - })) - } - - // ring_depth is unused - this sync backend performs blocking I/O - // instead of submitting to an async ring. - fn create_async_io(&self, _ring_depth: u32) -> BlockResult> { - Ok(Box::new(QcowSync::new( - Arc::clone(&self.metadata), - self.data_raw_file.clone(), - self.backing_file.as_ref().map(Arc::clone), - self.sparse, - ))) - } -} - pub struct QcowSync { metadata: Arc, data_file: QcowRawFile, - /// See the backing_file field on QcowDiskSync. + /// See the backing_file field on QcowDisk. backing_file: Option>, sparse: bool, /// O_DIRECT alignment requirement (0 = no alignment needed). From 4ec2ff10802f70e1c1129b5b3a42df4c344eafc5 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 21:07:33 +0200 Subject: [PATCH 1416/1893] 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 --- block/src/qcow_disk.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/block/src/qcow_disk.rs b/block/src/qcow_disk.rs index effea82af7..eb9dff6008 100644 --- a/block/src/qcow_disk.rs +++ b/block/src/qcow_disk.rs @@ -186,3 +186,30 @@ impl disk_file::AsyncDiskFile for QcowDisk { ))) } } + +#[cfg(test)] +mod unit_tests { + use vmm_sys_util::tempfile::TempFile; + + use super::*; + use crate::disk_file::DiskSize; + use crate::qcow::{QcowFile, RawFile}; + + const TEST_SIZE: u64 = 0x5566_7788; + + fn make_qcow_file() -> File { + let temp_file = TempFile::new().unwrap(); + { + let raw = RawFile::new(temp_file.as_file().try_clone().unwrap(), false); + QcowFile::new(raw, 3, TEST_SIZE, true).unwrap(); + } + temp_file.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); + } +} From 90430984736c1ef2e09495f4ab83149b9fdd354b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 21:13:46 +0200 Subject: [PATCH 1417/1893] 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 --- block/src/qcow_disk.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_disk.rs b/block/src/qcow_disk.rs index eb9dff6008..798c354d57 100644 --- a/block/src/qcow_disk.rs +++ b/block/src/qcow_disk.rs @@ -192,7 +192,8 @@ mod unit_tests { use vmm_sys_util::tempfile::TempFile; use super::*; - use crate::disk_file::DiskSize; + use crate::async_io::AsyncIo; + use crate::disk_file::{AsyncDiskFile, DiskSize}; use crate::qcow::{QcowFile, RawFile}; const TEST_SIZE: u64 = 0x5566_7788; @@ -212,4 +213,28 @@ mod unit_tests { 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 = 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); + } } From 5958872943e22f58262cb8b3573fc9cb34543a2d Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 21:17:10 +0200 Subject: [PATCH 1418/1893] 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 --- block/src/qcow_disk.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/block/src/qcow_disk.rs b/block/src/qcow_disk.rs index 798c354d57..0ccad1e32d 100644 --- a/block/src/qcow_disk.rs +++ b/block/src/qcow_disk.rs @@ -237,4 +237,21 @@ mod unit_tests { 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); + } } From abef0e5b69290eddc6167cee8d2aca90a9b7b379 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 23 Apr 2026 21:22:32 +0200 Subject: [PATCH 1419/1893] 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 --- block/src/qcow_disk.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/block/src/qcow_disk.rs b/block/src/qcow_disk.rs index 0ccad1e32d..ef27305264 100644 --- a/block/src/qcow_disk.rs +++ b/block/src/qcow_disk.rs @@ -193,7 +193,7 @@ mod unit_tests { use super::*; use crate::async_io::AsyncIo; - use crate::disk_file::{AsyncDiskFile, DiskSize}; + use crate::disk_file::{AsyncDiskFile, DiskSize, PhysicalSize}; use crate::qcow::{QcowFile, RawFile}; const TEST_SIZE: u64 = 0x5566_7788; @@ -254,4 +254,13 @@ mod unit_tests { 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()); + } } From 8d6213338379d2d4597bb4a2c99ace30590557d9 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 25 Apr 2026 10:32:38 +0100 Subject: [PATCH 1420/1893] 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 --- .../src/vhost_user/generic_vhost_user.rs | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 3ac01406bf..8397744abd 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -26,13 +26,27 @@ use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::{VhostUserCommon, VhostUserState}; use crate::{ ActivateResult, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_ACCESS_PLATFORM, - VirtioCommon, VirtioDevice, VirtioSharedMemoryList, + VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioInterruptType, VirtioSharedMemoryList, }; pub type State = VhostUserState<()>; -struct BackendReqHandler {} -impl VhostUserFrontendReqHandler for BackendReqHandler {} +struct BackendReqHandler { + interrupt_cb: Arc, +} + +impl VhostUserFrontendReqHandler for BackendReqHandler { + fn handle_config_change(&self) -> std::io::Result { + self.interrupt_cb + .trigger(VirtioInterruptType::Config) + .map_err(|e| { + error!("Failed to signal config change: {e:?}"); + std::io::Error::other(e) + })?; + Ok(0) + } +} + pub struct GenericVhostUser { vu_common: VhostUserCommon, id: String, @@ -97,7 +111,8 @@ impl GenericVhostUser { | VhostUserProtocolFeatures::REPLY_ACK | VhostUserProtocolFeatures::INFLIGHT_SHMFD | VhostUserProtocolFeatures::LOG_SHMFD - | VhostUserProtocolFeatures::DEVICE_STATE; + | VhostUserProtocolFeatures::DEVICE_STATE + | VhostUserProtocolFeatures::BACKEND_REQ; let avail_features = super::DEFAULT_VIRTIO_FEATURES; @@ -275,7 +290,22 @@ impl VirtioDevice for GenericVhostUser { .activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); - let backend_req_handler: Option> = None; + let has_backend_req = self.vu_common.acked_protocol_features + & VhostUserProtocolFeatures::BACKEND_REQ.bits() + != 0; + + let backend_req_handler = has_backend_req + .then(|| { + FrontendReqHandler::new(Arc::new(BackendReqHandler { + interrupt_cb: interrupt_cb.clone(), + })) + .map_err(|e| { + crate::ActivateError::VhostUserSetup(Error::FrontendReqHandlerCreation(e)) + }) + }) + // Return inner Err early, keep Option of `Ok` value + .transpose()?; + // Run a dedicated thread for handling potential reconnections with // the backend. let (kill_evt, pause_evt) = self.vu_common.virtio_common.dup_eventfds(); From 7e3129a303ae63949f048c42b0b61c5ec0dd2ff7 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 24 Apr 2026 12:42:18 +0100 Subject: [PATCH 1421/1893] 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 --- .github/workflows/ci.yaml | 769 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 769 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000000..cebcfa17a1 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,769 @@ +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@v6 + with: + fetch-depth: 0 + - id: changes + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + 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@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: | + 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@v6 + 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@v6 + 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@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.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@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 + audit: + name: audit + needs: [preflight] + if: needs.preflight.outputs.cargo == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - 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@v6 + - 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@v6 + - 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@v6 + - 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@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 + package-consistency: + name: package-consistency + needs: [preflight] + if: needs.preflight.outputs.full == 'true' + 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: | + 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@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 + openapi: + name: openapi + needs: [preflight] + if: needs.preflight.outputs.openapi == 'true' + runs-on: ubuntu-latest + container: openapitools/openapi-generator-cli + steps: + - uses: actions/checkout@v6 + - 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@v6 + - uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1 + 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@v6 + 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 (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 "sev_snp" -- -D warnings + - name: Clippy (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 "igvm" -- -D warnings + - name: Clippy (kvm + tdx) + 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 "tdx,kvm" -- -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@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 (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 (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)" + # 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@v6 + 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@v6 + 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@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 + 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-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 + 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@v6 + with: + fetch-depth: 0 + - name: Run VFIO integration tests + 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 + # timeout-minutes: 15 + # 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@v6 + 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-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@v6 + 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 + - dco + - formatting + - fuzz-build + - gitlint + - hadolint + - integration-arm64 + - integration-vfio + - integration-windows + - integration-x86-64-mq + - integration-x86-64-pr + - lychee + - 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 From 1bee4edf0c3679fdca4b24f3b053349d23eb8ce1 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 24 Apr 2026 17:01:12 +0100 Subject: [PATCH 1422/1893] ci: Drop superseded workflows Delete the PR/MQ workflows superseded by consolidated ci.yaml. Signed-off-by: Rob Bradford --- .github/workflows/audit.yaml | 16 -- .github/workflows/build.yaml | 80 -------- .github/workflows/dco.yaml | 21 -- .github/workflows/formatting.yaml | 32 ---- .github/workflows/fuzz-build.yaml | 32 ---- .github/workflows/gitlint.yaml | 25 --- .github/workflows/hadolint.yaml | 25 --- .github/workflows/integration-arm64.yaml | 69 ------- .../workflows/integration-rate-limiter.yaml | 25 --- .github/workflows/integration-vfio.yaml | 33 ---- .github/workflows/integration-windows.yaml | 53 ------ .github/workflows/integration-x86-64.yaml | 54 ------ .github/workflows/lychee.yaml | 46 ----- .github/workflows/openapi.yaml | 14 -- .github/workflows/package-consistency.yaml | 33 ---- .github/workflows/quality.yaml | 179 ------------------ .github/workflows/reuse.yaml | 12 -- .github/workflows/shlint.yaml | 20 -- .github/workflows/taplo.yaml | 21 -- 19 files changed, 790 deletions(-) delete mode 100644 .github/workflows/audit.yaml delete mode 100644 .github/workflows/build.yaml delete mode 100644 .github/workflows/dco.yaml delete mode 100644 .github/workflows/formatting.yaml delete mode 100644 .github/workflows/fuzz-build.yaml delete mode 100644 .github/workflows/gitlint.yaml delete mode 100644 .github/workflows/hadolint.yaml delete mode 100644 .github/workflows/integration-arm64.yaml delete mode 100644 .github/workflows/integration-rate-limiter.yaml delete mode 100644 .github/workflows/integration-vfio.yaml delete mode 100644 .github/workflows/integration-windows.yaml delete mode 100644 .github/workflows/integration-x86-64.yaml delete mode 100644 .github/workflows/lychee.yaml delete mode 100644 .github/workflows/openapi.yaml delete mode 100644 .github/workflows/package-consistency.yaml delete mode 100644 .github/workflows/quality.yaml delete mode 100644 .github/workflows/reuse.yaml delete mode 100644 .github/workflows/shlint.yaml delete mode 100644 .github/workflows/taplo.yaml diff --git a/.github/workflows/audit.yaml b/.github/workflows/audit.yaml deleted file mode 100644 index bab8eaa145..0000000000 --- a/.github/workflows/audit.yaml +++ /dev/null @@ -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 }} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 628c163db8..0000000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,80 +0,0 @@ -name: Cloud Hypervisor Build -on: [pull_request, merge_group] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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 (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 (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)" diff --git a/.github/workflows/dco.yaml b/.github/workflows/dco.yaml deleted file mode 100644 index 67dfadd5c4..0000000000 --- a/.github/workflows/dco.yaml +++ /dev/null @@ -1,21 +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: | - set -eufo pipefail - pip3 install -U dco-check - dco-check -e "49699333+dependabot[bot]@users.noreply.github.com" diff --git a/.github/workflows/formatting.yaml b/.github/workflows/formatting.yaml deleted file mode 100644 index 8dee21e6b2..0000000000 --- a/.github/workflows/formatting.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: Cloud Hypervisor Code Formatting -on: [pull_request, merge_group] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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 diff --git a/.github/workflows/fuzz-build.yaml b/.github/workflows/fuzz-build.yaml deleted file mode 100644 index d5f0332a9c..0000000000 --- a/.github/workflows/fuzz-build.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: Cloud Hypervisor Cargo Fuzz Build -on: [pull_request, merge_group] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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 diff --git a/.github/workflows/gitlint.yaml b/.github/workflows/gitlint.yaml deleted file mode 100644 index 178f15aa40..0000000000 --- a/.github/workflows/gitlint.yaml +++ /dev/null @@ -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.." diff --git a/.github/workflows/hadolint.yaml b/.github/workflows/hadolint.yaml deleted file mode 100644 index 631c50eefd..0000000000 --- a/.github/workflows/hadolint.yaml +++ /dev/null @@ -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 diff --git a/.github/workflows/integration-arm64.yaml b/.github/workflows/integration-arm64.yaml deleted file mode 100644 index d34d28c3c7..0000000000 --- a/.github/workflows/integration-arm64.yaml +++ /dev/null @@ -1,69 +0,0 @@ -name: Cloud Hypervisor Tests (ARM64) -on: [pull_request, merge_group] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - cancel-in-progress: true - -jobs: - build: - 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 - name: Tests (ARM64) - runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'bookworm-arm64' }} - 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 unit tests (musl) - if: ${{ github.event_name != 'pull_request' }} - run: scripts/dev_cli.sh tests --unit --libc musl - - name: Load openvswitch module - if: ${{ github.event_name != 'pull_request' }} - run: sudo modprobe openvswitch - - name: Run integration tests (musl) - if: ${{ github.event_name != 'pull_request' }} - timeout-minutes: 60 - run: scripts/dev_cli.sh tests --integration --libc musl - - name: Install Azure CLI - if: ${{ github.event_name != 'pull_request' }} - 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 - if: ${{ github.event_name != 'pull_request' }} - 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-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 - - name: Skipping build for PR - if: ${{ github.event_name == 'pull_request' }} - run: echo "Skipping build for PR" diff --git a/.github/workflows/integration-rate-limiter.yaml b/.github/workflows/integration-rate-limiter.yaml deleted file mode 100644 index b76c35c739..0000000000 --- a/.github/workflows/integration-rate-limiter.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: Cloud Hypervisor Tests (Rate-Limiter) -on: [merge_group, pull_request] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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" diff --git a/.github/workflows/integration-vfio.yaml b/.github/workflows/integration-vfio.yaml deleted file mode 100644 index b4f2ca2f94..0000000000 --- a/.github/workflows/integration-vfio.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Cloud Hypervisor Tests (VFIO) -on: [merge_group, pull_request] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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 github-runner:github-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" diff --git a/.github/workflows/integration-windows.yaml b/.github/workflows/integration-windows.yaml deleted file mode 100644 index 1010ab73ec..0000000000 --- a/.github/workflows/integration-windows.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: Cloud Hypervisor Tests (Windows Guest) -on: [merge_group, pull_request] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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: | - 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 - if: ${{ github.event_name != 'pull_request' }} - 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 - if: ${{ github.event_name != 'pull_request' }} - 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 - 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" \ No newline at end of file diff --git a/.github/workflows/integration-x86-64.yaml b/.github/workflows/integration-x86-64.yaml deleted file mode 100644 index 38dbcb06f2..0000000000 --- a/.github/workflows/integration-x86-64.yaml +++ /dev/null @@ -1,54 +0,0 @@ -name: Cloud Hypervisor Tests (x86-64) -on: [pull_request, merge_group] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - cancel-in-progress: true - -jobs: - build: - 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: - 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: | - 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 - 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: 80 - run: scripts/dev_cli.sh tests --integration --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" diff --git a/.github/workflows/lychee.yaml b/.github/workflows/lychee.yaml deleted file mode 100644 index 105e2e9a6e..0000000000 --- a/.github/workflows/lychee.yaml +++ /dev/null @@ -1,46 +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: | - 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 "----------------------------------------" - # 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 \ No newline at end of file diff --git a/.github/workflows/openapi.yaml b/.github/workflows/openapi.yaml deleted file mode 100644 index 4c179f7e82..0000000000 --- a/.github/workflows/openapi.yaml +++ /dev/null @@ -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 diff --git a/.github/workflows/package-consistency.yaml b/.github/workflows/package-consistency.yaml deleted file mode 100644 index 7f7808c882..0000000000 --- a/.github/workflows/package-consistency.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Cloud Hypervisor Consistency -on: [pull_request, merge_group] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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: | - set -eufo pipefail - pushd fuzz - python3 ../scripts/package-consistency-check.py github.com/rust-vmm - popd diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml deleted file mode 100644 index 1290b0f872..0000000000 --- a/.github/workflows/quality.yaml +++ /dev/null @@ -1,179 +0,0 @@ -name: Cloud Hypervisor Quality Checks -on: [pull_request, merge_group] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }} - 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 -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: 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 - 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 - 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 - 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 - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -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)" - - 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.45.1 diff --git a/.github/workflows/reuse.yaml b/.github/workflows/reuse.yaml deleted file mode 100644 index 39ae873011..0000000000 --- a/.github/workflows/reuse.yaml +++ /dev/null @@ -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 diff --git a/.github/workflows/shlint.yaml b/.github/workflows/shlint.yaml deleted file mode 100644 index 068b9930ed..0000000000 --- a/.github/workflows/shlint.yaml +++ /dev/null @@ -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 diff --git a/.github/workflows/taplo.yaml b/.github/workflows/taplo.yaml deleted file mode 100644 index 24577878bd..0000000000 --- a/.github/workflows/taplo.yaml +++ /dev/null @@ -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 From b03e186270bc7711286f7324d127cd0e65afced4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:26:42 +0000 Subject: [PATCH 1423/1893] 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](https://github.com/crate-ci/typos/compare/cf5f1c29a8ac336af8568821ec41919923b05a83...7c572958218557a3272c2d6719629443b5cc26fd) --- 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] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cebcfa17a1..7763c9c9b0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -301,7 +301,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1 + - uses: crate-ci/typos@7c572958218557a3272c2d6719629443b5cc26fd # v1.45.2 quality: name: quality needs: [preflight] From 389257964b10a0968bc6c0e8ea7a5a7a853aab52 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 28 Apr 2026 04:11:39 +0000 Subject: [PATCH 1424/1893] hypervisor: mshv: Make the translation caching code idiomatic Signed-off-by: Wei Liu --- hypervisor/src/mshv/mod.rs | 6 +----- hypervisor/src/mshv/x86_64/emulator.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 32ef68bc55..502c36f10d 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -737,11 +737,7 @@ impl cpu::Vcpu for MshvVcpu { let mut context = MshvEmulatorContext { vcpu: self, - map: if gva_gpa_valid { - (gva, gpa) - } else { - (u64::MAX, 0) - }, + mapping: gva_gpa_valid.then_some((gva, gpa)), }; let old_state = context diff --git a/hypervisor/src/mshv/x86_64/emulator.rs b/hypervisor/src/mshv/x86_64/emulator.rs index eb2be3d2f2..211d0cb8e8 100644 --- a/hypervisor/src/mshv/x86_64/emulator.rs +++ b/hypervisor/src/mshv/x86_64/emulator.rs @@ -15,20 +15,20 @@ use crate::mshv::MshvVcpu; pub struct MshvEmulatorContext<'a> { pub vcpu: &'a MshvVcpu, - pub map: (u64, u64), // Initial GVA to GPA mapping provided by the hypervisor + /// Initial (GVA, GPA) mapping provided by the hypervisor if the hypervisor provided a + /// valid mapping. Used as a fast path in [`MshvEmulatorContext::translate`] to avoid a + /// translate hypercall. `None` when the hypervisor did not provide a valid mapping. + pub mapping: Option<(u64, u64)>, } impl MshvEmulatorContext<'_> { - // Do the actual gva -> gpa translation. - // - // When the hypervisor sets GvaGpaValid in the intercept message, `map` - // caches the (gva, gpa) pair as a fast path that avoids a translate - // hypercall. When the flag is clear, `map` is set to a sentinel - // (u64::MAX, 0) so this shortcut never fires. + // Do the actual gva -> gpa translation #[allow(non_upper_case_globals)] fn translate(&self, gva: u64, flags: u32) -> Result { - if self.map.0 == gva { - return Ok(self.map.1); + if let Some((cached_gva, cached_gpa)) = self.mapping + && cached_gva == gva + { + return Ok(cached_gpa); } let (gpa, result_code) = self From feddff025a1cdbf27f64419c1876ebda8caa0c73 Mon Sep 17 00:00:00 2001 From: Stepan Rabotkin Date: Sun, 26 Apr 2026 00:10:54 +0300 Subject: [PATCH 1425/1893] vmm: openapi: add user_devices to spec Signed-off-by: Stepan Rabotkin --- vmm/src/api/openapi/cloud-hypervisor.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 422660d07a..427c2616dd 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -647,6 +647,10 @@ components: type: array items: $ref: "#/components/schemas/DeviceConfig" + user_devices: + type: array + items: + $ref: '#/components/schemas/UserDeviceConfig' vdpa: type: array items: @@ -1219,6 +1223,23 @@ components: x_nv_gpudirect_clique: type: integer format: int8 + + UserDeviceConfig: + required: + - socket + type: object + properties: + socket: + type: string + id: + type: string + pci_segment: + type: integer + format: int16 + pci_device_id: + type: integer + format: uint8 + TpmConfig: required: - socket From 22eeb3f80804e321b7fa358ac9e4f5b45727baa4 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Apr 2026 16:54:45 -0700 Subject: [PATCH 1426/1893] 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 --- virtio-devices/src/transport/pci_device.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 833836d774..619336e89c 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -807,7 +807,8 @@ impl VirtioPciDevice { } if !queue.is_valid(self.memory.memory().deref()) { - error!("Queue {queue_index} is not valid"); + error!("Queue {queue_index} is not valid; skipping activation"); + continue; } queues.push(( From 5b199498ffbaa718a6007654e69859787a5a4862 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Apr 2026 16:56:59 -0700 Subject: [PATCH 1427/1893] 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 --- virtio-devices/src/iommu.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index bc03e6775f..c99de399aa 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -494,12 +494,25 @@ impl Request { .map(|(&e, _)| e) .collect(); + let Some(size) = req + .virt_end + .checked_sub(req.virt_start) + .and_then(|delta| delta.checked_add(1)) + else { + status = VIRTIO_IOMMU_S_RANGE; + return Err(Error::InvalidMapRequest); + }; + + if req.phys_start > u64::MAX - size || req.virt_start > u64::MAX - size { + status = VIRTIO_IOMMU_S_RANGE; + return Err(Error::InvalidMapRequest); + } + // For viommu all endpoints receive their own VFIO container, as a result // Each endpoint within the domain needs to be separately mapped, as the // mapping is done on a per-container level, not a per-domain level for endpoint in endpoints { if let Some(ext_map) = ext_mapping.get(&endpoint) { - let size = req.virt_end - req.virt_start + 1; ext_map .map(req.virt_start, req.phys_start, size) .map_err(Error::ExternalMapping)?; @@ -518,7 +531,7 @@ impl Request { req.virt_start, Mapping { gpa: req.phys_start, - size: req.virt_end - req.virt_start + 1, + size, }, ); } @@ -558,10 +571,18 @@ impl Request { .map(|(&e, _)| e) .collect(); + let Some(size) = req + .virt_end + .checked_sub(virt_start) + .and_then(|d| d.checked_add(1)) + else { + status = VIRTIO_IOMMU_S_RANGE; + return Err(Error::InvalidUnmapRequest); + }; + // Trigger external unmapping if necessary. for endpoint in endpoints { if let Some(ext_map) = ext_mapping.get(&endpoint) { - let size = req.virt_end - virt_start + 1; ext_map .unmap(virt_start, size) .map_err(Error::ExternalUnmapping)?; From 02b503ee16cc7e8a08d5103e92fd2ea6a0060263 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Apr 2026 16:57:24 -0700 Subject: [PATCH 1428/1893] 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 --- virtio-devices/src/vdpa.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 44b5cfde8f..92793d96a1 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -365,7 +365,12 @@ impl Vdpa { } fn dma_unmap(&self, iova: u64, size: u64) -> Result<()> { - let iova_last = iova + size - 1; + let Some(iova_last) = iova.checked_add(size) else { + return Err(Error::InvalidIovaRange(iova, u64::MAX)); + }; + let Some(iova_last) = iova_last.checked_sub(1) else { + return Err(Error::InvalidIovaRange(0, 0)); + }; if iova < self.iova_range.first || iova_last > self.iova_range.last { return Err(Error::InvalidIovaRange(iova, iova_last)); } From 9f405a21ac25954667b7ca9f81605879b4006327 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Apr 2026 16:58:07 -0700 Subject: [PATCH 1429/1893] 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 --- virtio-devices/src/vsock/packet.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/virtio-devices/src/vsock/packet.rs b/virtio-devices/src/vsock/packet.rs index e6b4c5afbb..e9834b5f36 100644 --- a/virtio-devices/src/vsock/packet.rs +++ b/virtio-devices/src/vsock/packet.rs @@ -175,6 +175,9 @@ impl VsockPacket { // For small packets, the data may be stored in the same descriptor as the header. if !head.has_next() { let buf_size: usize = head.len() as usize - VSOCK_PKT_HDR_SIZE; + if buf_size < pkt.len() as usize { + return Err(VsockError::BufDescTooSmall); + } let buf_ptr = get_host_address_range( desc_chain.memory(), head.addr() From 54cde010c5579a9b2d0a3b97f2ea20f2aad70279 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Apr 2026 17:01:22 -0700 Subject: [PATCH 1430/1893] 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 --- pci/src/vfio_user.rs | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/pci/src/vfio_user.rs b/pci/src/vfio_user.rs index 27c7dc0405..499966569f 100644 --- a/pci/src/vfio_user.rs +++ b/pci/src/vfio_user.rs @@ -557,23 +557,34 @@ impl ExternalDmaMapping for VfioUserDmaMappi fn map(&self, iova: u64, gpa: u64, size: u64) -> std::result::Result<(), std::io::Error> { let mem = self.memory.memory(); let guest_addr = GuestAddress(gpa); - let region = mem.find_region(guest_addr); - - if let Some(region) = region { - let file_offset = region.file_offset().unwrap(); - let offset = (GuestAddress(gpa).checked_offset_from(region.start_addr())).unwrap() - + file_offset.start(); - - self.client - .lock() - .unwrap() - .dma_map(offset, iova, size, file_offset.file().as_raw_fd()) - .map_err(|e| std::io::Error::other(format!("Error mapping region: {e}"))) - } else { - Err(std::io::Error::other(format!( + let Some(region) = mem.find_region(guest_addr) else { + return Err(std::io::Error::other(format!( "Region not found for 0x{gpa:x}" - ))) + ))); + }; + + // Check that the range fits in the region. + let region_offset = guest_addr + .checked_offset_from(region.start_addr()) + .ok_or_else(|| std::io::Error::other(format!("gpa 0x{gpa:x} below region start")))?; + let region_remaining = (region.len()) + .checked_sub(region_offset) + .ok_or_else(|| std::io::Error::other(format!("gpa 0x{gpa:x} past region end")))?; + if size > region_remaining { + return Err(std::io::Error::other(format!( + "DMA map (gpa 0x{gpa:x}, size 0x{size:x}) extends past region end" + ))); } + + // Unwrap is safe as we only do vfio with shared mem with a backing file. + let file_offset = region.file_offset().unwrap(); + let offset = region_offset + file_offset.start(); + + self.client + .lock() + .unwrap() + .dma_map(offset, iova, size, file_offset.file().as_raw_fd()) + .map_err(|e| std::io::Error::other(format!("Error mapping region: {e}"))) } fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error> { From 475c8d3efb3d8febf626f6fbf4b5fe364c72d48b Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Mon, 27 Apr 2026 16:06:13 -0700 Subject: [PATCH 1431/1893] 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 --- hypervisor/src/mshv/x86_64/emulator.rs | 58 ++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/hypervisor/src/mshv/x86_64/emulator.rs b/hypervisor/src/mshv/x86_64/emulator.rs index 211d0cb8e8..ab18e96bb3 100644 --- a/hypervisor/src/mshv/x86_64/emulator.rs +++ b/hypervisor/src/mshv/x86_64/emulator.rs @@ -134,11 +134,30 @@ impl MshvEmulatorContext<'_> { .map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?; if old_state.sregs != new_state.sregs { - debug!("mshv emulator: Updating CPU special registers"); - debug!("mshv emulator: {:#x?}", new_state.sregs); - self.vcpu - .set_sregs(&new_state.sregs) - .map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?; + debug!("mshv emulator: Updating CPU segment registers"); + // Emulation only modifies segment registers among special + // registers. Use the VP register page to write only segments, + // avoiding IOCTLs for other special registers (tr, ldt, gdt, + // idt, cr*, efer, etc.) that emulation never modifies. + if let Some(reg_page) = self.vcpu.fd.get_vp_reg_page() { + let vp_reg_page = reg_page.0; + let sregs: SpecialRegisters = new_state.sregs.into(); + // SAFETY: vp_reg_page is a valid mapped pointer + unsafe { + (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.cs = sregs.cs.into(); + (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.ds = sregs.ds.into(); + (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.es = sregs.es.into(); + (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.fs = sregs.fs.into(); + (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.gs = sregs.gs.into(); + (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.ss = sregs.ss.into(); + (*vp_reg_page).dirty |= 1 << HV_X64_REGISTER_CLASS_SEGMENT; + } + } else { + debug!("mshv emulator: {:#x?}", new_state.sregs); + self.vcpu + .set_sregs(&new_state.sregs) + .map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?; + } } Ok(()) @@ -191,10 +210,31 @@ impl PlatformEmulator for MshvEmulatorContext<'_> { .vcpu .get_regs() .map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?; - let sregs = self - .vcpu - .get_sregs() - .map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?; + + // For emulation, we only need segment registers, cr0, and efer + // from special registers. Read them directly from the VP register + // page to avoid IOCTLs for other special registers (tr, ldt, gdt, + // idt, cr2, apic_base, etc.) that emulation doesn't use. + let sregs = if let Some(reg_page) = self.vcpu.fd.get_vp_reg_page() { + let vp_reg_page = reg_page.0; + let mut mshv_sregs = SpecialRegisters::default(); + // SAFETY: vp_reg_page is a valid mapped pointer + unsafe { + mshv_sregs.cs = (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.cs.into(); + mshv_sregs.ds = (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.ds.into(); + mshv_sregs.es = (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.es.into(); + mshv_sregs.fs = (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.fs.into(); + mshv_sregs.gs = (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.gs.into(); + mshv_sregs.ss = (*vp_reg_page).__bindgen_anon_3.__bindgen_anon_1.ss.into(); + mshv_sregs.cr0 = (*vp_reg_page).cr0; + mshv_sregs.efer = (*vp_reg_page).efer; + } + mshv_sregs.into() + } else { + self.vcpu + .get_sregs() + .map_err(|e| PlatformError::GetCpuStateFailure(e.into()))? + }; debug!("mshv emulator: Getting new CPU state"); debug!("mshv emulator: {regs:#x?}"); From 87ffc620e44c888b63ad1d9daf358ec8fa46fa7b Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 29 Apr 2026 18:10:29 +0100 Subject: [PATCH 1432/1893] 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 --- virtio-devices/src/net.rs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 13c64b270e..30fa25b6d2 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -481,7 +481,14 @@ impl Net { ) -> Result { assert!(!taps.is_empty()); - let mtu = taps[0].mtu().map_err(Error::TapError)? as u16; + // Skip advertising VIRTIO_NET_F_MTU and let the guest fall back to the Ethernet default if querying failed + let mtu = match taps[0].mtu() { + Ok(m) => Some(m as u16), + Err(e) => { + warn!("Failed to query tap MTU; not advertising VIRTIO_NET_F_MTU: {e}"); + None + } + }; let (avail_features, acked_features, config, queue_sizes, paused) = if let Some(state) = state @@ -495,9 +502,11 @@ impl Net { true, ) } else { - let mut avail_features = (1 << VIRTIO_NET_F_MTU) - | (1 << VIRTIO_RING_F_EVENT_IDX) - | (1 << VIRTIO_F_VERSION_1); + let mut avail_features = (1 << VIRTIO_RING_F_EVENT_IDX) | (1 << VIRTIO_F_VERSION_1); + + if mtu.is_some() { + avail_features |= 1 << VIRTIO_NET_F_MTU; + } if access_platform_enabled { avail_features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; @@ -528,20 +537,9 @@ impl Net { let mut config = VirtioNetConfig::default(); if let Some(mac) = guest_mac { - build_net_config_space( - &mut config, - mac, - num_queues, - Some(mtu), - &mut avail_features, - ); + build_net_config_space(&mut config, mac, num_queues, mtu, &mut avail_features); } else { - build_net_config_space_with_mq( - &mut config, - num_queues, - Some(mtu), - &mut avail_features, - ); + build_net_config_space_with_mq(&mut config, num_queues, mtu, &mut avail_features); } ( From 8598b45a9519b205970e7430ad3abd5fb7067ae1 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 29 Apr 2026 15:34:18 -0700 Subject: [PATCH 1433/1893] 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 7d65187350a1 ("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 refactoring. Assisted-by: Claude:Opus-4.6 Signed-off-by: Muminul Islam --- vmm/src/vm.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index cd118782e9..e4df94b76c 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2840,10 +2840,13 @@ impl Vm { let rsdp_addr = self.create_acpi_tables(); #[cfg(not(target_arch = "riscv64"))] - // Configure shared state based on loaded kernel - entry_point - .map(|entry_point| self.configure_system(rsdp_addr, entry_point)) - .transpose()?; + // Configure shared state based on loaded kernel. + // Skip for SEV-SNP guests where system configuration is provided via IGVM. + if rsdp_addr.is_some() { + entry_point + .map(|entry_point| self.configure_system(rsdp_addr, entry_point)) + .transpose()?; + } #[cfg(target_arch = "riscv64")] self.configure_system().unwrap(); From 7d24608bcfb233693a63231051e4ca2e8116b58a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 29 Apr 2026 15:34:40 -0700 Subject: [PATCH 1434/1893] 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 75ed2c9f903f ("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 --- vmm/src/igvm/igvm_loader.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 5cdb0d01b4..790d04d4fb 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -232,8 +232,8 @@ pub fn load_igvm( isolated_page_size_4kb: mshv_bindings::hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, normal: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_NORMAL, unmeasured: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED, - cpuid: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_NORMAL, - secrets: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED, + cpuid: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_CPUID, + secrets: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_SECRETS, vmsa: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_VMSA, }, #[cfg(feature = "kvm")] From 4608de134f4ea66dedca0aac3be9c33689ca72b9 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 29 Apr 2026 15:35:04 -0700 Subject: [PATCH 1435/1893] 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 --- vmm/src/igvm/igvm_loader.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 790d04d4fb..75b70bb7f5 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -23,12 +23,14 @@ use log::info; #[cfg(feature = "mshv")] use mshv_bindings::*; use thiserror::Error; +#[cfg(all(feature = "kvm", feature = "sev_snp"))] +use vm_memory::Bytes; #[cfg(feature = "sev_snp")] -use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemory}; +use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemory}; #[cfg(all(feature = "kvm", feature = "sev_snp"))] use vm_migration::Snapshottable; use zerocopy::IntoBytes; -#[cfg(feature = "sev_snp")] +#[cfg(all(feature = "kvm", feature = "sev_snp"))] use zerocopy::{FromBytes, FromZeros}; #[cfg(feature = "sev_snp")] @@ -40,11 +42,11 @@ use crate::memory_manager::{Error as MemoryManagerError, MemoryManager}; #[cfg(feature = "sev_snp")] const ISOLATED_PAGE_SHIFT: u32 = 12; -#[cfg(feature = "sev_snp")] +#[cfg(all(feature = "kvm", feature = "sev_snp"))] const SNP_CPUID_LIMIT: u32 = 64; // see section 7.1 // https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf -#[cfg(feature = "sev_snp")] +#[cfg(all(feature = "kvm", feature = "sev_snp"))] #[repr(C)] #[derive(Debug, Clone, PartialEq, Eq, IntoBytes, FromBytes)] pub struct SnpCpuidFunc { @@ -59,7 +61,7 @@ pub struct SnpCpuidFunc { pub reserved: u64, } -#[cfg(feature = "sev_snp")] +#[cfg(all(feature = "kvm", feature = "sev_snp"))] #[repr(C)] #[derive(Debug, Clone, FromBytes, IntoBytes)] pub struct SnpCpuidInfo { @@ -656,8 +658,12 @@ pub fn load_igvm( .collect(); #[cfg(feature = "kvm")] let page_type = group[0].page_type; + #[cfg(feature = "kvm")] let mut new_cp = SnpCpuidInfo::new_zeroed(); - let _ = guest_memory.read(new_cp.as_mut_bytes(), GuestAddress(group[0].gpa)); + #[cfg(feature = "kvm")] + if hypervisor_type == HypervisorType::Kvm { + let _ = guest_memory.read(new_cp.as_mut_bytes(), GuestAddress(group[0].gpa)); + } let import_result = memory_manager .lock() .unwrap() From f6ed896f6882cc9b6eb6334ad54c06f7b06726ba Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 29 Apr 2026 15:35:31 -0700 Subject: [PATCH 1436/1893] 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 --- vmm/src/vm.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index e4df94b76c..3d5315b192 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1547,7 +1547,9 @@ impl Vm { // Only reserve bootloader/VMSA regions for KVM + SEV-SNP; other hypervisors // (e.g. MSHV) handle this through their own import path. #[cfg(all(feature = "kvm", feature = "sev_snp"))] - if cpu_manager.lock().unwrap().sev_snp_enabled() { + if cpu_manager.lock().unwrap().sev_snp_enabled() + && cpu_manager.lock().unwrap().hypervisor_type() == hypervisor::HypervisorType::Kvm + { Self::reserve_bootloader_regions(&memory_manager)?; } From 9411f7ecd879056cdb12d05f1f445f3087d5f1c3 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 30 Apr 2026 15:21:20 +0100 Subject: [PATCH 1437/1893] 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 --- vmm/src/config.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 3faa7ff75f..3d37420b40 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -316,7 +316,7 @@ pub enum ValidationError { )] InvalidIommuAddressWidthBits(u8), /// Balloon too big - #[error("Ballon size ({0}) greater than RAM ({1})")] + #[error("Balloon size ({0}) greater than RAM ({1})")] BalloonLargerThanRam(u64, u64), /// On a IOMMU segment but not behind IOMMU #[error("Device is on an IOMMU PCI segment ({0}) but not placed behind IOMMU")] @@ -3044,14 +3044,7 @@ impl VmConfig { } if let Some(balloon) = &self.balloon { - let mut ram_size = self.memory.size; - - if let Some(zones) = &self.memory.zones { - for zone in zones { - ram_size += zone.size; - } - } - + let ram_size = self.memory.total_size(); if balloon.size >= ram_size { return Err(ValidationError::BalloonLargerThanRam( balloon.size, From 3bd90933e38cf2c2a1177c346b1ccc147beb9209 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:54:06 +0000 Subject: [PATCH 1438/1893] 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](https://github.com/crate-ci/typos/compare/7c572958218557a3272c2d6719629443b5cc26fd...bbaefadf97b0ec5fdc942684b647f1a6ab250274) --- 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] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7763c9c9b0..07edc10282 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -301,7 +301,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: crate-ci/typos@7c572958218557a3272c2d6719629443b5cc26fd # v1.45.2 + - uses: crate-ci/typos@bbaefadf97b0ec5fdc942684b647f1a6ab250274 # v1.46.0 quality: name: quality needs: [preflight] From e0ab116a0c079bbb0e11ecc3132bd80b95c85139 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 00:04:10 +0000 Subject: [PATCH 1439/1893] 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] --- Cargo.lock | 38 +++++++++++++++++++------------------- Cargo.toml | 2 +- fuzz/Cargo.lock | 20 ++++++++++---------- fuzz/Cargo.toml | 2 +- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a866c5ebd7..fdab21b8e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,9 +370,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -1146,9 +1146,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "log", @@ -1159,9 +1159,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", @@ -1552,9 +1552,9 @@ dependencies = [ [[package]] name = "pastey" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" +checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" [[package]] name = "pci" @@ -2900,12 +2900,12 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow 0.7.15", + "winnow 1.0.0", "zvariant", ] @@ -2965,23 +2965,23 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.10.0" +version = "5.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +checksum = "c4db0ecb8987cf5e92653c57c098f7f0e39a03112edb796f4fe089fb7eaa14ff" dependencies = [ "endi", "enumflags2", "serde", - "winnow 0.7.15", + "winnow 1.0.0", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.10.0" +version = "5.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +checksum = "5b949b639ab1b4bed763aa7481ba0e368af68d8b55532f8ed4bec86a59f2ca98" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2992,13 +2992,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.3.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" dependencies = [ "proc-macro2", "quote", "serde", "syn", - "winnow 0.7.15", + "winnow 1.0.0", ] diff --git a/Cargo.toml b/Cargo.toml index 69488e5192..84ed42012c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ env_logger = "0.11.10" epoll = "4.4.0" flume = "0.12.0" itertools = "0.14.0" -libc = "0.2.185" +libc = "0.2.186" log = "0.4.29" signal-hook = "0.4.4" thiserror = "2.0.18" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 51a21c27cd..dbcd5e649d 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -115,9 +115,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitfield-struct" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" +checksum = "3ca6739863c590881f038d033a146c51ddae239186a4327014839fd864f44ed5" dependencies = [ "proc-macro2", "quote", @@ -173,9 +173,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -724,9 +724,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libfuzzer-sys" @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "pastey" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" +checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" [[package]] name = "pci" @@ -1643,9 +1643,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index bb80658aef..93c4c2d08e 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ block = { path = "../block" } devices = { path = "../devices" } epoll = "4.4.0" hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } -libc = "0.2.185" +libc = "0.2.186" libfuzzer-sys = "0.4.12" linux-loader = { version = "0.13.2", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } From 05d8606a32c20d0e12ff265882cca0f0d5a2af16 Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 20:43:25 +0200 Subject: [PATCH 1440/1893] 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 --- vmm/src/cpu.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 303f5de997..ae7b440765 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -2271,20 +2271,16 @@ impl Cpu { fn generate_mat(&self) -> Vec { let x2apic_id = arch::x86_64::get_x2apic_id(self.cpu_id, self.topology); - let lapic = LocalX2Apic { + LocalX2Apic { r#type: crate::acpi::ACPI_X2APIC_PROCESSOR, length: 16, processor_id: self.cpu_id, apic_id: x2apic_id, flags: 1 << MADT_CPU_ENABLE_FLAG, _reserved: 0, - }; - - let mut mat_data: Vec = vec![0; std::mem::size_of_val(&lapic)]; - // SAFETY: mat_data is large enough to hold lapic - unsafe { *(mat_data.as_mut_ptr() as *mut LocalX2Apic) = lapic }; - - mat_data + } + .as_bytes() + .to_vec() } } From 246890802b51beb0f575fb285900c8ff4adf5e9d Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 29 Apr 2026 18:36:53 -0700 Subject: [PATCH 1441/1893] 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 --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- fuzz/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fdab21b8e5..128f468aca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1353,9 +1353,9 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mshv-bindings" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94fc3871dd23738188e5bc76a1d1a5930ebcaf9308c560a7274aa62b1770594" +checksum = "83303108160c2b7a7bdd25000ee679384e19471386d23e501ed832574c9229ef" dependencies = [ "libc", "num_enum", @@ -1367,9 +1367,9 @@ dependencies = [ [[package]] name = "mshv-ioctls" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1339723fe3a26baf4041459de20ad923e89d312c3bb25dbf9f60738c22a47f5e" +checksum = "1db4449ac7012237b133da366f5b32ce4af1f8caf770486e5a9d54f7f6b73c4c" dependencies = [ "libc", "mshv-bindings", diff --git a/Cargo.toml b/Cargo.toml index 84ed42012c..aff98c0c21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,8 +57,8 @@ iommufd-ioctls = "0.1.0" kvm-bindings = "0.14.0" kvm-ioctls = "0.24.0" linux-loader = "0.13.2" -mshv-bindings = "0.6.8" -mshv-ioctls = "0.6.8" +mshv-bindings = "0.6.9" +mshv-ioctls = "0.6.9" seccompiler = "0.5.0" vfio-bindings = { version = "0.6.2", default-features = false } vfio-ioctls = { version = "0.6.0", default-features = false } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 93c4c2d08e..020245b5d5 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -25,7 +25,7 @@ libc = "0.2.186" libfuzzer-sys = "0.4.12" linux-loader = { version = "0.13.2", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } -mshv-bindings = "0.6.8" +mshv-bindings = "0.6.9" net_util = { path = "../net_util" } seccompiler = "0.5.0" virtio-devices = { path = "../virtio-devices" } From 0e7fcc623e0d339c35d58b998336d6bfbc84bc78 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 18:45:05 -0700 Subject: [PATCH 1442/1893] 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 --- test_infra/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index bfdccf0c68..d6e21cd5c1 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -2493,6 +2493,18 @@ pub enum GuestVmType { Confidential, } +impl FromStr for GuestVmType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "regular" => Ok(GuestVmType::Regular), + "confidential" => Ok(GuestVmType::Confidential), + _ => Err(()), + } + } +} + // Get the direct igvm boot file path based on the console type fn direct_igvm_boot_path(console: Option<&str>) -> Option { // get the default hvc0 igvm file if console string is not passed From 11623a21831acd2767ddbff468234b874e621edd Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 18:45:16 -0700 Subject: [PATCH 1443/1893] 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 --- test_infra/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index d6e21cd5c1..6a72255777 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use std::ffi::OsStr; -use std::fmt::Display; +use std::fmt::{Display, Formatter}; use std::fs::OpenOptions; use std::io::{Read, Seek, SeekFrom, Write}; use std::net::{TcpListener, TcpStream}; @@ -2505,6 +2505,15 @@ impl FromStr for GuestVmType { } } +impl Display for GuestVmType { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + GuestVmType::Regular => write!(f, "regular"), + GuestVmType::Confidential => write!(f, "confidential"), + } + } +} + // Get the direct igvm boot file path based on the console type fn direct_igvm_boot_path(console: Option<&str>) -> Option { // get the default hvc0 igvm file if console string is not passed From 4b4845eb4d1480b0e3c2e6808263c13d1d68cb17 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 18:31:20 -0700 Subject: [PATCH 1444/1893] 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 --- performance-metrics/src/main.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 0844838c74..0b5b774be2 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -282,15 +282,17 @@ impl PerformanceTest { ); } - // Run warmup iterations if configured (results discarded) - for _ in 0..self.control.warmup_iterations { + let effective_control = { + let mut control = self.control.clone(); if let Some(test_timeout) = overrides.test_timeout { - let mut control: PerformanceTestControl = self.control.clone(); control.test_timeout = test_timeout; - let _ = (self.func_ptr)(&control); - } else { - let _ = (self.func_ptr)(&self.control); } + control + }; + + // Run warmup iterations if configured (results discarded) + for _ in 0..self.control.warmup_iterations { + let _ = (self.func_ptr)(&effective_control); } let mut metrics = Vec::new(); @@ -298,14 +300,7 @@ impl PerformanceTest { .test_iterations .unwrap_or(self.control.test_iterations) { - // update the timeout in control if passed explicitly and run testcase with it - if let Some(test_timeout) = overrides.test_timeout { - let mut control: PerformanceTestControl = self.control.clone(); - control.test_timeout = test_timeout; - metrics.push((self.func_ptr)(&control)); - } else { - metrics.push((self.func_ptr)(&self.control)); - } + metrics.push((self.func_ptr)(&effective_control)); } let mean = (self.unit_adjuster)(mean(&metrics).unwrap()); From 82a0dc7252f9c0c65075421632b233befcb117a2 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 18:32:11 -0700 Subject: [PATCH 1445/1893] 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 --- performance-metrics/src/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 0b5b774be2..76e9d4b53f 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -17,7 +17,7 @@ use std::{env, fmt, thread}; use clap::{Arg, ArgAction, Command as ClapCommand}; use performance_tests::*; use serde::{Deserialize, Serialize}; -use test_infra::{FioOps, ProcessRegistry}; +use test_infra::{FioOps, GuestVmType, ProcessRegistry}; use thiserror::Error; #[derive(Error, Debug)] @@ -215,6 +215,7 @@ pub struct PerformanceTestControl { block_control: Option, num_boot_vcpus: Option, num_ops: Option, // Workload size for micro benchmarks + vm_type: GuestVmType, } impl fmt::Display for PerformanceTestControl { @@ -243,6 +244,8 @@ impl fmt::Display for PerformanceTestControl { output = format!("{output}, num_ops = {o}"); } + output = format!("{output}, vm_type = {}", self.vm_type); + write!(f, "{output}") } } @@ -259,6 +262,7 @@ impl PerformanceTestControl { block_control: None, num_boot_vcpus: Some(1), num_ops: None, + vm_type: GuestVmType::Regular, } } } From 938db39e260be1423c658d2bc0ed18264a18928a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 18:47:50 -0700 Subject: [PATCH 1446/1893] 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 --- performance-metrics/src/performance_tests.rs | 22 +++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 813963481b..16f80a6c08 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -120,8 +120,14 @@ pub fn cleanup_tests() { // private network. The default constructor "Guest::new()" does not work // well, as we can easily create more than 256 VMs from repeating various // performance tests dozens times in a single run. -fn performance_test_new_guest(disk_config: Box) -> Guest { - Guest::new_from_ip_range(disk_config, "172.19", 0) +fn performance_test_new_guest(disk_config: Box, vm_type: GuestVmType) -> Guest { + let mut guest = Guest::new_from_ip_range(disk_config, "172.19", 0); + if vm_type == GuestVmType::Confidential { + guest.vm_type = GuestVmType::Confidential; + guest.boot_timeout = DEFAULT_CVM_TCP_LISTENER_TIMEOUT; + guest.nested = false; + } + guest } pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 { @@ -129,7 +135,7 @@ pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 { let (rx, bandwidth) = control.net_control.unwrap(); let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal)); + let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); let num_queues = control.num_queues.unwrap(); let queue_size = control.queue_size.unwrap(); @@ -170,7 +176,7 @@ pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 { pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal)); + let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); let num_queues = control.num_queues.unwrap(); let queue_size = control.queue_size.unwrap(); @@ -318,7 +324,7 @@ fn measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result f64 { let r = std::panic::catch_unwind(|| { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal)); + let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); let mut cmd = GuestCommand::new(&guest); let c = cmd @@ -346,7 +352,7 @@ pub fn performance_boot_time(control: &PerformanceTestControl) -> f64 { pub fn performance_boot_time_pmem(control: &PerformanceTestControl) -> f64 { let r = std::panic::catch_unwind(|| { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal)); + let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); let mut cmd = GuestCommand::new(&guest); let c = cmd .args([ @@ -387,7 +393,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { let test_file = block_control.test_file; let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal)); + let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); let api_socket = guest .tmp_dir .as_path() @@ -529,7 +535,7 @@ fn measure_restore_time( pub fn performance_restore_latency(control: &PerformanceTestControl) -> f64 { let r = std::panic::catch_unwind(|| { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal)); + let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); let api_socket_source = String::from( guest .tmp_dir From 55e2700fbcd646a9846ef79f2730ffd20ca966d2 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 18:48:19 -0700 Subject: [PATCH 1447/1893] 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 --- performance-metrics/src/performance_tests.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 16f80a6c08..10c298f2df 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -135,7 +135,7 @@ pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 { let (rx, bandwidth) = control.net_control.unwrap(); let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); + let guest = performance_test_new_guest(Box::new(focal), control.vm_type); let num_queues = control.num_queues.unwrap(); let queue_size = control.queue_size.unwrap(); @@ -176,7 +176,7 @@ pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 { pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); + let guest = performance_test_new_guest(Box::new(focal), control.vm_type); let num_queues = control.num_queues.unwrap(); let queue_size = control.queue_size.unwrap(); @@ -324,7 +324,7 @@ fn measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result f64 { let r = std::panic::catch_unwind(|| { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); + let guest = performance_test_new_guest(Box::new(focal), control.vm_type); let mut cmd = GuestCommand::new(&guest); let c = cmd @@ -352,7 +352,7 @@ pub fn performance_boot_time(control: &PerformanceTestControl) -> f64 { pub fn performance_boot_time_pmem(control: &PerformanceTestControl) -> f64 { let r = std::panic::catch_unwind(|| { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); + let guest = performance_test_new_guest(Box::new(focal), control.vm_type); let mut cmd = GuestCommand::new(&guest); let c = cmd .args([ @@ -393,7 +393,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { let test_file = block_control.test_file; let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); + let guest = performance_test_new_guest(Box::new(focal), control.vm_type); let api_socket = guest .tmp_dir .as_path() @@ -535,7 +535,7 @@ fn measure_restore_time( pub fn performance_restore_latency(control: &PerformanceTestControl) -> f64 { let r = std::panic::catch_unwind(|| { let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); - let guest = performance_test_new_guest(Box::new(focal), GuestVmType::Regular); + let guest = performance_test_new_guest(Box::new(focal), control.vm_type); let api_socket_source = String::from( guest .tmp_dir From 56a986145eabaa6640fe6709ba89d1f6ec736960 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 18:35:27 -0700 Subject: [PATCH 1448/1893] 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 --- performance-metrics/src/main.rs | 4 ++++ test_infra/src/lib.rs | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 76e9d4b53f..b099860ce5 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -178,6 +178,7 @@ pub struct PerformanceTestOverrides { test_iterations: Option, test_timeout: Option, test_image_format: Option, + vm_type: GuestVmType, } impl fmt::Display for PerformanceTestOverrides { @@ -193,6 +194,8 @@ impl fmt::Display for PerformanceTestOverrides { write!(f, "test_image_format = {test_image_format}")?; } + write!(f, "vm_type = {}", self.vm_type)?; + Ok(()) } } @@ -1887,6 +1890,7 @@ fn main() { .map(|s| s.parse()) .transpose() .unwrap_or_default(), + vm_type: GuestVmType::Regular, }); // Skip heavy VM level init/cleanup when only micro benchmarks are selected. diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 6a72255777..9f825da9f4 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -2487,8 +2487,9 @@ pub fn extract_bar_address(output: &str, device_desc: &str, bar_index: usize) -> None } -#[derive(PartialEq, Clone, Copy)] +#[derive(PartialEq, Clone, Copy, Default)] pub enum GuestVmType { + #[default] Regular, Confidential, } From ab7749a5ce83eb6c8202b337a8c7223acb2c97f6 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 18:36:24 -0700 Subject: [PATCH 1449/1893] 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 --- performance-metrics/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index b099860ce5..f86ab1982f 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -294,6 +294,7 @@ impl PerformanceTest { if let Some(test_timeout) = overrides.test_timeout { control.test_timeout = test_timeout; } + control.vm_type = overrides.vm_type; control }; From 7d684e3addfbf565dedeba7eced54bcb8c9f9bd9 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 1 Apr 2026 18:37:28 -0700 Subject: [PATCH 1450/1893] 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 --- performance-metrics/src/main.rs | 43 +++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index f86ab1982f..99ddf699fa 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -183,20 +183,20 @@ pub struct PerformanceTestOverrides { impl fmt::Display for PerformanceTestOverrides { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(test_iterations) = self.test_iterations { - write!(f, "test_iterations = {test_iterations}, ")?; - } - if let Some(test_timeout) = self.test_timeout { - write!(f, "test_timeout = {test_timeout}")?; - } - - if let Some(test_image_format) = self.test_image_format { - write!(f, "test_image_format = {test_image_format}")?; - } - - write!(f, "vm_type = {}", self.vm_type)?; - - Ok(()) + write!( + f, + "{}{}{}vm_type = {}", + self.test_iterations + .map(|v| format!("test_iterations = {v}, ")) + .unwrap_or_default(), + self.test_timeout + .map(|v| format!("test_timeout = {v}, ")) + .unwrap_or_default(), + self.test_image_format + .map(|v| format!("test_image_format = {v}, ")) + .unwrap_or_default(), + self.vm_type + ) } } @@ -1837,6 +1837,16 @@ fn main() { ) .num_args(1), ) + .arg( + Arg::new("vm-type") + .long("vm-type") + .help( + "Set the VM type: 'regular' (default) or 'confidential' (CVM).", + ) + .num_args(1) + .value_parser(["regular","confidential"]) + .default_value("regular"), + ) .get_matches(); // It seems that the tool (ethr) used for testing the virtio-net latency @@ -1891,7 +1901,10 @@ fn main() { .map(|s| s.parse()) .transpose() .unwrap_or_default(), - vm_type: GuestVmType::Regular, + vm_type: cmd_arguments + .get_one::("vm-type") + .map(|s| s.parse().unwrap_or_default()) + .unwrap_or_default(), }); // Skip heavy VM level init/cleanup when only micro benchmarks are selected. From 3440802c997965392fdd9ea9affcd3a3109b3089 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 2 Apr 2026 23:42:39 -0700 Subject: [PATCH 1451/1893] 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 --- performance-metrics/src/main.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/performance-metrics/src/main.rs b/performance-metrics/src/main.rs index 99ddf699fa..9687ae1c27 100644 --- a/performance-metrics/src/main.rs +++ b/performance-metrics/src/main.rs @@ -1907,6 +1907,14 @@ fn main() { .unwrap_or_default(), }); + #[cfg(target_arch = "aarch64")] + { + if overrides.vm_type == GuestVmType::Confidential { + eprintln!("Confidential VM is currently not supported on Arm64"); + std::process::exit(1); + } + } + // Skip heavy VM level init/cleanup when only micro benchmarks are selected. let needs_vm_tests = tests_to_run.iter().any(|t| !t.name.starts_with("micro_")); From 89cae74ddd37603feecb4b5e52d61ea60a4b32be Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 3 Apr 2026 01:12:16 -0700 Subject: [PATCH 1452/1893] 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 --- performance-metrics/src/performance_tests.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index 10c298f2df..fd90c19fbe 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -147,8 +147,7 @@ pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 { let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("boot={num_queues}")]) .args(["--memory", "size=4G"]) - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_kernel_cmdline() .default_disks() .args(["--net", net_params.as_str()]) .capture_output() @@ -188,8 +187,7 @@ pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 { let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("boot={num_queues}")]) .args(["--memory", "size=4G"]) - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_kernel_cmdline() .default_disks() .args(["--net", net_params.as_str()]) .capture_output() @@ -333,8 +331,7 @@ pub fn performance_boot_time(control: &PerformanceTestControl) -> f64 { &format!("boot={}", control.num_boot_vcpus.unwrap_or(1)), ]) .args(["--memory", "size=1G"]) - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_kernel_cmdline() .args(["--console", "off"]) .default_disks(); @@ -421,8 +418,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { let mut child = GuestCommand::new(&guest) .args(["--cpus", &format!("boot={num_queues}")]) .args(["--memory", "size=4G"]) - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_kernel_cmdline() .args([ "--disk", format!( @@ -552,8 +548,7 @@ pub fn performance_restore_latency(control: &PerformanceTestControl) -> f64 { &format!("boot={}", control.num_boot_vcpus.unwrap_or(1)), ]) .args(["--memory", "size=256M"]) - .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .default_kernel_cmdline() .args(["--console", "off"]) .default_disks() .set_print_cmd(false) From 739ea8c9fce3ee18e0635b773d03829b0df84b62 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 1 May 2026 09:43:16 +0100 Subject: [PATCH 1453/1893] ci: Remove lychee as a required CI step Remove the dependency on lychee from all-green. Signed-off-by: Rob Bradford --- .github/workflows/ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 07edc10282..36e04e94f1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -743,7 +743,6 @@ jobs: - integration-windows - integration-x86-64-mq - integration-x86-64-pr - - lychee - openapi - package-consistency - preflight From bf3279f0948b84a4076314ddd7f2c88a8f836b80 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 29 Apr 2026 19:26:27 +0100 Subject: [PATCH 1454/1893] 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 --- Cargo.lock | 1 + vmm/Cargo.toml | 3 + vmm/src/memory_manager.rs | 123 +++++++++++++++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 128f468aca..ef41207044 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2557,6 +2557,7 @@ dependencies = [ "serde_json", "serial_buffer", "signal-hook", + "tempfile", "thiserror", "tracer", "uuid", diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 7c8354a0cb..b3b52f4944 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -103,5 +103,8 @@ vmm-sys-util = { workspace = true, features = ["with-serde"] } zbus = { version = "5.14.0", optional = true } zerocopy = { workspace = true, features = ["alloc", "derive"] } +[dev-dependencies] +tempfile = "3" + [lints] workspace = true diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index a01949b0fc..3d2fcdd9a0 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use std::fs::{File, OpenOptions}; use std::io::{self, Read as _, Seek, SeekFrom}; use std::ops::{BitAnd, Not, Sub}; -use std::os::fd::{AsFd, OwnedFd}; +use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU32, Ordering}; @@ -57,6 +57,43 @@ use crate::migration::url_to_path; use crate::vm_config::{HotplugMethod, MemoryConfig, MemoryZoneConfig}; use crate::{GuestMemoryMmap, GuestRegionMmap, MEMORY_MANAGER_SNAPSHOT_ID, uffd}; +/// Find the next populated (data) extent in `[cursor, end)` of `fd`, +/// driven by `lseek(SEEK_DATA)` / `lseek(SEEK_HOLE)`. Returns +/// `Ok(Some((offset, len)))` for the next data extent within the window, +/// or `Ok(None)` if there is no more data. Returns `Err` if the fd or +/// filesystem does not support `SEEK_HOLE` (e.g. hugetlbfs) or for any +/// other I/O error. Callers should treat a first-call error as "fall +/// back to the dense path". +#[allow(dead_code)] +fn next_data_extent(fd: BorrowedFd<'_>, cursor: u64, end: u64) -> io::Result> { + let raw = fd.as_raw_fd(); + // SAFETY: BorrowedFd guarantees the fd is valid for the lifetime of + // the borrow; lseek does not consume or close it. + let data_off = unsafe { libc::lseek(raw, cursor as i64, libc::SEEK_DATA) }; + if data_off < 0 { + let e = io::Error::last_os_error(); + // ENXIO from SEEK_DATA means there is no more data at or after + // cursor. Any other error means the filesystem does not support + // SEEK_HOLE; the caller falls back to the dense path. + return if e.raw_os_error() == Some(libc::ENXIO) { + Ok(None) + } else { + Err(e) + }; + } + let data_off = data_off as u64; + if data_off >= end { + return Ok(None); + } + // SAFETY: same as above. + let hole_off = unsafe { libc::lseek(raw, data_off as i64, libc::SEEK_HOLE) }; + if hole_off < 0 { + return Err(io::Error::last_os_error()); + } + let hole_off = (hole_off as u64).min(end); + Ok(Some((data_off, hole_off - data_off))) +} + struct UffdHandler { stop_event: EventFd, result_rx: Receiver>, @@ -3109,3 +3146,87 @@ impl Migratable for MemoryManager { Ok(table) } } + +#[cfg(test)] +mod unit_tests { + use std::io::{Seek, SeekFrom, Write}; + use std::os::fd::{AsFd, BorrowedFd, FromRawFd}; + + use super::next_data_extent; + + fn make_memfd(size: u64) -> std::fs::File { + // SAFETY: memfd_create is a self-contained syscall; we own the + // returned fd. + let fd = unsafe { libc::syscall(libc::SYS_memfd_create, c"sparse-test".as_ptr(), 0u32) }; + assert!(fd >= 0, "memfd_create failed"); + // SAFETY: memfd_create returned a valid fd that we now own; wrap it + // in File so it is closed on drop. + let f = unsafe { std::fs::File::from_raw_fd(fd as i32) }; + f.set_len(size).unwrap(); + f + } + + fn collect_extents( + fd: BorrowedFd<'_>, + start: u64, + end: u64, + ) -> std::io::Result> { + let mut out = Vec::new(); + let mut cursor = start; + while let Some((off, len)) = next_data_extent(fd, cursor, end)? { + out.push((off, len)); + cursor = off + len; + } + Ok(out) + } + + #[test] + fn empty_memfd_has_no_data_extents() { + let f = make_memfd(4096 * 16); + let extents = collect_extents(f.as_fd(), 0, 4096 * 16).unwrap(); + assert!(extents.is_empty(), "got {extents:?}"); + } + + #[test] + fn written_pages_show_as_data_extents() { + let mut f = make_memfd(4096 * 16); + f.seek(SeekFrom::Start(4096 * 2)).unwrap(); + f.write_all(&[0xAB; 4096]).unwrap(); + f.seek(SeekFrom::Start(4096 * 5)).unwrap(); + f.write_all(&[0xCD; 4096 * 2]).unwrap(); + + let extents = collect_extents(f.as_fd(), 0, 4096 * 16).unwrap(); + assert_eq!(extents, vec![(4096 * 2, 4096), (4096 * 5, 4096 * 2)]); + } + + #[test] + fn enumeration_respects_window() { + let mut f = make_memfd(4096 * 16); + for page in 0..16u64 { + f.seek(SeekFrom::Start(4096 * page)).unwrap(); + f.write_all(&[page as u8; 4096]).unwrap(); + } + let extents = collect_extents(f.as_fd(), 4096 * 4, 4096 * 8).unwrap(); + assert_eq!(extents, vec![(4096 * 4, 4096 * 4)]); + } + + #[test] + fn dense_file_yields_single_extent() { + let mut tmp = tempfile::NamedTempFile::new().unwrap(); + tmp.write_all(&vec![0xEEu8; 4096 * 8]).unwrap(); + let f = tmp.reopen().unwrap(); + let extents = collect_extents(f.as_fd(), 0, 4096 * 8).unwrap(); + assert_eq!(extents, vec![(0, 4096 * 8)]); + } + + #[test] + fn sparse_file_yields_extents_at_written_positions() { + let tmp = tempfile::NamedTempFile::new().unwrap(); + let mut f = tmp.reopen().unwrap(); + f.set_len(4096 * 16).unwrap(); + f.seek(SeekFrom::Start(4096 * 4)).unwrap(); + f.write_all(&[0x55; 4096 * 2]).unwrap(); + let extents = collect_extents(f.as_fd(), 0, 4096 * 16).unwrap(); + assert_eq!(extents, vec![(4096 * 4, 4096 * 2)]); + } +} From ee1d9dae343eb3034fcf70a38e377b02afc5e987 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 29 Apr 2026 19:46:56 +0100 Subject: [PATCH 1455/1893] 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 --- vmm/src/memory_manager.rs | 230 ++++++++++++++++++++++++++++++++++---- 1 file changed, 209 insertions(+), 21 deletions(-) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 3d2fcdd9a0..aa04c8cc3e 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -10,6 +10,7 @@ use std::fs::{File, OpenOptions}; use std::io::{self, Read as _, Seek, SeekFrom}; use std::ops::{BitAnd, Not, Sub}; use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; +use std::os::unix::fs::FileExt; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU32, Ordering}; @@ -64,7 +65,6 @@ use crate::{GuestMemoryMmap, GuestRegionMmap, MEMORY_MANAGER_SNAPSHOT_ID, uffd}; /// filesystem does not support `SEEK_HOLE` (e.g. hugetlbfs) or for any /// other I/O error. Callers should treat a first-call error as "fall /// back to the dense path". -#[allow(dead_code)] fn next_data_extent(fd: BorrowedFd<'_>, cursor: u64, end: u64) -> io::Result> { let raw = fd.as_raw_fd(); // SAFETY: BorrowedFd guarantees the fd is valid for the lifetime of @@ -3028,6 +3028,74 @@ impl Snapshottable for MemoryManager { } } +/// Write a single guest RAM region to the snapshot file at `dst_offset`, +/// streaming populated extents via `SEEK_DATA` / `SEEK_HOLE` on the +/// backing fd. `set_len(total)` must have been called on `dst` by the +/// caller. Returns `Ok(true)` if the region was written sparsely, or +/// `Ok(false)` if `SEEK_HOLE` is unsupported on the source fd (caller +/// should fall back to dense write). +fn write_region_sparse( + src: &File, + src_offset: u64, + dst: &File, + dst_offset: u64, + len: u64, +) -> io::Result { + let src_fd = src.as_fd(); + let end = src_offset + .checked_add(len) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "range overflow"))?; + + // First call to next_data_extent doubles as a SEEK_HOLE-support probe: + // a non-ENXIO error means the filesystem doesn't support sparse-seek; + // tell the caller to use the dense path instead. + let mut next = match next_data_extent(src_fd, src_offset, end) { + Ok(opt) => opt, + Err(_) => return Ok(false), + }; + + const CHUNK: usize = 1 << 20; + let mut buf = vec![0u8; CHUNK]; + + while let Some((data_off, ext_len)) = next { + debug_assert!(data_off >= src_offset); + let in_region = data_off + .checked_sub(src_offset) + .expect("extent precedes src_offset"); + let mut written = 0u64; + while written < ext_len { + let this = ((ext_len - written) as usize).min(CHUNK); + let slice = &mut buf[..this]; + let read = src.read_at(slice, data_off + written)?; + if read == 0 { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "read_at returned 0 inside data extent", + )); + } + let mut wrote_total = 0; + while wrote_total < read { + let n = dst.write_at( + &slice[wrote_total..read], + dst_offset + in_region + written + wrote_total as u64, + )?; + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "write_at returned 0", + )); + } + wrote_total += n; + } + written += read as u64; + } + // Subsequent next_data_extent failures are real I/O errors, not + // unsupported-FS, since the first probe succeeded. + next = next_data_extent(src_fd, data_off + ext_len, end)?; + } + Ok(true) +} + impl Transportable for MemoryManager { fn send( &self, @@ -3041,38 +3109,85 @@ impl Transportable for MemoryManager { let mut memory_file_path = url_to_path(destination_url)?; memory_file_path.push(String::from(SNAPSHOT_FILENAME)); - // Create the snapshot file for the entire memory let mut memory_file = OpenOptions::new() .read(true) .write(true) .create_new(true) - .open(memory_file_path) + .open(&memory_file_path) .map_err(|e| MigratableError::MigrateSend(e.into()))?; + let total_len: u64 = self + .snapshot_memory_ranges + .regions() + .iter() + .map(|r| r.length) + .sum(); + + // Pre-size the file so per-region write_at lands at the dense-layout + // offset. 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 extending the file is not + // supported by the destination filesystem (some FUSE backends + // reject ftruncate-extend with EOPNOTSUPP), fall back to the dense + // write path which never writes past the growing EOF. + let sparse_layout = memory_file.set_len(total_len).is_ok(); + let guest_memory = self.guest_memory.memory(); + let mut file_cursor: u64 = 0; for range in self.snapshot_memory_ranges.regions() { - let mut offset: u64 = 0; - // Here we are manually handling the retry in case we can't read - // the whole region at once because we can't use the implementation - // from vm-memory::GuestMemory of write_all_to() as it is not - // following the correct behavior. For more info about this issue - // see: https://github.com/rust-vmm/vm-memory/issues/174 - loop { - let bytes_written = guest_memory - .write_volatile_to( - GuestAddress(range.gpa + offset), - &mut memory_file, - (range.length - offset) as usize, - ) - .map_err(|e| MigratableError::MigrateSend(e.into()))?; - offset += bytes_written as u64; + let mut wrote_sparse = false; + if sparse_layout + && let Some(region) = guest_memory.find_region(GuestAddress(range.gpa)) + && (region.flags() & libc::MAP_SHARED) == libc::MAP_SHARED + && let Some(file_offset) = region.file_offset() + { + let region_base = region.start_addr().raw_value(); + let in_fd_off = file_offset.start() + + range + .gpa + .checked_sub(region_base) + .expect("range outside its region"); + wrote_sparse = write_region_sparse( + file_offset.file(), + in_fd_off, + &memory_file, + file_cursor, + range.length, + ) + .map_err(|e| MigratableError::MigrateSend(e.into()))?; + } - if offset == range.length { - break; + if !wrote_sparse { + // Dense fallback: anonymous mmap or hugetlbfs (no + // SEEK_HOLE). Match the dense layout by seeking to + // file_cursor and streaming bytes via the existing + // volatile copy. + memory_file + .seek(SeekFrom::Start(file_cursor)) + .map_err(|e| MigratableError::MigrateSend(e.into()))?; + let mut offset: u64 = 0; + // Manual partial-write loop preserves the workaround for + // https://github.com/rust-vmm/vm-memory/issues/174 + loop { + let bytes_written = guest_memory + .write_volatile_to( + GuestAddress(range.gpa + offset), + &mut memory_file, + (range.length - offset) as usize, + ) + .map_err(|e| MigratableError::MigrateSend(e.into()))?; + offset += bytes_written as u64; + if offset == range.length { + break; + } } } + + file_cursor += range.length; } + + debug_assert_eq!(file_cursor, total_len); Ok(()) } } @@ -3152,7 +3267,7 @@ mod unit_tests { use std::io::{Seek, SeekFrom, Write}; use std::os::fd::{AsFd, BorrowedFd, FromRawFd}; - use super::next_data_extent; + use super::{next_data_extent, write_region_sparse}; fn make_memfd(size: u64) -> std::fs::File { // SAFETY: memfd_create is a self-contained syscall; we own the @@ -3180,6 +3295,11 @@ mod unit_tests { Ok(out) } + fn populated(src: &mut std::fs::File, off: u64, byte: u8, len: u64) { + src.seek(SeekFrom::Start(off)).unwrap(); + src.write_all(&vec![byte; len as usize]).unwrap(); + } + #[test] fn empty_memfd_has_no_data_extents() { let f = make_memfd(4096 * 16); @@ -3229,4 +3349,72 @@ mod unit_tests { let extents = collect_extents(f.as_fd(), 0, 4096 * 16).unwrap(); assert_eq!(extents, vec![(4096 * 4, 4096 * 2)]); } + + #[test] + fn single_extent_at_zero_offset() { + let mut src = make_memfd(4096 * 16); + populated(&mut src, 4096 * 3, 0x42, 4096 * 2); + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let dst = tmp.reopen().unwrap(); + dst.set_len(4096 * 16).unwrap(); + + let used = write_region_sparse(&src, 0, &dst, 0, 4096 * 16).unwrap(); + assert!(used); + + use std::os::unix::fs::MetadataExt; + let meta = std::fs::metadata(tmp.path()).unwrap(); + assert!( + meta.blocks() * 512 < meta.len(), + "blocks={} len={}", + meta.blocks(), + meta.len() + ); + + let buf = std::fs::read(tmp.path()).unwrap(); + assert!(buf[..4096 * 3].iter().all(|&b| b == 0)); + assert!(buf[4096 * 3..4096 * 5].iter().all(|&b| b == 0x42)); + assert!(buf[4096 * 5..].iter().all(|&b| b == 0)); + } + + #[test] + fn two_regions_in_same_destination_file_at_dst_offset() { + let mut src_a = make_memfd(4096 * 16); + populated(&mut src_a, 4096, 0xAA, 4096 * 2); + let mut src_b = make_memfd(4096 * 16); + populated(&mut src_b, 4096 * 5, 0xBB, 4096 * 3); + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let dst = tmp.reopen().unwrap(); + dst.set_len(4096 * 32).unwrap(); + + let _ = write_region_sparse(&src_a, 0, &dst, 0, 4096 * 16).unwrap(); + let _ = write_region_sparse(&src_b, 0, &dst, 4096 * 16, 4096 * 16).unwrap(); + + let buf = std::fs::read(tmp.path()).unwrap(); + assert!(buf[..4096].iter().all(|&b| b == 0)); + assert!(buf[4096..4096 * 3].iter().all(|&b| b == 0xAA)); + assert!(buf[4096 * 3..4096 * 16].iter().all(|&b| b == 0)); + assert!(buf[4096 * 16..4096 * 21].iter().all(|&b| b == 0)); + assert!(buf[4096 * 21..4096 * 24].iter().all(|&b| b == 0xBB)); + assert!(buf[4096 * 24..].iter().all(|&b| b == 0)); + } + + #[test] + fn extent_at_non_zero_src_offset() { + let mut src = make_memfd(4096 * 32); + populated(&mut src, 4096 * 20, 0x77, 4096 * 2); + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let dst = tmp.reopen().unwrap(); + dst.set_len(4096 * 16).unwrap(); + + let used = write_region_sparse(&src, 4096 * 16, &dst, 0, 4096 * 16).unwrap(); + assert!(used); + + let buf = std::fs::read(tmp.path()).unwrap(); + assert!(buf[..4096 * 4].iter().all(|&b| b == 0)); + assert!(buf[4096 * 4..4096 * 6].iter().all(|&b| b == 0x77)); + assert!(buf[4096 * 6..].iter().all(|&b| b == 0)); + } } From ffeee2880f4f2d2a5a640554739bfc4f373e1561 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 29 Apr 2026 23:06:52 +0100 Subject: [PATCH 1456/1893] 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 --- vmm/src/memory_manager.rs | 137 +++++++++++++++++++++++++++++++++----- 1 file changed, 119 insertions(+), 18 deletions(-) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index aa04c8cc3e..870333a502 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -411,6 +411,10 @@ pub enum Error { #[error("Error opening snapshot file")] SnapshotOpen(#[source] io::Error), + /// Error reading from snapshot file + #[error("Error reading from snapshot file")] + SnapshotRead(#[source] io::Error), + // Error copying snapshot into region #[error("Error copying snapshot into region")] SnapshotCopy(#[source] GuestMemoryError), @@ -847,27 +851,68 @@ impl MemoryManager { .map_err(Error::SnapshotOpen)?; let guest_memory = self.guest_memory.memory(); - for range in saved_regions.regions() { - let mut offset: u64 = 0; - // Here we are manually handling the retry in case we can't write - // the whole region at once because we can't use the implementation - // from vm-memory::GuestMemory of read_exact_from() as it is not - // following the correct behavior. For more info about this issue - // see: https://github.com/rust-vmm/vm-memory/issues/174 - loop { - let bytes_read = guest_memory - .read_volatile_from( - GuestAddress(range.gpa + offset), - &mut memory_file, - (range.length - offset) as usize, - ) - .map_err(Error::SnapshotCopy)?; - offset += bytes_read as u64; + let mut file_cursor: u64 = 0; - if offset == range.length { - break; + for range in saved_regions.regions() { + let end = file_cursor + range.length; + + // First call doubles as a SEEK_HOLE-support probe. On error, + // take the dense path which seeks-and-streams sequentially. + match next_data_extent(memory_file.as_fd(), file_cursor, end) { + Ok(mut next) => { + while let Some((data_off, ext_len)) = next { + debug_assert!(data_off >= file_cursor); + let in_region = data_off + .checked_sub(file_cursor) + .expect("extent precedes file_cursor"); + memory_file + .seek(SeekFrom::Start(data_off)) + .map_err(Error::SnapshotRead)?; + let mut done: u64 = 0; + while done < ext_len { + let n = guest_memory + .read_volatile_from( + GuestAddress(range.gpa + in_region + done), + &mut memory_file, + (ext_len - done) as usize, + ) + .map_err(Error::SnapshotCopy)?; + if n == 0 { + return Err(Error::SnapshotRead(io::Error::new( + io::ErrorKind::UnexpectedEof, + "read_volatile_from returned 0 inside data extent", + ))); + } + done += n as u64; + } + next = next_data_extent(memory_file.as_fd(), data_off + ext_len, end) + .map_err(Error::SnapshotRead)?; + } + } + Err(_) => { + memory_file + .seek(SeekFrom::Start(file_cursor)) + .map_err(Error::SnapshotRead)?; + let mut offset: u64 = 0; + // Manual partial-read loop preserves the workaround for + // https://github.com/rust-vmm/vm-memory/issues/174 + loop { + let bytes_read = guest_memory + .read_volatile_from( + GuestAddress(range.gpa + offset), + &mut memory_file, + (range.length - offset) as usize, + ) + .map_err(Error::SnapshotCopy)?; + offset += bytes_read as u64; + if offset == range.length { + break; + } + } } } + + file_cursor = end; } Ok(()) @@ -3266,6 +3311,7 @@ impl Migratable for MemoryManager { mod unit_tests { use std::io::{Seek, SeekFrom, Write}; use std::os::fd::{AsFd, BorrowedFd, FromRawFd}; + use std::os::unix::fs::FileExt; use super::{next_data_extent, write_region_sparse}; @@ -3417,4 +3463,59 @@ mod unit_tests { assert!(buf[4096 * 4..4096 * 6].iter().all(|&b| b == 0x77)); assert!(buf[4096 * 6..].iter().all(|&b| b == 0)); } + + /// Round-trip: write two regions sparsely into a snapshot file, then + /// read them back using the same next_data_extent + read_at pattern + /// that fill_saved_regions uses. Verifies the restore path recovers + /// the original content including holes. + #[test] + fn round_trip_sparse_write_then_read() { + let mut src_a = make_memfd(4096 * 16); + populated(&mut src_a, 4096 * 2, 0xAA, 4096 * 3); + + let mut src_b = make_memfd(4096 * 16); + populated(&mut src_b, 4096 * 10, 0xBB, 4096 * 4); + + let tmp = tempfile::NamedTempFile::new().unwrap(); + let dst = tmp.reopen().unwrap(); + let total = 4096u64 * 32; + dst.set_len(total).unwrap(); + + write_region_sparse(&src_a, 0, &dst, 0, 4096 * 16).unwrap(); + write_region_sparse(&src_b, 0, &dst, 4096 * 16, 4096 * 16).unwrap(); + + // Read back using next_data_extent + read_at, mirroring + // fill_saved_regions's sparse restore path. + let snap = tmp.reopen().unwrap(); + let regions: Vec<(u64, u64)> = vec![(0, 4096 * 16), (4096 * 16, 4096 * 16)]; + let mut restored = vec![0u8; total as usize]; + + for &(file_cursor, region_len) in ®ions { + let end = file_cursor + region_len; + let mut cursor = file_cursor; + while let Some((data_off, ext_len)) = + next_data_extent(snap.as_fd(), cursor, end).unwrap() + { + let in_region = (data_off - file_cursor) as usize; + let dst_start = file_cursor as usize + in_region; + snap.read_at( + &mut restored[dst_start..dst_start + ext_len as usize], + data_off, + ) + .unwrap(); + cursor = data_off + ext_len; + } + } + + // Verify content matches a dense read. + let dense = std::fs::read(tmp.path()).unwrap(); + assert_eq!(restored, dense); + + // Verify the actual data landed in the right places. + assert!(restored[..4096 * 2].iter().all(|&b| b == 0)); + assert!(restored[4096 * 2..4096 * 5].iter().all(|&b| b == 0xAA)); + assert!(restored[4096 * 5..4096 * 26].iter().all(|&b| b == 0)); + assert!(restored[4096 * 26..4096 * 30].iter().all(|&b| b == 0xBB)); + assert!(restored[4096 * 30..].iter().all(|&b| b == 0)); + } } From 9f1247fe60d96bc4c89308f64d65258af1037b82 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Wed, 29 Apr 2026 16:55:16 -0700 Subject: [PATCH 1457/1893] 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 --- devices/src/legacy/fw_cfg.rs | 36 +++++++++++++++++++++++++++--------- vmm/src/vm.rs | 24 +++++++++++++++++++++++- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 876ceaaf3c..c5200e5f50 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -441,12 +441,17 @@ impl FwCfg { initramfs: Option, cmdline: Option, fw_cfg_item_list: Option>, + #[cfg(target_arch = "x86_64")] kvm_sev_snp_enabled: bool, ) -> Result<()> { if let Some(mem_size) = mem_size { self.add_e820(mem_size)?; } if let Some(kernel) = kernel { - self.add_kernel_data(&kernel)?; + self.add_kernel_data( + &kernel, + #[cfg(target_arch = "x86_64")] + kvm_sev_snp_enabled, + )?; } if let Some(cmdline) = cmdline { self.add_kernel_cmdline(cmdline); @@ -631,24 +636,37 @@ impl FwCfg { } } - pub fn add_kernel_data(&mut self, file: &File) -> Result<()> { + pub fn add_kernel_data( + &mut self, + file: &File, + #[cfg(target_arch = "x86_64")] kvm_sev_snp_enabled: bool, + ) -> Result<()> { let mut buffer = vec![0u8; size_of::()]; file.read_exact_at(&mut buffer, 0)?; let bp = boot_params::from_mut_slice(&mut buffer).unwrap(); #[cfg(target_arch = "x86_64")] { - // must set to 4 for backwards compatibility - // https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header - if bp.hdr.setup_sects == 0 { - bp.hdr.setup_sects = 4; + // For SEV-SNP guests on KVM, don't modify the kernel header so the + // bytes sent via fw_cfg match what the VMM hashes for the launch digest. + // The guest firmware handles these fields itself. + if !kvm_sev_snp_enabled { + if bp.hdr.setup_sects == 0 { + bp.hdr.setup_sects = 4; + } + bp.hdr.type_of_loader = 0xff; } - // wildcard boot loader type - bp.hdr.type_of_loader = 0xff; } #[cfg(target_arch = "aarch64")] let kernel_start = bp.text_offset; #[cfg(target_arch = "x86_64")] - let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512; + let kernel_start = { + let sects = if bp.hdr.setup_sects == 0 { + 4 + } else { + bp.hdr.setup_sects + }; + (sects as usize + 1) * 512 + }; #[cfg(target_arch = "x86_64")] if kernel_start <= buffer.len() { diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 3d5315b192..d479ad7229 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1139,6 +1139,7 @@ impl Vm { fw_cfg_config: &FwCfgConfig, device_manager: &Arc>, config: &Arc>, + #[cfg(target_arch = "x86_64")] kvm_sev_snp_enabled: bool, ) -> Result<()> { let mut e820_option: Option = None; if fw_cfg_config.e820 { @@ -1222,6 +1223,8 @@ impl Vm { initramfs_option, cmdline_option, fw_cfg_item_list_option, + #[cfg(target_arch = "x86_64")] + kvm_sev_snp_enabled, ) .map_err(Error::ErrorPopulatingFwCfg)?; Ok(()) @@ -2739,7 +2742,26 @@ impl Vm { .map(|p| p.fw_cfg_config.clone()) .unwrap_or_default() .ok_or(Error::VmMissingConfig)?; - Self::populate_fw_cfg(&fw_cfg_config, &self.device_manager, &self.config)?; + #[cfg(target_arch = "x86_64")] + let kvm_sev_snp_enabled = { + #[cfg(feature = "sev_snp")] + { + self.config.lock().unwrap().is_sev_snp_enabled() + && self.hypervisor.hypervisor_type() == hypervisor::HypervisorType::Kvm + } + #[cfg(not(feature = "sev_snp"))] + { + false + } + }; + + Self::populate_fw_cfg( + &fw_cfg_config, + &self.device_manager, + &self.config, + #[cfg(target_arch = "x86_64")] + kvm_sev_snp_enabled, + )?; if fw_cfg_config.acpi_tables { let tpm_enabled = self.config.lock().unwrap().tpm.is_some(); From 70388fb1bb81815d703b8db1f04d25dfbd251791 Mon Sep 17 00:00:00 2001 From: Kevin Hui Date: Wed, 22 Apr 2026 12:31:15 -0700 Subject: [PATCH 1458/1893] 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 --- Cargo.lock | 62 ++++++++++++ Cargo.toml | 1 + vmm/Cargo.toml | 1 + vmm/src/igvm/igvm_loader.rs | 171 ++++++++++++++++++++++++++++++++-- vmm/src/lib.rs | 7 ++ vmm/src/sev.rs | 181 ++++++++++++++++++++++++++++++++++++ vmm/src/vm.rs | 72 ++++++++++++++ 7 files changed, 487 insertions(+), 8 deletions(-) create mode 100644 vmm/src/sev.rs diff --git a/Cargo.lock b/Cargo.lock index ef41207044..1b1d8c4bc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,6 +343,15 @@ dependencies = [ "zstd", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "blocking" version = "1.6.2" @@ -482,6 +491,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "cpufeatures" version = "0.3.0" @@ -515,6 +530,15 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + [[package]] name = "darling" version = "0.23.0" @@ -599,6 +623,17 @@ dependencies = [ "thousands", ] +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + [[package]] name = "dirs" version = "6.0.0" @@ -992,6 +1027,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + [[package]] name = "hypervisor" version = "0.1.0" @@ -2010,6 +2054,17 @@ dependencies = [ name = "serial_buffer" version = "0.1.0" +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2244,6 +2299,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + [[package]] name = "uds_windows" version = "1.2.1" @@ -2556,6 +2617,7 @@ dependencies = [ "serde", "serde_json", "serial_buffer", + "sha2", "signal-hook", "tempfile", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index aff98c0c21..4667108e46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ flume = "0.12.0" itertools = "0.14.0" libc = "0.2.186" log = "0.4.29" +sha2 = "0.11.0" signal-hook = "0.4.4" thiserror = "2.0.18" uuid = { version = "1.23.1" } diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index b3b52f4944..9e37621d80 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -81,6 +81,7 @@ seccompiler = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } serial_buffer = { path = "../serial_buffer" } +sha2 = { workspace = true } signal-hook = { workspace = true } thiserror = { workspace = true } tracer = { path = "../tracer" } diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 75b70bb7f5..e60d586673 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -39,6 +39,13 @@ use crate::cpu::CpuManager; use crate::igvm::loader::Loader; use crate::igvm::{BootPageAcceptance, HV_PAGE_SIZE, IgvmLoadedInfo, StartupMemoryType}; use crate::memory_manager::{Error as MemoryManagerError, MemoryManager}; +#[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" +))] +use crate::sev::{MeasuredBootInfo, SEV_HASH_BLOCK_ADDRESS, SEV_HASH_BLOCK_SIZE}; #[cfg(feature = "sev_snp")] const ISOLATED_PAGE_SHIFT: u32 = 12; @@ -96,6 +103,29 @@ pub enum Error { MissingIgvm, #[error("Error applying VMSA to vCPU registers: {0}")] SetVmsa(#[source] crate::cpu::Error), + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + #[error("Error building SEV-SNP measured boot hash block")] + MeasuredBoot(#[source] vmm_sys_util::errno::Error), + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + #[error( + "igvmfile inserts unmeasured parameter area [0x{region_start:x}, 0x{region_end:x}) over SEV-SNP kernel hashes region [0x{hash_start:x}, 0x{hash_end:x})" + )] + MeasuredBootHashOverlap { + region_start: u64, + region_end: u64, + hash_start: u64, + hash_end: u64, + }, } // KVM SNP page types — linux/arch/x86/include/uapi/asm/sev-guest.h @@ -225,6 +255,13 @@ pub fn load_igvm( memory_manager: Arc>, cpu_manager: Arc>, cmdline: &str, + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + measured_boot: Option, #[cfg(feature = "sev_snp")] host_data: &Option, ) -> Result, Error> { let hypervisor_type = cpu_manager.lock().unwrap().hypervisor_type(); @@ -275,6 +312,45 @@ pub fn load_igvm( let mut loader = Loader::new(memory); let mut parameter_areas: HashMap = HashMap::new(); + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + let measured_boot_hash_block = if hypervisor_type == HypervisorType::Kvm { + measured_boot + .as_ref() + .map(|measured_boot| { + measured_boot + .build_hash_block() + .map_err(Error::MeasuredBoot) + }) + .transpose()? + } else { + None + }; + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + let measured_boot_hash_page_base = SEV_HASH_BLOCK_ADDRESS / HV_PAGE_SIZE; + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + let measured_boot_hash_offset = (SEV_HASH_BLOCK_ADDRESS % HV_PAGE_SIZE) as usize; + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + let mut measured_boot_hash_block_inserted = measured_boot_hash_block.is_none(); for header in igvm_file.directives() { debug_assert!(header.compatibility_mask().unwrap_or(mask) & mask == mask); @@ -418,6 +494,34 @@ pub fn load_igvm( .map_err(Error::Loader)?; imported_page = true; } + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + if let Some(hash_block) = measured_boot_hash_block.as_ref().filter(|_| { + !imported_page && gpa / HV_PAGE_SIZE == measured_boot_hash_page_base + }) { + let mut page = if data.is_empty() { + vec![0; HV_PAGE_SIZE as usize] + } else { + let mut page = data.clone(); + page.resize(HV_PAGE_SIZE as usize, 0); + page + }; + // If a data page from the bootloader contains this range, + // we need to ensure that the measured boot table is injected + // prior to importing the pages + page[measured_boot_hash_offset + ..measured_boot_hash_offset + SEV_HASH_BLOCK_SIZE] + .copy_from_slice(&hash_block[..SEV_HASH_BLOCK_SIZE]); + loader + .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, &page) + .map_err(Error::Loader)?; + measured_boot_hash_block_inserted = true; + imported_page = true; + } if !imported_page { loader .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, data) @@ -579,14 +683,37 @@ pub fn load_igvm( .get_mut(parameter_area_index) .expect("igvmfile should be valid"); match area { - ParameterAreaState::Allocated { data, max_size } => loader - .import_pages( - gpa / HV_PAGE_SIZE, - *max_size / HV_PAGE_SIZE, - BootPageAcceptance::ExclusiveUnmeasured, - data, - ) - .map_err(Error::Loader)?, + ParameterAreaState::Allocated { data, max_size } => { + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + if measured_boot_hash_block.is_some() { + let region_end = *gpa + *max_size; + let hash_end = SEV_HASH_BLOCK_ADDRESS + SEV_HASH_BLOCK_SIZE as u64; + if *gpa <= SEV_HASH_BLOCK_ADDRESS && hash_end <= region_end { + // In the case of parameter being inserted where the kernel hashes table lies, + // we should reject the igvmfile since it would interfere with the launch digest + return Err(Error::MeasuredBootHashOverlap { + region_start: *gpa, + region_end, + hash_start: SEV_HASH_BLOCK_ADDRESS, + hash_end, + }); + } + } + + loader + .import_pages( + gpa / HV_PAGE_SIZE, + *max_size / HV_PAGE_SIZE, + BootPageAcceptance::ExclusiveUnmeasured, + data, + ) + .map_err(Error::Loader)?; + } ParameterAreaState::Inserted => panic!("igvmfile is invalid, multiple insert"), } *area = ParameterAreaState::Inserted; @@ -605,6 +732,34 @@ pub fn load_igvm( } } + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + if let Some(hash_block) = measured_boot_hash_block.as_ref() + && !measured_boot_hash_block_inserted + { + // Fallback to adding kernel hashes after importing if the page data wasn't found previously + let mut page = vec![0u8; HV_PAGE_SIZE as usize]; + page[measured_boot_hash_offset..measured_boot_hash_offset + SEV_HASH_BLOCK_SIZE] + .copy_from_slice(&hash_block[..SEV_HASH_BLOCK_SIZE]); + loader + .import_pages( + measured_boot_hash_page_base, + 1, + BootPageAcceptance::Exclusive, + &page, + ) + .map_err(Error::Loader)?; + gpas.push(GpaPages { + gpa: measured_boot_hash_page_base * HV_PAGE_SIZE, + page_type: page_types.normal, + page_size: page_types.isolated_page_size_4kb, + }); + } + #[cfg(feature = "sev_snp")] if sev_snp_enabled { memory_manager diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index b0117d729c..4003a241ac 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -91,6 +91,13 @@ pub mod migration_transport; mod pci_segment; pub mod seccomp_filters; mod serial_manager; +#[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" +))] +pub(crate) mod sev; mod sigwinch_listener; mod sync_utils; mod uffd; diff --git a/vmm/src/sev.rs b/vmm/src/sev.rs new file mode 100644 index 0000000000..b3832dece9 --- /dev/null +++ b/vmm/src/sev.rs @@ -0,0 +1,181 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// + +use core::mem::size_of; +use std::ffi::CString; +use std::fs::File; +use std::io::{self, Cursor, Read, Seek, SeekFrom}; +use std::os::unix::fs::FileExt; + +use linux_loader::bootparam::boot_params; +use sha2::{Digest, Sha256}; +use uuid::{Uuid, uuid}; +use vm_memory::ByteValued; +use vmm_sys_util::errno; +use zerocopy::{Immutable, IntoBytes}; + +pub(crate) type Result = std::result::Result; + +// https://github.com/tianocore/edk2/blob/f98662c5e35b6ab60f46ee4350fa0e6eab0497cf/OvmfPkg/Include/Fdf/MemFd.fdf.inc#L89-L93 +pub const SEV_HASH_BLOCK_ADDRESS: u64 = 0x10c00; +pub const SEV_HASH_BLOCK_SIZE: usize = 0x400; +const SHA256_HASH_SIZE: usize = 32; + +// Measured hashes table definitions. These match the definitions found in +// QEMU's implementation: https://gitlab.com/qemu-project/qemu/-/blob/master/target/i386/sev.c#L68 +#[repr(C, packed)] +#[derive(IntoBytes, Immutable)] +struct SevHashTableEntry { + pub guid: [u8; 16], + pub len: u16, + pub hash: [u8; SHA256_HASH_SIZE], +} + +#[repr(C, packed)] +#[derive(IntoBytes, Immutable)] +struct SevHashTable { + pub guid: [u8; 16], + pub len: u16, + pub cmdline: SevHashTableEntry, + pub initrd: SevHashTableEntry, + pub kernel: SevHashTableEntry, +} + +const SEV_HASH_TABLE_PADDING: usize = + size_of::().next_multiple_of(16) - size_of::(); + +#[derive(IntoBytes, Immutable)] +pub struct PaddedSevHashTable { + #[allow(dead_code)] + hash_table: SevHashTable, + #[allow(dead_code)] + padding: [u8; SEV_HASH_TABLE_PADDING], +} + +// These GUIDs are defined in both EDK2 and QEMU: +// https://github.com/tianocore/edk2/blob/master/OvmfPkg/AmdSev/BlobVerifierLibSevHashes/BlobVerifierSevHashes.c#L36-L43 +// https://gitlab.com/qemu-project/qemu/-/blob/master/target/i386/sev.c#L2344-2360 +const SEV_HASH_TABLE_GUID: Uuid = uuid!("9438d606-4f22-4cc9-b479-a793d411fd21"); +const SEV_KERNEL_HASH_GUID: Uuid = uuid!("4de79437-abd2-427f-b835-d5b172d2045b"); +const SEV_INITRD_HASH_GUID: Uuid = uuid!("44baf731-3a2f-4bd7-9af1-41e29169781d"); +const SEV_CMDLINE_HASH_GUID: Uuid = uuid!("97d02dd8-bd20-4c94-aa78-e7714d36ab2a"); + +pub struct MeasuredBootInfo { + pub kernel: File, + // QEMU also makes initrd optional in the hash table + pub initramfs: Option, + pub cmdline: CString, +} + +impl MeasuredBootInfo { + fn measured_boot_io(err: &io::Error) -> errno::Error { + errno::Error::new(err.raw_os_error().unwrap_or(libc::EINVAL)) + } + + fn sha256_reader(mut reader: R) -> Result<[u8; 32]> { + let mut hasher = Sha256::new(); + let mut chunk = [0u8; 8192]; + loop { + let bytes_read = reader + .read(&mut chunk) + .map_err(|e| Self::measured_boot_io(&e))?; + if bytes_read == 0 { + break; + } + hasher.update(&chunk[..bytes_read]); + } + + Ok(hasher.finalize().into()) + } + + pub fn build_hash_block(&self) -> Result<[u8; SEV_HASH_BLOCK_SIZE]> { + // Current tooling appends a NUL byte at the end of cmdlines: + // https://github.com/virtee/sev-snp-measure/blob/main/sevsnpmeasure/sev_hashes.py#L71-L74 + let cmdline_digest: [u8; 32] = Sha256::digest(self.cmdline.as_bytes_with_nul()).into(); + + // If no initrd is provided, we will simply hash over an empty buffer, mimicking + // QEMU's behavior: https://gitlab.com/qemu-project/qemu/-/blob/master/target/i386/sev.c#L2387 + let initrd_digest: [u8; 32] = if let Some(initramfs) = &self.initramfs { + let mut initramfs = initramfs + .try_clone() + .map_err(|e| Self::measured_boot_io(&e))?; + initramfs + .seek(SeekFrom::Start(0)) + .map_err(|e| Self::measured_boot_io(&e))?; + Self::sha256_reader(initramfs)? + } else { + Self::sha256_reader(Cursor::new([]))? + }; + + // The kernel components are split up into a setup section and the actual kernel data, + // so split them up to avoid double counting, assuming this is a bzImage Linux kernel + let mut setup_header = vec![0u8; size_of::()]; + self.kernel + .read_exact_at(&mut setup_header, 0) + .map_err(|e| Self::measured_boot_io(&e))?; + + let kernel_start = { + // Matches QEMU's way of finding the kernel start/setup start + // https://gitlab.com/qemu-project/qemu/-/blob/master/hw/i386/x86-common.c#L903 + let bp = boot_params::from_mut_slice(&mut setup_header).unwrap(); + let setup_sects = if bp.hdr.setup_sects == 0 { + 4 + } else { + bp.hdr.setup_sects + }; + (u64::from(setup_sects) + 1) * 512 + }; + let setup_len = kernel_start as usize; + let mut setup_data = vec![0u8; setup_len]; + if setup_len <= setup_header.len() { + setup_data.copy_from_slice(&setup_header[..setup_len]); + } else { + setup_data[..setup_header.len()].copy_from_slice(&setup_header); + self.kernel + .read_exact_at( + &mut setup_data[setup_header.len()..], + setup_header.len() as u64, + ) + .map_err(|e| Self::measured_boot_io(&e))?; + } + + let mut kernel = self + .kernel + .try_clone() + .map_err(|e| Self::measured_boot_io(&e))?; + kernel + .seek(SeekFrom::Start(kernel_start)) + .map_err(|e| Self::measured_boot_io(&e))?; + let kernel_digest = Self::sha256_reader(Cursor::new(setup_data).chain(kernel))?; + + let table = PaddedSevHashTable { + hash_table: SevHashTable { + guid: SEV_HASH_TABLE_GUID.to_bytes_le(), + len: size_of::() as u16, + cmdline: SevHashTableEntry { + guid: SEV_CMDLINE_HASH_GUID.to_bytes_le(), + len: size_of::() as u16, + hash: cmdline_digest, + }, + initrd: SevHashTableEntry { + guid: SEV_INITRD_HASH_GUID.to_bytes_le(), + len: size_of::() as u16, + hash: initrd_digest, + }, + kernel: SevHashTableEntry { + guid: SEV_KERNEL_HASH_GUID.to_bytes_le(), + len: size_of::() as u16, + hash: kernel_digest, + }, + }, + padding: [0; SEV_HASH_TABLE_PADDING], + }; + + let mut hash_block = [0u8; SEV_HASH_BLOCK_SIZE]; + // The remainder of the page is zeroed to ensure measurements are reliably calculated + hash_block[..size_of::()].copy_from_slice(table.as_bytes()); + Ok(hash_block) + } +} diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index d479ad7229..df23e37e1e 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -105,6 +105,13 @@ use crate::migration::get_vm_snapshot; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::migration::url_to_file; use crate::migration::{SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE, url_to_path}; +#[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" +))] +use crate::sev::MeasuredBootInfo; #[cfg(feature = "fw_cfg")] use crate::vm_config::FwCfgConfig; use crate::vm_config::{ @@ -1545,6 +1552,13 @@ impl Vm { igvm_file: IgvmFile, memory_manager: Arc>, cpu_manager: Arc>, + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + measured_boot: Option, #[cfg(feature = "sev_snp")] host_data: &Option, ) -> Result { // Only reserve bootloader/VMSA regions for KVM + SEV-SNP; other hypervisors @@ -1561,6 +1575,13 @@ impl Vm { memory_manager, cpu_manager.clone(), "", + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + measured_boot, #[cfg(feature = "sev_snp")] host_data, ) @@ -1655,10 +1676,61 @@ impl Vm { if payload.igvm.is_some() { let igvm_file = igvm_file.ok_or(Error::IgvmLoad(igvm_loader::Error::MissingIgvm))?; + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + let measured_boot = if let (true, Some(kernel_path), Some(_cmdline)) = ( + payload + .fw_cfg_config + .as_ref() + .is_some_and(|cfg| cfg.kernel && cfg.cmdline), + payload.kernel.as_ref(), + payload.cmdline.as_ref(), + ) { + let kernel = File::open(kernel_path).map_err(Error::KernelFile)?; + let initramfs = if payload + .fw_cfg_config + .as_ref() + .is_some_and(|cfg| cfg.initramfs) + { + payload + .initramfs + .as_ref() + .map(File::open) + .transpose() + .map_err(Error::InitramfsFile)? + } else { + None + }; + + // Must byte-match the cmdline that populate_fw_cfg sends to the guest, + // otherwise the launch measurement will diverge. + let cmdline = Self::generate_cmdline(payload)? + .as_cstring() + .map_err(Error::CmdLineCreate)?; + + Some(MeasuredBootInfo { + kernel, + initramfs, + cmdline, + }) + } else { + None + }; return Self::load_igvm( igvm_file, memory_manager, cpu_manager, + #[cfg(all( + feature = "kvm", + feature = "sev_snp", + feature = "fw_cfg", + target_arch = "x86_64" + ))] + measured_boot, #[cfg(feature = "sev_snp")] &payload.host_data, ); From e3e22d8e78f0e43207e81caf3969e70e305da6c7 Mon Sep 17 00:00:00 2001 From: Kevin Hui Date: Thu, 23 Apr 2026 09:33:34 -0700 Subject: [PATCH 1459/1893] 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 --- vmm/src/sev.rs | 210 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/vmm/src/sev.rs b/vmm/src/sev.rs index b3832dece9..a235c6f508 100644 --- a/vmm/src/sev.rs +++ b/vmm/src/sev.rs @@ -179,3 +179,213 @@ impl MeasuredBootInfo { Ok(hash_block) } } + +#[cfg(test)] +mod tests { + use std::io::Write; + + use super::*; + + fn make_tempfile(data: &[u8]) -> File { + let mut f = tempfile::tempfile().unwrap(); + f.write_all(data).unwrap(); + f + } + + fn make_fake_kernel(setup_sects: u8, kernel_payload: &[u8]) -> File { + let effective_sects = if setup_sects == 0 { 4 } else { setup_sects }; + let setup_len = (usize::from(effective_sects) + 1) * 512; + let header_size = size_of::(); + let file_len = std::cmp::max(setup_len, header_size) + kernel_payload.len(); + + let mut buf = vec![0u8; file_len]; + let bp = boot_params::from_mut_slice(&mut buf[..header_size]).unwrap(); + bp.hdr.setup_sects = setup_sects; + buf[setup_len..setup_len + kernel_payload.len()].copy_from_slice(kernel_payload); + + make_tempfile(&buf) + } + + #[test] + fn hash_block_deterministic() { + let info = MeasuredBootInfo { + kernel: make_fake_kernel(1, b"KERNEL_DATA"), + initramfs: None, + cmdline: CString::new("console=ttyS0").unwrap(), + }; + let block1 = info.build_hash_block().unwrap(); + let block2 = info.build_hash_block().unwrap(); + assert_eq!(block1, block2); + } + + #[test] + fn hash_block_cmdline_includes_nul() { + let info = MeasuredBootInfo { + kernel: make_fake_kernel(1, b"K"), + initramfs: None, + cmdline: CString::new("test").unwrap(), + }; + let block = info.build_hash_block().unwrap(); + let expected: [u8; 32] = Sha256::digest(b"test\0").into(); + let cmdline_hash_offset = 2 * (size_of::<[u8; 16]>() + size_of::()); + assert_eq!( + &block[cmdline_hash_offset..cmdline_hash_offset + 32], + &expected + ); + } + + #[test] + fn hash_block_no_initrd_hashes_empty() { + let info = MeasuredBootInfo { + kernel: make_fake_kernel(1, b"K"), + initramfs: None, + cmdline: CString::new("").unwrap(), + }; + let block = info.build_hash_block().unwrap(); + let expected: [u8; 32] = Sha256::digest([]).into(); + + let initrd_hash_offset = + 2 * (size_of::<[u8; 16]>() + size_of::()) + size_of::(); + assert_eq!( + &block[initrd_hash_offset..initrd_hash_offset + 32], + &expected + ); + } + + #[test] + fn hash_block_with_initrd() { + let info = MeasuredBootInfo { + kernel: make_fake_kernel(1, b"K"), + initramfs: Some(make_tempfile(b"INITRD_CONTENTS")), + cmdline: CString::new("").unwrap(), + }; + let block = info.build_hash_block().unwrap(); + let expected: [u8; 32] = Sha256::digest(b"INITRD_CONTENTS").into(); + let initrd_hash_offset = + 2 * (size_of::<[u8; 16]>() + size_of::()) + size_of::(); + assert_eq!( + &block[initrd_hash_offset..initrd_hash_offset + 32], + &expected + ); + } + + #[test] + fn hash_block_setup_sects_zero_defaults_to_four() { + let info = MeasuredBootInfo { + kernel: make_fake_kernel(0, b"PAYLOAD"), + initramfs: None, + cmdline: CString::new("root=/dev/sda").unwrap(), + }; + info.build_hash_block().unwrap(); + } + + #[test] + fn hash_block_guid_placement() { + let info = MeasuredBootInfo { + kernel: make_fake_kernel(1, b"K"), + initramfs: None, + cmdline: CString::new("x").unwrap(), + }; + let block = info.build_hash_block().unwrap(); + assert_eq!(&block[0..16], SEV_HASH_TABLE_GUID.to_bytes_le()); + assert_eq!(&block[18..34], SEV_CMDLINE_HASH_GUID.to_bytes_le()); + let initrd_guid_offset = + size_of::<[u8; 16]>() + size_of::() + size_of::(); + assert_eq!( + &block[initrd_guid_offset..initrd_guid_offset + 16], + SEV_INITRD_HASH_GUID.to_bytes_le() + ); + let kernel_guid_offset = initrd_guid_offset + size_of::(); + assert_eq!( + &block[kernel_guid_offset..kernel_guid_offset + 16], + SEV_KERNEL_HASH_GUID.to_bytes_le() + ); + } + + #[test] + fn hash_block_struct_sizes() { + assert_eq!(size_of::(), 16 + 2 + 32); + assert_eq!( + size_of::(), + 16 + 2 + 3 * size_of::() + ); + assert_eq!(size_of::() % 16, 0); + assert!(size_of::() <= SEV_HASH_BLOCK_SIZE); + } + + #[test] + fn hash_block_remainder_is_zeroed() { + let info = MeasuredBootInfo { + kernel: make_fake_kernel(1, b"K"), + initramfs: None, + cmdline: CString::new("").unwrap(), + }; + let block = info.build_hash_block().unwrap(); + let table_end = size_of::(); + assert!(block[table_end..].iter().all(|&b| b == 0)); + } + + fn expected_kernel_digest(setup_sects: u8, kernel_payload: &[u8]) -> [u8; 32] { + let effective_sects = if setup_sects == 0 { 4 } else { setup_sects }; + let setup_len = (usize::from(effective_sects) + 1) * 512; + let header_size = size_of::(); + let file_len = std::cmp::max(setup_len, header_size) + kernel_payload.len(); + + let mut buf = vec![0u8; file_len]; + let bp = boot_params::from_mut_slice(&mut buf[..header_size]).unwrap(); + bp.hdr.setup_sects = setup_sects; + buf[setup_len..setup_len + kernel_payload.len()].copy_from_slice(kernel_payload); + + Sha256::digest(&buf).into() + } + + fn kernel_hash_offset() -> usize { + size_of::<[u8; 16]>() + + size_of::() + + 2 * size_of::() + + size_of::<[u8; 16]>() + + size_of::() + } + + #[test] + fn hash_block_kernel_digest_setup_sects_1() { + let payload = b"KERNEL_DATA"; + let info = MeasuredBootInfo { + kernel: make_fake_kernel(1, payload), + initramfs: None, + cmdline: CString::new("").unwrap(), + }; + let block = info.build_hash_block().unwrap(); + let expected = expected_kernel_digest(1, payload); + let offset = kernel_hash_offset(); + assert_eq!(&block[offset..offset + 32], &expected); + } + + #[test] + fn hash_block_kernel_digest_setup_sects_0() { + let payload = b"KERNEL_DATA"; + let info = MeasuredBootInfo { + kernel: make_fake_kernel(0, payload), + initramfs: None, + cmdline: CString::new("").unwrap(), + }; + let block = info.build_hash_block().unwrap(); + let expected = expected_kernel_digest(0, payload); + let offset = kernel_hash_offset(); + assert_eq!(&block[offset..offset + 32], &expected); + } + + #[test] + fn hash_block_kernel_digest_large_setup_sects() { + let payload = b"KERNEL_PAYLOAD_LARGE_SETUP"; + let info = MeasuredBootInfo { + kernel: make_fake_kernel(8, payload), + initramfs: None, + cmdline: CString::new("").unwrap(), + }; + let block = info.build_hash_block().unwrap(); + let expected = expected_kernel_digest(8, payload); + let offset = kernel_hash_offset(); + assert_eq!(&block[offset..offset + 32], &expected); + } +} From 982934fba0cdab88682ad1e099a3e10eb10f36bd Mon Sep 17 00:00:00 2001 From: Kevin Hui Date: Wed, 22 Apr 2026 13:11:19 -0700 Subject: [PATCH 1460/1893] 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 --- vmm/src/igvm/igvm_loader.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index e60d586673..b27a897770 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -134,6 +134,8 @@ const KVM_SNP_PAGE_TYPE_NORMAL: u32 = 1; #[cfg(feature = "kvm")] const KVM_SNP_PAGE_TYPE_VMSA: u32 = 2; #[cfg(feature = "kvm")] +const KVM_SNP_PAGE_TYPE_ZERO: u32 = 3; +#[cfg(feature = "kvm")] const KVM_SNP_PAGE_TYPE_UNMEASURED: u32 = 4; #[cfg(feature = "kvm")] const KVM_SNP_PAGE_TYPE_SECRETS: u32 = 5; @@ -144,6 +146,8 @@ const KVM_SNP_PAGE_TYPE_CPUID: u32 = 6; struct PageTypeConfig { isolated_page_size_4kb: u32, normal: u32, + #[allow(dead_code)] + zero: u32, unmeasured: u32, cpuid: u32, secrets: u32, @@ -270,6 +274,7 @@ pub fn load_igvm( HypervisorType::Mshv => PageTypeConfig { isolated_page_size_4kb: mshv_bindings::hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, normal: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_NORMAL, + zero: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_ZERO, unmeasured: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED, cpuid: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_CPUID, secrets: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_SECRETS, @@ -279,6 +284,7 @@ pub fn load_igvm( HypervisorType::Kvm => PageTypeConfig { isolated_page_size_4kb: HV_PAGE_SIZE as u32, normal: KVM_SNP_PAGE_TYPE_NORMAL, + zero: KVM_SNP_PAGE_TYPE_ZERO, unmeasured: KVM_SNP_PAGE_TYPE_UNMEASURED, cpuid: KVM_SNP_PAGE_TYPE_CPUID, secrets: KVM_SNP_PAGE_TYPE_SECRETS, @@ -378,9 +384,14 @@ pub fn load_igvm( }); BootPageAcceptance::ExclusiveUnmeasured } else { + let page_type = match hypervisor_type { + #[cfg(feature = "kvm")] + HypervisorType::Kvm if data.is_empty() => page_types.zero, + _ => page_types.normal, + }; gpas.push(GpaPages { gpa: *gpa, - page_type: page_types.normal, + page_type, page_size: page_types.isolated_page_size_4kb, }); BootPageAcceptance::Exclusive @@ -521,6 +532,10 @@ pub fn load_igvm( .map_err(Error::Loader)?; measured_boot_hash_block_inserted = true; imported_page = true; + if let Some(last) = gpas.last_mut() { + debug_assert_eq!(last.gpa, *gpa); + last.page_type = page_types.normal; + } } if !imported_page { loader From 4a91b4a608021b8e6dfe435141ecb962668066f1 Mon Sep 17 00:00:00 2001 From: Kevin Hui Date: Fri, 1 May 2026 09:25:30 -0700 Subject: [PATCH 1461/1893] 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 --- vmm/src/igvm/igvm_loader.rs | 46 +++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index b27a897770..760c9c9b0a 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -697,6 +697,11 @@ pub fn load_igvm( let area = parameter_areas .get_mut(parameter_area_index) .expect("igvmfile should be valid"); + #[cfg(feature = "kvm")] + let page_count = match area { + ParameterAreaState::Allocated { max_size, .. } => *max_size / HV_PAGE_SIZE, + ParameterAreaState::Inserted => panic!("igvmfile is invalid, multiple insert"), + }; match area { ParameterAreaState::Allocated { data, max_size } => { #[cfg(all( @@ -732,11 +737,25 @@ pub fn load_igvm( ParameterAreaState::Inserted => panic!("igvmfile is invalid, multiple insert"), } *area = ParameterAreaState::Inserted; - gpas.push(GpaPages { - gpa: *gpa, - page_type: page_types.unmeasured, - page_size: page_types.isolated_page_size_4kb, - }); + match hypervisor_type { + #[cfg(feature = "kvm")] + HypervisorType::Kvm => { + for page_index in 0..page_count { + gpas.push(GpaPages { + gpa: *gpa + page_index * HV_PAGE_SIZE, + page_type: page_types.unmeasured, + page_size: page_types.isolated_page_size_4kb, + }); + } + } + _ => { + gpas.push(GpaPages { + gpa: *gpa, + page_type: page_types.unmeasured, + page_size: page_types.isolated_page_size_4kb, + }); + } + } } IgvmDirectiveHeader::ErrorRange { .. } => { todo!("Error Range not supported") @@ -786,14 +805,24 @@ pub fn load_igvm( let mut now = Instant::now(); - // Sort the gpas to group them by the page type - gpas.sort_by_key(|a| a.gpa); + // KVM: preserve original IGVM ordering — the SNP launch digest is order-sensitive. + // MSHV: sort by GPA to group pages by type for fewer hypercalls. + match hypervisor_type { + #[cfg(feature = "kvm")] + HypervisorType::Kvm => {} + _ => gpas.sort_by_key(|a| a.gpa), + } let gpas_grouped = gpas .iter() .fold(Vec::>::new(), |mut acc, gpa| { if let Some(last_vec) = acc.last_mut() && last_vec[0].page_type == gpa.page_type + && match hypervisor_type { + #[cfg(feature = "kvm")] + HypervisorType::Kvm => last_vec[0].page_size == gpa.page_size, + _ => true, + } { last_vec.push(*gpa); return acc; @@ -802,8 +831,7 @@ pub fn load_igvm( acc }); - // Import the pages as a group(by page type) of PFNs to reduce the - // hypercall. + // Import pages as groups of PFNs to reduce hypercalls. for group in gpas_grouped.iter() { info!( "Importing {} page{}", From bfab43e252d31bff9cadc5fb10a772fb397e902e Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Fri, 1 May 2026 09:25:24 -0700 Subject: [PATCH 1462/1893] 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 --- vmm/src/igvm/igvm_loader.rs | 16 ++++++++++++++++ vmm/src/vm.rs | 12 ++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 760c9c9b0a..f24fe9c28f 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -8,6 +8,8 @@ use std::mem::size_of; use std::sync::{Arc, Mutex}; use hypervisor::HypervisorType; +#[cfg(feature = "sev_snp")] +use igvm::IgvmInitializationHeader; use igvm::snp_defs::SevVmsa; use igvm::{IgvmDirectiveHeader, IgvmFile, IgvmPlatformHeader}; #[cfg(feature = "sev_snp")] @@ -229,6 +231,20 @@ fn import_parameter( Ok(()) } +/// +/// Extract guest policy from the IGVM initialization headers. +#[cfg(feature = "sev_snp")] +pub fn extract_guest_policy(igvm_file: &IgvmFile) -> Option { + for header in igvm_file.initializations() { + if let IgvmInitializationHeader::GuestPolicy { policy, .. } = header + && *policy != 0 + { + return Some(igvm_defs::SnpPolicy::from_bits(*policy)); + } + } + None +} + /// /// Extract sev_features from the boot CPU (vp_index 0) VMSA. /// diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index df23e37e1e..bd6c9bee4e 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1012,8 +1012,16 @@ impl Vm { .create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID)) .map_err(Error::CpuManager)?; - // Initialize SEV-SNP - transitions guest into secure state - vm.sev_snp_init(Self::get_default_sev_snp_guest_policy()) + // Extract guest policy from IGVM if available, otherwise use default. + #[cfg(feature = "igvm")] + let guest_policy = igvm_file + .as_ref() + .and_then(igvm_loader::extract_guest_policy) + .unwrap_or_else(Self::get_default_sev_snp_guest_policy); + #[cfg(not(feature = "igvm"))] + let guest_policy = Self::get_default_sev_snp_guest_policy(); + + vm.sev_snp_init(guest_policy) .map_err(Error::InitializeSevSnpVm)?; // Load payload for SEV-SNP (IGVM parser needs cpu_manager for cpuid) From c97d635d40e16ffea77c38d35b1e012820cc7cff Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Thu, 30 Apr 2026 06:57:40 -0700 Subject: [PATCH 1463/1893] 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 --- hypervisor/src/kvm/mod.rs | 19 ++- hypervisor/src/kvm/x86_64/sev.rs | 200 ++++++++++++++++++++++++++++++- hypervisor/src/mshv/mod.rs | 3 +- hypervisor/src/vm.rs | 1 + vmm/src/igvm/igvm_loader.rs | 12 ++ vmm/src/igvm/mod.rs | 2 + 6 files changed, 228 insertions(+), 9 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 2f7137d55a..c127a15496 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -563,6 +563,8 @@ pub struct KvmVm { msrs: Vec, #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] sev_fd: Option, + #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] + snp_guest_policy: std::sync::OnceLock, dirty_log_slots: RwLock>, guest_memfds: Option>>, } @@ -689,7 +691,11 @@ impl vm::Vm for KvmVm { .as_ref() .unwrap() .launch_start(&self.fd, guest_policy) - .map_err(|e| vm::HypervisorVmError::InitializeSevSnp(e.into())) + .map_err(|e| vm::HypervisorVmError::InitializeSevSnp(e.into()))?; + self.snp_guest_policy + .set(guest_policy.into_bits()) + .expect("sev_snp_init called more than once"); + Ok(()) } #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] @@ -736,15 +742,22 @@ impl vm::Vm for KvmVm { snp_id_block: igvm_defs::IGVM_VHS_SNP_ID_BLOCK, host_data: [u8; 32], id_block_enabled: u8, + auth_key_enabled: u8, ) -> vm::Result<()> { + let guest_policy = *self + .snp_guest_policy + .get() + .expect("complete_isolated_import called before sev_snp_init"); self.sev_fd .as_ref() .unwrap() .launch_finish( &self.fd, + &snp_id_block, host_data, id_block_enabled, - snp_id_block.author_key_enabled, + auth_key_enabled, + guest_policy, ) .map_err(|e| vm::HypervisorVmError::CompleteIsolatedImport(e.into())) } @@ -1606,6 +1619,8 @@ impl hypervisor::Hypervisor for KvmHypervisor { dirty_log_slots: RwLock::new(HashMap::new()), #[cfg(feature = "sev_snp")] sev_fd, + #[cfg(feature = "sev_snp")] + snp_guest_policy: std::sync::OnceLock::new(), guest_memfds, })) } diff --git a/hypervisor/src/kvm/x86_64/sev.rs b/hypervisor/src/kvm/x86_64/sev.rs index 6249468fef..390a0ca84f 100644 --- a/hypervisor/src/kvm/x86_64/sev.rs +++ b/hypervisor/src/kvm/x86_64/sev.rs @@ -8,11 +8,12 @@ use std::os::fd::{AsRawFd, OwnedFd}; use std::os::unix::fs::OpenOptionsExt; use std::path::Path; -use igvm_defs::SnpPolicy; +use igvm_defs::{IGVM_VHS_SNP_ID_BLOCK, SnpPolicy}; use kvm_bindings::kvm_sev_cmd; use kvm_ioctls::VmFd; use log::{debug, error, info}; use vmm_sys_util::errno; +use zerocopy::{FromZeros, Immutable, IntoBytes}; pub(crate) type Result = std::result::Result; @@ -96,6 +97,88 @@ pub(crate) struct KvmSevSnpLaunchFinish { pub pad1: [u64; 4], } +// See AMD Spec Section 8.18 — Structure of the ID Block +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +#[repr(C)] +#[derive(Debug, Copy, Clone, IntoBytes, Immutable)] +pub(crate) struct KvmSevSnpIdBlock { + pub ld: [u8; 48], + pub family_id: [u8; 16], + pub image_id: [u8; 16], + pub version: u32, + pub guest_svn: u32, + pub policy: u64, +} + +// See AMD Spec Section 8.18 — Layout of the ID Authentication Information Structure +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +#[repr(C)] +#[derive(Clone, FromZeros, IntoBytes, Immutable)] +pub(crate) struct KvmSevSnpIdAuth { + pub id_key_alg: u32, + pub auth_key_algo: u32, + pub reserved: [u8; 56], + pub id_block_sig: [u8; 512], + pub id_key: [u8; 1028], + pub reserved2: [u8; 60], + pub id_key_sig: [u8; 512], + pub author_key: [u8; 1028], + pub reserved3: [u8; 892], +} + +// Must be 1 +// AMD SEV-SNP Firmware ABI, Section 8.18 — Structure of the ID Block +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +const IGVM_SEV_ID_BLOCK_VERSION: u32 = 1; + +fn build_id_block(snp_id_block: &IGVM_VHS_SNP_ID_BLOCK, guest_policy: u64) -> KvmSevSnpIdBlock { + KvmSevSnpIdBlock { + ld: snp_id_block.ld, + family_id: snp_id_block.family_id, + image_id: snp_id_block.image_id, + version: IGVM_SEV_ID_BLOCK_VERSION, + guest_svn: snp_id_block.guest_svn, + policy: guest_policy, + } +} + +// SEV-SNP Firmware ABI Spec Chapter 10: Format for an ECDSA P-384 Public Key +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +fn serialize_public_key(curve: u32, qx: &[u8; 72], qy: &[u8; 72]) -> [u8; 1028] { + let mut key = [0u8; 0x404]; + key[..0x004].copy_from_slice(&curve.to_le_bytes()); + key[0x004..0x04C].copy_from_slice(qx); + key[0x04C..0x094].copy_from_slice(qy); + key +} + +fn build_id_auth(snp_id_block: &IGVM_VHS_SNP_ID_BLOCK) -> KvmSevSnpIdAuth { + let mut id_auth = KvmSevSnpIdAuth::new_zeroed(); + + id_auth.id_key_alg = snp_id_block.id_key_algorithm; + id_auth.auth_key_algo = snp_id_block.author_key_algorithm; + + let sig = snp_id_block.id_key_signature.as_bytes(); + id_auth.id_block_sig[..sig.len()].copy_from_slice(sig); + + id_auth.id_key = serialize_public_key( + snp_id_block.id_public_key.curve, + &snp_id_block.id_public_key.qx, + &snp_id_block.id_public_key.qy, + ); + + let sig = snp_id_block.author_key_signature.as_bytes(); + id_auth.id_key_sig[..sig.len()].copy_from_slice(sig); + + id_auth.author_key = serialize_public_key( + snp_id_block.author_public_key.curve, + &snp_id_block.author_public_key.qx, + &snp_id_block.author_public_key.qy, + ); + + id_auth +} + impl SevFd { pub(crate) fn new(sev_path: impl AsRef) -> Result { let file = OpenOptions::new() @@ -132,9 +215,6 @@ impl SevFd { } pub(crate) fn launch_start(&self, vm: &VmFd, guest_policy: SnpPolicy) -> Result<()> { - // See AMD Spec Section 4.3 - Guest Policy - // Bit 17 is reserved and has to be one. - // https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP let mut start: KvmSevSnpLaunchStart = KvmSevSnpLaunchStart { policy: guest_policy.into_bits(), ..Default::default() @@ -177,14 +257,21 @@ impl SevFd { pub(crate) fn launch_finish( &self, vm: &VmFd, + snp_id_block: &IGVM_VHS_SNP_ID_BLOCK, host_data: [u8; 32], id_block_en: u8, auth_key_en: u8, + guest_policy: u64, ) -> Result<()> { + let id_block = build_id_block(snp_id_block, guest_policy); + let id_auth = build_id_auth(snp_id_block); + let mut finish = KvmSevSnpLaunchFinish { - host_data, + id_block_uaddr: id_block.as_bytes().as_ptr() as u64, + id_auth_uaddr: id_auth.as_bytes().as_ptr() as u64, id_block_en, auth_key_en, + host_data, ..Default::default() }; let mut sev_cmd = kvm_sev_cmd { @@ -194,7 +281,108 @@ impl SevFd { ..Default::default() }; let flags = finish.flags; - debug!("Calling KVM_SEV_SNP_LAUNCH_FINISH, flags: {flags}"); + debug!( + "KVM_SEV_SNP_LAUNCH_FINISH: id_block_en={id_block_en}, auth_key_en={auth_key_en}, policy={guest_policy:#x}, flags={flags}" + ); sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_FINISH") } } + +#[cfg(test)] +mod tests { + use core::mem::size_of; + + use super::*; + + fn make_test_igvm_id_block() -> IGVM_VHS_SNP_ID_BLOCK { + let mut block = IGVM_VHS_SNP_ID_BLOCK::new_zeroed(); + block.ld[0] = 0xAA; + block.ld[47] = 0xBB; + block.family_id[0] = 0x01; + block.image_id[0] = 0x02; + block.version = 42; + block.guest_svn = 7; + block.id_key_algorithm = 1; + block.author_key_algorithm = 1; + block.id_key_signature.r_comp[0] = 0x10; + block.id_key_signature.s_comp[0] = 0x20; + block.id_public_key.curve = 2; + block.id_public_key.qx[0] = 0x30; + block.id_public_key.qy[0] = 0x40; + block.author_key_signature.r_comp[0] = 0x50; + block.author_key_signature.s_comp[0] = 0x60; + block.author_public_key.curve = 2; + block.author_public_key.qx[0] = 0x70; + block.author_public_key.qy[0] = 0x80; + block + } + + #[test] + fn id_block_struct_sizes() { + assert_eq!(size_of::(), 96); + assert_eq!(size_of::(), 4096); + } + + #[test] + fn build_id_block_maps_fields_correctly() { + let igvm = make_test_igvm_id_block(); + let policy = 0x30000u64; + let id_block = build_id_block(&igvm, policy); + + assert_eq!(id_block.ld, igvm.ld); + assert_eq!(id_block.family_id, igvm.family_id); + assert_eq!(id_block.image_id, igvm.image_id); + assert_eq!(id_block.version, IGVM_SEV_ID_BLOCK_VERSION); + assert_eq!(id_block.guest_svn, igvm.guest_svn); + assert_eq!(id_block.policy, policy); + } + + #[test] + fn build_id_block_policy_at_offset_88() { + let igvm = IGVM_VHS_SNP_ID_BLOCK::new_zeroed(); + let policy = 0xDEAD_BEEF_CAFE_BABEu64; + let id_block = build_id_block(&igvm, policy); + let bytes = id_block.as_bytes(); + assert_eq!(&bytes[88..96], &policy.to_le_bytes()); + } + + #[test] + fn build_id_auth_maps_signatures_correctly() { + let igvm = make_test_igvm_id_block(); + let id_auth = build_id_auth(&igvm); + + assert_eq!(id_auth.id_key_alg, igvm.id_key_algorithm); + assert_eq!(id_auth.auth_key_algo, igvm.author_key_algorithm); + + assert_eq!(id_auth.id_block_sig[0], 0x10); + assert_eq!(id_auth.id_block_sig[72], 0x20); + assert!(id_auth.id_block_sig[144..].iter().all(|&b| b == 0)); + + assert_eq!(id_auth.id_key_sig[0], 0x50); + assert_eq!(id_auth.id_key_sig[72], 0x60); + assert!(id_auth.id_key_sig[144..].iter().all(|&b| b == 0)); + } + + #[test] + fn build_id_auth_serializes_public_keys() { + let igvm = make_test_igvm_id_block(); + let id_auth = build_id_auth(&igvm); + + assert_eq!(&id_auth.id_key[..4], &2u32.to_le_bytes()); + assert_eq!(id_auth.id_key[4], 0x30); + assert_eq!(id_auth.id_key[76], 0x40); + assert!(id_auth.id_key[148..].iter().all(|&b| b == 0)); + + assert_eq!(&id_auth.author_key[..4], &2u32.to_le_bytes()); + assert_eq!(id_auth.author_key[4], 0x70); + assert_eq!(id_auth.author_key[76], 0x80); + assert!(id_auth.author_key[148..].iter().all(|&b| b == 0)); + } + + #[test] + fn build_id_auth_zeroed_input() { + let igvm = IGVM_VHS_SNP_ID_BLOCK::new_zeroed(); + let id_auth = build_id_auth(&igvm); + assert!(id_auth.as_bytes().iter().all(|&b| b == 0)); + } +} diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 502c36f10d..9620454564 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -2318,6 +2318,7 @@ impl vm::Vm for MshvVm { snp_id_block: IGVM_VHS_SNP_ID_BLOCK, host_data: [u8; 32], id_block_enabled: u8, + auth_key_enabled: u8, ) -> vm::Result<()> { let mut auth_info = hv_snp_id_auth_info { id_key_algorithm: snp_id_block.id_key_algorithm, @@ -2351,7 +2352,7 @@ impl vm::Vm for MshvVm { id_auth_info: auth_info, host_data, id_block_enabled, - author_key_enabled: 0, + author_key_enabled: auth_key_enabled, }, }, }; diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 6d3a4a4ae5..03e0dd6911 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -440,6 +440,7 @@ pub trait Vm: Send + Sync + Any { _snp_id_block: IGVM_VHS_SNP_ID_BLOCK, _host_data: [u8; 32], _id_block_enabled: u8, + _auth_key_enabled: u8, ) -> Result<()> { unimplemented!() } diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index f24fe9c28f..4113436a84 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -87,6 +87,8 @@ pub enum Error { Igvm(#[source] std::io::Error), #[error("invalid igvm file")] InvalidIgvmFile(#[source] igvm::Error), + #[error("multiple SNP ID blocks in IGVM file")] + DuplicateSnpIdBlock, #[error("invalid guest memory map")] InvalidGuestMemmap(#[source] arch::Error), #[error("loader error")] @@ -678,6 +680,9 @@ pub fn load_igvm( author_key_signature, author_public_key, } => { + if loaded_info.has_snp_id_block { + return Err(Error::DuplicateSnpIdBlock); + } loaded_info.snp_id_block.compatibility_mask = *compatibility_mask; loaded_info.snp_id_block.author_key_enabled = *author_key_enabled; loaded_info.snp_id_block.reserved = *reserved; @@ -692,6 +697,7 @@ pub fn load_igvm( loaded_info.snp_id_block.id_public_key = **id_public_key; loaded_info.snp_id_block.author_key_signature = **author_key_signature; loaded_info.snp_id_block.author_public_key = **author_public_key; + loaded_info.has_snp_id_block = true; } IgvmDirectiveHeader::X64VbsVpContext { vtl: _, @@ -929,7 +935,12 @@ pub fn load_igvm( let id_block_enabled = if hypervisor_type == HypervisorType::Mshv { 1 } else { + u8::from(loaded_info.has_snp_id_block) + }; + let auth_key_enabled = if hypervisor_type == HypervisorType::Mshv { 0 + } else { + loaded_info.snp_id_block.author_key_enabled }; now = Instant::now(); @@ -942,6 +953,7 @@ pub fn load_igvm( loaded_info.snp_id_block, host_data_contents, id_block_enabled, + auth_key_enabled, ) .map_err(Error::CompleteIsolatedImport)?; diff --git a/vmm/src/igvm/mod.rs b/vmm/src/igvm/mod.rs index ded102bd35..5065adf4ed 100644 --- a/vmm/src/igvm/mod.rs +++ b/vmm/src/igvm/mod.rs @@ -45,6 +45,7 @@ pub struct IgvmLoadedInfo { pub gpas: Vec, pub vmsa_gpa: u64, pub snp_id_block: IGVM_VHS_SNP_ID_BLOCK, + pub has_snp_id_block: bool, pub vmsa: SevVmsa, } @@ -54,6 +55,7 @@ impl Default for IgvmLoadedInfo { gpas: Vec::new(), vmsa_gpa: 0, snp_id_block: IGVM_VHS_SNP_ID_BLOCK::new_zeroed(), + has_snp_id_block: false, vmsa: SevVmsa::new_zeroed(), } } From 032f29da29d728c512ba40dbd6ee23dc29203af1 Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1464/1893] misc: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- performance-metrics/src/micro_bench_block.rs | 13 +++++++------ performance-metrics/src/util.rs | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/performance-metrics/src/micro_bench_block.rs b/performance-metrics/src/micro_bench_block.rs index 604e953f69..4c67694ef6 100644 --- a/performance-metrics/src/micro_bench_block.rs +++ b/performance-metrics/src/micro_bench_block.rs @@ -32,12 +32,12 @@ pub fn micro_bench_aio_drain(control: &PerformanceTestControl) -> f64 { .create_async_io(num_ops as u32) .expect("failed to create AIO context"); - let buf = vec![0xA5u8; BLOCK_SIZE as usize]; + let mut buf = vec![0xA5u8; BLOCK_SIZE as usize]; // Submit all writes. for i in 0..num_ops { let iovec = libc::iovec { - iov_base: buf.as_ptr() as *mut _, + iov_base: buf.as_mut_ptr().cast(), iov_len: buf.len(), }; aio.write_vectored((i as u64 * BLOCK_SIZE) as libc::off_t, &[iovec], i as u64) @@ -378,7 +378,7 @@ pub fn micro_bench_qcow_batch_read(control: &PerformanceTestControl) -> f64 { BatchRequest { offset: (i as u64 * QCOW_CLUSTER_SIZE) as libc::off_t, iovecs: vec![libc::iovec { - iov_base: slice.as_mut_ptr() as *mut libc::c_void, + iov_base: slice.as_mut_ptr().cast(), iov_len: QCOW_CLUSTER_SIZE as usize, }] .into(), @@ -565,15 +565,16 @@ pub fn micro_bench_qcow_batch_write(control: &PerformanceTestControl) -> f64 { .create_async_io(num_ops as u32) .expect("create_async_io failed"); - let buf = vec![0xA5u8; num_ops * QCOW_CLUSTER_SIZE as usize]; + let mut buf = vec![0xA5u8; num_ops * QCOW_CLUSTER_SIZE as usize]; let batch: Vec = (0..num_ops) .map(|i| { - let slice = &buf[i * QCOW_CLUSTER_SIZE as usize..(i + 1) * QCOW_CLUSTER_SIZE as usize]; + let slice = + &mut buf[i * QCOW_CLUSTER_SIZE as usize..(i + 1) * QCOW_CLUSTER_SIZE as usize]; BatchRequest { offset: (i as u64 * QCOW_CLUSTER_SIZE) as libc::off_t, iovecs: vec![libc::iovec { - iov_base: slice.as_ptr() as *mut libc::c_void, + iov_base: slice.as_mut_ptr().cast(), iov_len: QCOW_CLUSTER_SIZE as usize, }] .into(), diff --git a/performance-metrics/src/util.rs b/performance-metrics/src/util.rs index 0f1d4e1b24..ce443c20b1 100644 --- a/performance-metrics/src/util.rs +++ b/performance-metrics/src/util.rs @@ -84,7 +84,7 @@ pub fn drain_completions(async_io: &mut dyn AsyncIo, count: usize) { /// Build an iovec suitable for a read into `buf`. pub fn read_iovec(buf: &mut [u8]) -> libc::iovec { libc::iovec { - iov_base: buf.as_mut_ptr() as *mut libc::c_void, + iov_base: buf.as_mut_ptr().cast(), iov_len: buf.len(), } } @@ -92,7 +92,7 @@ pub fn read_iovec(buf: &mut [u8]) -> libc::iovec { /// Build an iovec suitable for a write from `buf`. pub fn write_iovec(buf: &[u8]) -> libc::iovec { libc::iovec { - iov_base: buf.as_ptr() as *mut libc::c_void, + iov_base: buf.as_ptr().cast::().cast_mut(), iov_len: buf.len(), } } From 8abd9d7db530be0872db69ee016dfcc2f326b0a6 Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1465/1893] tpm: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- tpm/src/emulator.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tpm/src/emulator.rs b/tpm/src/emulator.rs index 0ffd3a62e4..32721fdc48 100644 --- a/tpm/src/emulator.rs +++ b/tpm/src/emulator.rs @@ -8,7 +8,7 @@ use std::path::Path; use std::{mem, ptr}; use anyhow::anyhow; -use libc::{c_void, sockaddr_storage, socklen_t}; +use libc::{sockaddr_storage, socklen_t}; use log::{debug, error}; use thiserror::Error; @@ -158,7 +158,7 @@ impl Emulator { fds[0], libc::SOL_SOCKET, libc::SO_RCVTIMEO, - &tv as *const _ as *const libc::c_void, + (&raw const tv).cast(), std::mem::size_of::() as u32, ); if ret == -1 { @@ -318,8 +318,8 @@ impl Emulator { cmd.buffer, cmd.input_len ); - let data_vecs = [libc::iovec { - iov_base: cmd.buffer.as_ptr() as *mut libc::c_void, + let mut data_vecs = [libc::iovec { + iov_base: cmd.buffer.as_mut_ptr().cast(), iov_len: cmd.input_len, }; 1]; @@ -327,7 +327,7 @@ impl Emulator { let mut msghdr: libc::msghdr = unsafe { mem::zeroed() }; msghdr.msg_name = ptr::null_mut(); msghdr.msg_namelen = 0; - msghdr.msg_iov = data_vecs.as_ptr() as *mut libc::iovec; + msghdr.msg_iov = data_vecs.as_mut_ptr().cast(); msghdr.msg_iovlen = data_vecs.len() as _; msghdr.msg_control = ptr::null_mut(); msghdr.msg_controllen = 0; @@ -348,11 +348,11 @@ impl Emulator { unsafe { let ret = libc::recvfrom( self.data_fd, - cmd.buffer.as_mut_ptr() as *mut c_void, + cmd.buffer.as_mut_ptr().cast(), cmd.buffer.len(), 0, - &mut addr as *mut libc::sockaddr_storage as *mut libc::sockaddr, - &mut len as *mut socklen_t, + (&raw mut addr).cast(), + &raw mut len, ); if ret == -1 { return Err(Error::SendReceive(anyhow!( From cb09c37c55cc522ed50316413a07debf4536693f Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1466/1893] vm-migration: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- vm-migration/src/protocol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs index f927d7a36e..7cccc66453 100644 --- a/vm-migration/src/protocol.rs +++ b/vm-migration/src/protocol.rs @@ -444,7 +444,7 @@ impl MemoryRangeTable { pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> { // SAFETY: the slice is constructed with the correct arguments fd.write_all(unsafe { - std::slice::from_raw_parts(self.data.as_ptr() as *const u8, self.length() as usize) + std::slice::from_raw_parts(self.data.as_ptr().cast(), self.length() as usize) }) .map_err(MigratableError::MigrateSocket) } From 8b101fb89074da2614d5f0cc834bf182ced899a4 Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1467/1893] vmm: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- vmm/src/acpi.rs | 2 +- vmm/src/console_devices.rs | 4 ++-- vmm/src/cpu.rs | 5 +++-- vmm/src/device_manager.rs | 2 +- vmm/src/memory_manager.rs | 14 +++++++------- vmm/src/vm.rs | 2 +- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/vmm/src/acpi.rs b/vmm/src/acpi.rs index ac05306bcf..5809f5ead7 100644 --- a/vmm/src/acpi.rs +++ b/vmm/src/acpi.rs @@ -1278,7 +1278,7 @@ mod tests { // safe to read. Casting to `u8` satisfies alignment requirements. let bytes = unsafe { std::slice::from_raw_parts( - &gi as *const GenericInitiatorAffinity as *const u8, + (&raw const gi).cast::(), std::mem::size_of::(), ) }; diff --git a/vmm/src/console_devices.rs b/vmm/src/console_devices.rs index 32cba7b780..1b21440a24 100644 --- a/vmm/src/console_devices.rs +++ b/vmm/src/console_devices.rs @@ -87,7 +87,7 @@ fn modify_mode( // and we check the return result. let mut termios: termios = unsafe { zeroed() }; // SAFETY: see above - let ret = unsafe { tcgetattr(fd, &mut termios as *mut _) }; + let ret = unsafe { tcgetattr(fd, &raw mut termios) }; if ret < 0 { return vmm_sys_util::errno::errno_result(); } @@ -98,7 +98,7 @@ fn modify_mode( f(&mut termios); // SAFETY: Safe because the syscall will only read the extent of termios and we check // the return result. - let ret = unsafe { tcsetattr(fd, TCSANOW, &termios as *const _) }; + let ret = unsafe { tcsetattr(fd, TCSANOW, &raw const termios) }; if ret < 0 { return vmm_sys_util::errno::errno_result(); } diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index ae7b440765..1f31ff61ee 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -303,7 +303,7 @@ fn core_scheduling_cookie() -> u64 { PR_SCHED_CORE_GET, 0, PR_SCHED_CORE_SCOPE_THREAD, - &mut cookie as *mut u64, + &raw mut cookie, ) }; if ret == -1 { @@ -1187,12 +1187,13 @@ impl CpuManager { .spawn(move || { // Schedule the thread to run on the expected CPU set if let Some(cpuset) = cpuset.as_ref() { + let cpuset: *const libc::cpu_set_t = cpuset; // SAFETY: FFI call with correct arguments let ret = unsafe { libc::sched_setaffinity( 0, std::mem::size_of::(), - cpuset as *const libc::cpu_set_t, + cpuset, ) }; diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 45dc9a2eff..ac85e1553c 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4902,7 +4902,7 @@ impl DeviceManager { .remove_userspace_mapping( mapping.addr.raw_value(), mapping.mapping.size(), - mapping.mapping.as_ptr() as _, + mapping.mapping.as_ptr().cast(), mapping.mergeable, mapping.mem_slot, ) diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 870333a502..60885f2ce6 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -1188,7 +1188,7 @@ impl MemoryManager { let n = unsafe { libc::read( uffd_raw_fd, - &mut msg as *mut uffd::UffdMsg as *mut libc::c_void, + (&raw mut msg).cast(), std::mem::size_of::(), ) }; @@ -1824,7 +1824,7 @@ impl MemoryManager { let res = unsafe { libc::syscall( libc::SYS_mbind, - addr as *mut libc::c_void, + addr.cast::(), len, mode, nodemask.as_ptr(), @@ -2002,7 +2002,7 @@ impl MemoryManager { // SAFETY: FFI call with correct arguments let ret = unsafe { let addr = r.as_ptr().add(offset); - libc::madvise(addr as _, pages * page_size, libc::MADV_POPULATE_WRITE) + libc::madvise(addr.cast(), pages * page_size, libc::MADV_POPULATE_WRITE) }; if ret != 0 { let e = io::Error::last_os_error(); @@ -2021,7 +2021,7 @@ impl MemoryManager { if thp && !hugepages { // SAFETY: FFI call with correct arguments - let ret = unsafe { libc::madvise(region.as_ptr() as _, size, libc::MADV_HUGEPAGE) }; + let ret = unsafe { libc::madvise(region.as_ptr().cast(), size, libc::MADV_HUGEPAGE) }; if ret != 0 { let e = io::Error::last_os_error(); warn!("Failed to mark pages as THP eligible: {e}"); @@ -2319,7 +2319,7 @@ impl MemoryManager { // mmap succeeded. let ret = unsafe { libc::madvise( - userspace_addr as *mut libc::c_void, + userspace_addr.cast(), memory_size as libc::size_t, libc::MADV_DONTDUMP, ) @@ -2335,7 +2335,7 @@ impl MemoryManager { // mmap succeeded. let ret = unsafe { libc::madvise( - userspace_addr as *mut libc::c_void, + userspace_addr.cast(), memory_size as libc::size_t, libc::MADV_MERGEABLE, ) @@ -2400,7 +2400,7 @@ impl MemoryManager { // previously advised. let ret = unsafe { libc::madvise( - userspace_addr as *mut libc::c_void, + userspace_addr.cast(), memory_size as libc::size_t, libc::MADV_UNMERGEABLE, ) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index bd6c9bee4e..1c3baf8652 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -4069,7 +4069,7 @@ pub fn test_vm() { index as u32, region.start_addr().raw_value(), region.len().try_into().unwrap(), - region.as_ptr() as _, + region.as_ptr().cast(), false, false, ) From ae7113e1d43c669a12f0e566f5e8d220a64ae741 Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1468/1893] virtio-devices: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- virtio-devices/src/balloon.rs | 2 +- virtio-devices/src/block.rs | 9 +++------ virtio-devices/src/iommu.rs | 2 +- virtio-devices/src/mem.rs | 2 +- virtio-devices/src/vhost_user/blk.rs | 2 +- virtio-devices/src/vhost_user/vu_common_ctrl.rs | 2 +- virtio-devices/src/vsock/packet.rs | 2 +- 7 files changed, 9 insertions(+), 12 deletions(-) diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index 74b83dff21..5f9119fdf7 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -185,7 +185,7 @@ impl BalloonEpollHandler { let res = // SAFETY: FFI call with valid arguments, guaranteed by VolatileSlice unsafe { - libc::madvise(slice.ptr_guard_mut().as_ptr() as *mut libc::c_void, + libc::madvise(slice.ptr_guard_mut().as_ptr().cast(), range_len as libc::size_t, advice) }; if res != 0 { return Err(Error::MadviseFail(io::Error::last_os_error())); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index d37db6cd85..60ffade93e 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -605,13 +605,10 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese // Schedule the thread to run on the expected CPU set if let Some(cpuset) = cpuset.as_ref() { + let cpuset: *const libc::cpu_set_t = cpuset; // SAFETY: FFI call with correct arguments let ret = unsafe { - libc::sched_setaffinity( - 0, - std::mem::size_of::(), - cpuset as *const libc::cpu_set_t, - ) + libc::sched_setaffinity(0, std::mem::size_of::(), cpuset) }; if ret != 0 { @@ -1070,7 +1067,7 @@ impl VirtioDevice for Block { fn write_config(&mut self, offset: u64, data: &[u8]) { // The "writeback" field is the only mutable field let writeback_offset = - (&self.config.writeback as *const _ as u64) - (&self.config as *const _ as u64); + (&raw const self.config.writeback as u64) - (&raw const self.config as u64); if offset != writeback_offset || data.len() != std::mem::size_of_val(&self.config.writeback) { error!( diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index c99de399aa..baa2a24214 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -1081,7 +1081,7 @@ impl VirtioDevice for Iommu { fn write_config(&mut self, offset: u64, data: &[u8]) { // The "bypass" field is the only mutable field let bypass_offset = - (&self.config.bypass as *const _ as u64) - (&self.config as *const _ as u64); + (&raw const self.config.bypass as u64) - (&raw const self.config as u64); if offset != bypass_offset || data.len() != std::mem::size_of_val(&self.config.bypass) { error!( "Attempt to write to read-only field: offset {:x} length {}", diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index afc168a6ed..e03252d1b2 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -480,7 +480,7 @@ impl MemEpollHandler { // alone is not past the end. let res = unsafe { libc::madvise( - self.region.as_ptr().offset(offset as isize) as *mut libc::c_void, + self.region.as_ptr().offset(offset as isize).cast(), size as libc::size_t, libc::MADV_DONTNEED, ) diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index 5b64bbb5ab..cddffc5d62 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -233,7 +233,7 @@ impl VirtioDevice for Blk { fn write_config(&mut self, offset: u64, data: &[u8]) { // The "writeback" field is the only mutable field let writeback_offset = - (&self.config.writeback as *const _ as u64) - (&self.config as *const _ as u64); + (&raw const self.config.writeback as u64) - (&raw const self.config as u64); if offset != writeback_offset || data.len() != std::mem::size_of_val(&self.config.writeback) { error!( diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 5ad9425c5a..ca19ffea27 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -687,7 +687,7 @@ impl VhostUserHandle { // SAFETY: region is of size len let bitmap: &[u64] = unsafe { // Cast the pointer to u64 - let ptr = region.as_ptr() as *const u64; + let ptr = region.as_ptr().cast(); std::slice::from_raw_parts(ptr, len) }; Ok(MemoryRangeTable::from_dirty_bitmap( diff --git a/virtio-devices/src/vsock/packet.rs b/virtio-devices/src/vsock/packet.rs index e9834b5f36..656b388057 100644 --- a/virtio-devices/src/vsock/packet.rs +++ b/virtio-devices/src/vsock/packet.rs @@ -396,7 +396,7 @@ impl VsockPacket { PacketBuffer::Borrowed { ptr, len } => { // SAFETY: bound checks have already been performed when creating the packet // from the virtq descriptor. - Some(unsafe { std::slice::from_raw_parts(*ptr as *const u8, *len) }) + Some(unsafe { std::slice::from_raw_parts((*ptr).cast(), *len) }) } } } From 6de472f1bba98ab544bd23abee47b390beed1c0e Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1469/1893] pci: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- pci/src/mmap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pci/src/mmap.rs b/pci/src/mmap.rs index 79d3ab20ba..1a239f3a91 100644 --- a/pci/src/mmap.rs +++ b/pci/src/mmap.rs @@ -26,7 +26,7 @@ pub struct MmapRegion { impl Drop for MmapRegion { fn drop(&mut self) { // SAFETY: guaranteed by type validity invariant - unsafe { assert_eq!(libc::munmap(self.addr as *mut _, self.len), 0) } + unsafe { assert_eq!(libc::munmap(self.addr.cast(), self.len), 0) } } } // SAFETY: the caller is responsible for avoiding data races @@ -82,7 +82,7 @@ in both isize and libc::size_t"; if addr == libc::MAP_FAILED { Err(Error::last_os_error()) } else { - let addr = addr as _; + let addr = addr.cast(); Ok(Self { addr, len }) } } From 2a6b746f5ede747426c306105adae547a11363c0 Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1470/1893] net_util: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- net_util/src/queue_pair.rs | 4 ++-- net_util/src/tap.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index ee4d4d61cf..33b49a0c60 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -81,7 +81,7 @@ impl TxVirtio { assert!(buf.len() >= desc.len() as usize); let buf = buf.ptr_guard_mut(); let iovec = libc::iovec { - iov_base: buf.as_ptr() as *mut libc::c_void, + iov_base: buf.as_ptr().cast(), iov_len: desc.len() as libc::size_t, }; iovecs.push(iovec); @@ -238,7 +238,7 @@ impl RxVirtio { assert!(buf.len() >= desc.len() as usize); let buf = buf.ptr_guard_mut(); let iovec = libc::iovec { - iov_base: buf.as_ptr() as *mut libc::c_void, + iov_base: buf.as_ptr().cast(), iov_len: desc.len() as libc::size_t, }; iovecs.push(iovec); diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 43b42a4a2e..012c5b9442 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -169,7 +169,7 @@ impl Tap { // Open calls are safe because we give a constant null-terminated // string and verify the result. libc::open( - c"/dev/net/tun".as_ptr() as *const c_char, + c"/dev/net/tun".as_ptr().cast(), flags.unwrap_or(libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC), ) }; From 9b0d4b20ea8c2dc94044d813b73d796892711fad Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1471/1893] hypervisor: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- hypervisor/src/kvm/aarch64/gic/dist_regs.rs | 6 +++--- hypervisor/src/kvm/aarch64/gic/icc_regs.rs | 4 ++-- hypervisor/src/kvm/aarch64/gic/mod.rs | 12 ++++++------ hypervisor/src/kvm/aarch64/gic/redist_regs.rs | 4 ++-- hypervisor/src/kvm/mod.rs | 18 ++++++++---------- hypervisor/src/kvm/riscv64/aia.rs | 12 ++++++------ hypervisor/src/kvm/x86_64/sev.rs | 8 ++++---- hypervisor/src/mshv/mod.rs | 2 +- 8 files changed, 32 insertions(+), 34 deletions(-) diff --git a/hypervisor/src/kvm/aarch64/gic/dist_regs.rs b/hypervisor/src/kvm/aarch64/gic/dist_regs.rs index 9a3c719e7c..bfec2f8bfa 100644 --- a/hypervisor/src/kvm/aarch64/gic/dist_regs.rs +++ b/hypervisor/src/kvm/aarch64/gic/dist_regs.rs @@ -82,7 +82,7 @@ fn dist_attr_set(gic: &DeviceFd, offset: u32, val: u32) -> Result<()> { let gic_dist_attr = kvm_device_attr { group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS, attr: offset as u64, - addr: &val as *const u32 as u64, + addr: &raw const val as u64, flags: 0, }; @@ -99,7 +99,7 @@ fn dist_attr_get(gic: &DeviceFd, offset: u32) -> Result { let mut gic_dist_attr = kvm_device_attr { group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS, attr: offset as u64, - addr: &mut val as *mut u32 as u64, + addr: &raw mut val as u64, flags: 0, }; @@ -127,7 +127,7 @@ fn get_interrupts_num(gic: &DeviceFd) -> Result { let mut nr_irqs_attr = kvm_device_attr { group: KVM_DEV_ARM_VGIC_GRP_NR_IRQS, attr: 0, - addr: &mut num_irq as *mut u32 as u64, + addr: &raw mut num_irq as u64, flags: 0, }; // SAFETY: nr_irqs_attr.addr is safe to write to. diff --git a/hypervisor/src/kvm/aarch64/gic/icc_regs.rs b/hypervisor/src/kvm/aarch64/gic/icc_regs.rs index b084c89899..681754dc40 100644 --- a/hypervisor/src/kvm/aarch64/gic/icc_regs.rs +++ b/hypervisor/src/kvm/aarch64/gic/icc_regs.rs @@ -85,7 +85,7 @@ fn icc_attr_set(gic: &DeviceFd, offset: u64, typer: u64, val: u32) -> Result<()> let gic_icc_attr = kvm_device_attr { group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr - addr: &val as *const u32 as u64, + addr: &raw const val as u64, flags: 0, }; @@ -102,7 +102,7 @@ fn icc_attr_get(gic: &DeviceFd, offset: u64, typer: u64) -> Result { let mut gic_icc_attr = kvm_device_attr { group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr - addr: &mut val as *mut u32 as u64, + addr: &raw mut val as u64, flags: 0, }; diff --git a/hypervisor/src/kvm/aarch64/gic/mod.rs b/hypervisor/src/kvm/aarch64/gic/mod.rs index 8dcf060781..1e25a69e7d 100644 --- a/hypervisor/src/kvm/aarch64/gic/mod.rs +++ b/hypervisor/src/kvm/aarch64/gic/mod.rs @@ -30,7 +30,7 @@ fn gicv3_its_attr_set(its_device: &DeviceFd, group: u32, attr: u32, val: u64) -> let gicv3_its_attr = kvm_bindings::kvm_device_attr { group, attr: attr as u64, - addr: &val as *const u64 as u64, + addr: &raw const val as u64, flags: 0, }; @@ -45,7 +45,7 @@ fn gicv3_its_attr_get(its_device: &DeviceFd, group: u32, attr: u32) -> Result Result< let gic_redist_attr = kvm_device_attr { group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr - addr: &val as *const u32 as u64, + addr: &raw const val as u64, flags: 0, }; @@ -117,7 +117,7 @@ fn redist_attr_get(gic: &DeviceFd, offset: u32, typer: u64) -> Result { let mut gic_redist_attr = kvm_device_attr { group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr - addr: &mut val as *mut u32 as u64, + addr: &raw mut val as u64, flags: 0, }; diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index c127a15496..24e3cdbd75 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -121,7 +121,7 @@ use kvm_bindings::{ #[cfg(target_arch = "riscv64")] use kvm_bindings::{KVM_REG_RISCV_CORE, kvm_riscv_core}; #[cfg(feature = "tdx")] -use kvm_bindings::{KVM_X86_SW_PROTECTED_VM, KVMIO, kvm_run__bindgen_ty_1}; +use kvm_bindings::{KVM_X86_SW_PROTECTED_VM, KVMIO}; #[cfg(target_arch = "x86_64")] use kvm_bindings::{Xsave as xsave2, kvm_xsave2}; pub use kvm_ioctls::{self, Cap, Kvm, VcpuExit}; @@ -1331,7 +1331,7 @@ impl vm::Vm for KvmVm { &self.fd.as_raw_fd(), TdxCommand::InitVm, 0, - &data as *const _ as *const _, + (&raw const data).cast(), ) .map_err(vm::HypervisorVmError::InitializeTdx) } @@ -1379,7 +1379,7 @@ impl vm::Vm for KvmVm { &self.fd.as_raw_fd(), TdxCommand::InitMemRegion, u32::from(measure), - &data as *const _ as *const _, + (&raw const data).cast(), ) .map_err(vm::HypervisorVmError::InitMemRegionTdx) } @@ -1417,7 +1417,7 @@ fn tdx_command( ioctl_with_val( fd, KVM_MEMORY_ENCRYPT_OP(), - &cmd as *const TdxIoctlCmd as std::os::raw::c_ulong, + &raw const cmd as std::os::raw::c_ulong, ) }; @@ -1677,7 +1677,7 @@ impl hypervisor::Hypervisor for KvmHypervisor { &self.kvm.as_raw_fd(), TdxCommand::Capabilities, 0, - &data as *const _ as *const _, + (&raw const data).cast(), ) .map_err(|e| hypervisor::HypervisorError::TdxCapabilities(e.into()))?; @@ -3155,8 +3155,7 @@ impl cpu::Vcpu for KvmVcpu { let kvm_run = self.fd.get_kvm_run(); // SAFETY: accessing a union field in a valid structure let tdx_vmcall = unsafe { - &mut (*((&mut kvm_run.__bindgen_anon_1) as *mut kvm_run__bindgen_ty_1 - as *mut KvmTdxExit)) + &mut (*((&raw mut kvm_run.__bindgen_anon_1).cast::())) .u .vmcall }; @@ -3184,8 +3183,7 @@ impl cpu::Vcpu for KvmVcpu { let kvm_run = self.fd.get_kvm_run(); // SAFETY: accessing a union field in a valid structure let tdx_vmcall = unsafe { - &mut (*((&mut kvm_run.__bindgen_anon_1) as *mut kvm_run__bindgen_ty_1 - as *mut KvmTdxExit)) + &mut (*((&raw mut kvm_run.__bindgen_anon_1).cast::())) .u .vmcall }; @@ -3243,7 +3241,7 @@ impl cpu::Vcpu for KvmVcpu { let cpu_attr_irq = kvm_bindings::kvm_device_attr { group: kvm_bindings::KVM_ARM_VCPU_PMU_V3_CTRL, attr: u64::from(kvm_bindings::KVM_ARM_VCPU_PMU_V3_IRQ), - addr: &irq as *const u32 as u64, + addr: &raw const irq as u64, flags: 0, }; self.fd diff --git a/hypervisor/src/kvm/riscv64/aia.rs b/hypervisor/src/kvm/riscv64/aia.rs index 67857c6ae9..0915119af7 100644 --- a/hypervisor/src/kvm/riscv64/aia.rs +++ b/hypervisor/src/kvm/riscv64/aia.rs @@ -45,7 +45,7 @@ impl KvmAiaImsics { &self.device, kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_MODE), - &mut aia_mode as *mut u32 as u64, + &raw mut aia_mode as u64, 0, )?; @@ -56,7 +56,7 @@ impl KvmAiaImsics { &self.device, kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_SRCS), - &nr_irqs as *const u32 as u64, + &raw const nr_irqs as u64, 0, )?; @@ -66,7 +66,7 @@ impl KvmAiaImsics { &self.device, kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_IDS), - &mut aia_nr_ids as *mut u32 as u64, + &raw mut aia_nr_ids as u64, 0, )?; @@ -79,7 +79,7 @@ impl KvmAiaImsics { &self.device, kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_HART_BITS), - &hart_bits as *const u32 as u64, + &raw const hart_bits as u64, 0, )?; @@ -90,7 +90,7 @@ impl KvmAiaImsics { &self.device, kvm_bindings::KVM_DEV_RISCV_AIA_GRP_ADDR, u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_ADDR_APLIC), - &self.aplic_addr as *const u64 as u64, + &raw const self.aplic_addr as u64, 0, )?; @@ -107,7 +107,7 @@ impl KvmAiaImsics { &self.device, kvm_bindings::KVM_DEV_RISCV_AIA_GRP_ADDR, riscv_imsic_attr_of(cpu_index), - &cpu_imsic_addr as *const u64 as u64, + &raw const cpu_imsic_addr as u64, 0, )?; } diff --git a/hypervisor/src/kvm/x86_64/sev.rs b/hypervisor/src/kvm/x86_64/sev.rs index 390a0ca84f..184690f99f 100644 --- a/hypervisor/src/kvm/x86_64/sev.rs +++ b/hypervisor/src/kvm/x86_64/sev.rs @@ -207,7 +207,7 @@ impl SevFd { }; let mut sev_cmd = kvm_sev_cmd { id: KVM_SEV_INIT2, - data: &mut init as *mut KvmSevInit as _, + data: &raw mut init as u64, sev_fd: self.fd.as_raw_fd() as _, ..Default::default() }; @@ -221,7 +221,7 @@ impl SevFd { }; let mut sev_cmd = kvm_sev_cmd { id: KVM_SEV_SNP_LAUNCH_START, - data: &mut start as *mut KvmSevSnpLaunchStart as _, + data: &raw mut start as u64, sev_fd: self.fd.as_raw_fd() as _, ..Default::default() }; @@ -247,7 +247,7 @@ impl SevFd { }; let mut sev_cmd = kvm_sev_cmd { id: KVM_SEV_SNP_LAUNCH_UPDATE, - data: &mut update as *mut KvmSevSnpLaunchUpdate as _, + data: &raw mut update as u64, sev_fd: self.fd.as_raw_fd() as _, ..Default::default() }; @@ -276,7 +276,7 @@ impl SevFd { }; let mut sev_cmd = kvm_sev_cmd { id: KVM_SEV_SNP_LAUNCH_FINISH, - data: &mut finish as *mut KvmSevSnpLaunchFinish as _, + data: &raw mut finish as u64, sev_fd: self.fd.as_raw_fd() as _, ..Default::default() }; diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 9620454564..d38aff9860 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -1961,7 +1961,7 @@ impl vm::Vm for MshvVm { // Return error return Err(vm::HypervisorVmError::MmapToRoot); } - Some(Ghcb(addr as *mut svm_ghcb_base)) + Some(Ghcb(addr.cast())) } else { None }; From 7455ff1ea40d0fe796942fb11876b01706f3b078 Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1472/1893] devices: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- devices/src/legacy/cmos.rs | 4 ++-- devices/src/pvmemcontrol.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devices/src/legacy/cmos.rs b/devices/src/legacy/cmos.rs index 7b23d616dc..238f2184d6 100644 --- a/devices/src/legacy/cmos.rs +++ b/devices/src/legacy/cmos.rs @@ -122,13 +122,13 @@ impl BusDevice for Cmos { // the tm and timespec struct because it contains only plain data. let update_in_progress = unsafe { let mut timespec: timespec = mem::zeroed(); - clock_gettime(CLOCK_REALTIME, &mut timespec as *mut _); + clock_gettime(CLOCK_REALTIME, &raw mut timespec); // https://github.com/rust-lang/libc/issues/1848 #[cfg_attr(target_env = "musl", allow(deprecated))] let now: time_t = timespec.tv_sec; let mut tm: tm = mem::zeroed(); - gmtime_r(&now, &mut tm as *mut _); + gmtime_r(&now, &raw mut tm); // The following lines of code are safe but depend on tm being in scope. seconds = tm.tm_sec; diff --git a/devices/src/pvmemcontrol.rs b/devices/src/pvmemcontrol.rs index 171fdf1544..50e4cd16ba 100644 --- a/devices/src/pvmemcontrol.rs +++ b/devices/src/pvmemcontrol.rs @@ -443,7 +443,7 @@ impl PvmemcontrolBusDevice { ))); }; assert!(slice.len() >= range_len); - let res = f(slice.ptr_guard_mut().as_ptr() as _, slice.len()); + let res = f(slice.ptr_guard_mut().as_ptr().cast(), slice.len()); if res != 0 { return Err(Error::LibcFail(io::Error::last_os_error())); } From 2b6e9df4e369cbc4c19d63592a26ff5323227cff Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1473/1893] block: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- block/src/lib.rs | 5 ++--- block/src/qcow/raw_file.rs | 16 ++++------------ block/src/qcow_async.rs | 16 ++++++++-------- block/src/qcow_common.rs | 10 +++++----- block/src/qcow_sync.rs | 16 ++++++++-------- block/src/request.rs | 6 +++--- block/src/vhdx/vhdx_header.rs | 11 +++++------ block/src/vhdx/vhdx_metadata.rs | 4 ++-- 8 files changed, 37 insertions(+), 47 deletions(-) diff --git a/block/src/lib.rs b/block/src/lib.rs index a58c445ef9..93a9f13280 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -931,8 +931,7 @@ mod unit_tests { unsafe { ptr::write_bytes(buf, 0xAB, alignment) }; // SAFETY: buf is aligned and sized for O_DIRECT; fd is valid. - let written = - unsafe { libc::pwrite(f.as_raw_fd(), buf as *const libc::c_void, alignment, 0) }; + let written = unsafe { libc::pwrite(f.as_raw_fd(), buf.cast(), alignment, 0) }; assert_eq!( written as usize, alignment, @@ -943,7 +942,7 @@ mod unit_tests { // SAFETY: buf is valid for `alignment` bytes. unsafe { ptr::write_bytes(buf, 0x00, alignment) }; // SAFETY: buf is aligned and sized for O_DIRECT; fd is valid. - let read = unsafe { libc::pread(f.as_raw_fd(), buf as *mut libc::c_void, alignment, 0) }; + let read = unsafe { libc::pread(f.as_raw_fd(), buf.cast(), alignment, 0) }; assert_eq!( read as usize, alignment, diff --git a/block/src/qcow/raw_file.rs b/block/src/qcow/raw_file.rs index c2a01811f8..fa33478bf9 100644 --- a/block/src/qcow/raw_file.rs +++ b/block/src/qcow/raw_file.rs @@ -15,7 +15,6 @@ use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::io::{AsRawFd, RawFd}; use std::slice; -use libc::c_void; use vmm_sys_util::file_traits::FileSync; use vmm_sys_util::seek_hole::SeekHole; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; @@ -39,14 +38,7 @@ fn is_valid_alignment(fd: RawFd, alignment: usize) -> bool { assert!(!ptr.is_null()); // SAFETY: FFI call - let ret = unsafe { - ::libc::pread( - fd, - ptr as *mut c_void, - alignment, - alignment.try_into().unwrap(), - ) - }; + let ret = unsafe { ::libc::pread(fd, ptr.cast(), alignment, alignment.try_into().unwrap()) }; // SAFETY: ptr was allocated by alloc_zeroed with layout unsafe { dealloc(ptr, layout) }; @@ -187,7 +179,7 @@ impl Read for RawFile { let ret = unsafe { ::libc::pread64( self.file.as_raw_fd(), - tmp_buf.as_mut_ptr() as *mut c_void, + tmp_buf.as_mut_ptr().cast(), tmp_buf.len(), rounded_pos.try_into().unwrap(), ) @@ -267,7 +259,7 @@ impl Write for RawFile { let ret = unsafe { ::libc::pread64( self.file.as_raw_fd(), - tmp_buf.as_mut_ptr() as *mut c_void, + tmp_buf.as_mut_ptr().cast(), tmp_buf.len(), rounded_pos.try_into().unwrap(), ) @@ -286,7 +278,7 @@ impl Write for RawFile { let ret = unsafe { ::libc::pwrite64( self.file.as_raw_fd(), - tmp_buf.as_ptr() as *const c_void, + tmp_buf.as_ptr().cast(), tmp_buf.len(), rounded_pos.try_into().unwrap(), ) diff --git a/block/src/qcow_async.rs b/block/src/qcow_async.rs index bf435105f1..870095aa66 100644 --- a/block/src/qcow_async.rs +++ b/block/src/qcow_async.rs @@ -597,7 +597,7 @@ mod unit_tests { let mut val = 0u64; // SAFETY: reading 8 bytes from a valid eventfd. unsafe { - libc::read(fd, &mut val as *mut u64 as *mut libc::c_void, 8); + libc::read(fd, (&raw mut val).cast(), 8); } } } @@ -605,7 +605,7 @@ mod unit_tests { fn async_write(disk: &QcowDisk, offset: u64, data: &[u8]) { let mut async_io = disk.create_async_io(1).unwrap(); let iovec = libc::iovec { - iov_base: data.as_ptr() as *mut libc::c_void, + iov_base: data.as_ptr().cast::().cast_mut(), iov_len: data.len(), }; async_io @@ -624,7 +624,7 @@ mod unit_tests { let mut async_io = disk.create_async_io(1).unwrap(); let mut buf = vec![0xFFu8; len]; let iovec = libc::iovec { - iov_base: buf.as_mut_ptr() as *mut libc::c_void, + iov_base: buf.as_mut_ptr().cast(), iov_len: buf.len(), }; async_io @@ -755,11 +755,11 @@ mod unit_tests { let offset_b: u64 = 65536; let iov_a = libc::iovec { - iov_base: write_a.as_ptr() as *mut libc::c_void, + iov_base: write_a.as_ptr().cast::().cast_mut(), iov_len: write_a.len(), }; let iov_b = libc::iovec { - iov_base: write_b.as_ptr() as *mut libc::c_void, + iov_base: write_b.as_ptr().cast::().cast_mut(), iov_len: write_b.len(), }; @@ -793,11 +793,11 @@ mod unit_tests { let mut read_a = vec![0u8; 4096]; let mut read_b = vec![0u8; 4096]; let riov_a = libc::iovec { - iov_base: read_a.as_mut_ptr() as *mut libc::c_void, + iov_base: read_a.as_mut_ptr().cast(), iov_len: read_a.len(), }; let riov_b = libc::iovec { - iov_base: read_b.as_mut_ptr() as *mut libc::c_void, + iov_base: read_b.as_mut_ptr().cast(), iov_len: read_b.len(), }; @@ -1068,7 +1068,7 @@ mod unit_tests { let mut async_io = disk.create_async_io(1).unwrap(); let mut buf = vec![0xFFu8; cluster_size]; let iovec = libc::iovec { - iov_base: buf.as_mut_ptr() as *mut libc::c_void, + iov_base: buf.as_mut_ptr().cast(), iov_len: buf.len(), }; async_io.read_vectored(0, &[iovec], 1).unwrap(); diff --git a/block/src/qcow_common.rs b/block/src/qcow_common.rs index 9b8354d776..49cae1f79a 100644 --- a/block/src/qcow_common.rs +++ b/block/src/qcow_common.rs @@ -30,7 +30,7 @@ pub fn pread_exact(fd: RawFd, buf: &mut [u8], offset: u64) -> io::Result<()> { let ret = unsafe { libc::pread64( fd, - buf[total..].as_mut_ptr() as *mut libc::c_void, + buf[total..].as_mut_ptr().cast(), buf.len() - total, (offset + total as u64) as libc::off_t, ) @@ -81,7 +81,7 @@ pub fn pwrite_all(fd: RawFd, buf: &[u8], offset: u64) -> io::Result<()> { let ret = unsafe { libc::pwrite64( fd, - buf[total..].as_ptr() as *const libc::c_void, + buf[total..].as_ptr().cast(), buf.len() - total, (offset + total as u64) as libc::off_t, ) @@ -211,7 +211,7 @@ pub unsafe fn scatter_to_iovecs(iovecs: &[libc::iovec], start: usize, data: &[u8 let count = min(available, remaining.len()); // SAFETY: iov_base is valid for iov_len bytes per caller contract. unsafe { - let dst = (iov.iov_base as *mut u8).add(iov_start); + let dst = iov.iov_base.cast::().add(iov_start); ptr::copy_nonoverlapping(remaining.as_ptr(), dst, count); } remaining = &remaining[count..]; @@ -240,7 +240,7 @@ pub unsafe fn zero_fill_iovecs(iovecs: &[libc::iovec], start: usize, len: usize) let count = min(available, remaining); // SAFETY: iov_base is valid for iov_len bytes per caller contract. unsafe { - let dst = (iov.iov_base as *mut u8).add(iov_start); + let dst = iov.iov_base.cast::().add(iov_start); ptr::write_bytes(dst, 0, count); } remaining -= count; @@ -270,7 +270,7 @@ pub unsafe fn gather_from_iovecs_into(iovecs: &[libc::iovec], start: usize, dst: let count = min(available, len - written); // SAFETY: iov_base is valid for iov_len bytes per caller contract. unsafe { - let src = (iov.iov_base as *const u8).add(iov_start); + let src = iov.iov_base.cast::().add(iov_start); ptr::copy_nonoverlapping(src, dst.as_mut_ptr().add(written), count); } written += count; diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 9e183dee14..6a81c4ff99 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -353,7 +353,7 @@ mod unit_tests { let mut async_io = disk.create_async_io(1).unwrap(); let mut buf = vec![0xFFu8; len]; let iovec = libc::iovec { - iov_base: buf.as_mut_ptr() as *mut libc::c_void, + iov_base: buf.as_mut_ptr().cast(), iov_len: buf.len(), }; async_io @@ -368,7 +368,7 @@ mod unit_tests { fn async_write(disk: &QcowDisk, offset: u64, data: &[u8]) { let mut async_io = disk.create_async_io(1).unwrap(); let iovec = libc::iovec { - iov_base: data.as_ptr() as *mut libc::c_void, + iov_base: data.as_ptr().cast::().cast_mut(), iov_len: data.len(), }; async_io @@ -1550,15 +1550,15 @@ mod unit_tests { let c = vec![0xCCu8; 16 * 1024]; let iovecs_w = [ libc::iovec { - iov_base: a.as_ptr() as *mut libc::c_void, + iov_base: a.as_ptr().cast::().cast_mut(), iov_len: a.len(), }, libc::iovec { - iov_base: b.as_ptr() as *mut libc::c_void, + iov_base: b.as_ptr().cast::().cast_mut(), iov_len: b.len(), }, libc::iovec { - iov_base: c.as_ptr() as *mut libc::c_void, + iov_base: c.as_ptr().cast::().cast_mut(), iov_len: c.len(), }, ]; @@ -1578,15 +1578,15 @@ mod unit_tests { let mut r3 = vec![0u8; 8 * 1024]; let iovecs_r = [ libc::iovec { - iov_base: r1.as_mut_ptr() as *mut libc::c_void, + iov_base: r1.as_mut_ptr().cast(), iov_len: r1.len(), }, libc::iovec { - iov_base: r2.as_mut_ptr() as *mut libc::c_void, + iov_base: r2.as_mut_ptr().cast(), iov_len: r2.len(), }, libc::iovec { - iov_base: r3.as_mut_ptr() as *mut libc::c_void, + iov_base: r3.as_mut_ptr().cast(), iov_len: r3.len(), }, ]; diff --git a/block/src/request.rs b/block/src/request.rs index 721bc8e29d..49ce3abbb9 100644 --- a/block/src/request.rs +++ b/block/src/request.rs @@ -291,7 +291,7 @@ impl Request { .get_slice(data_addr, data_len) .map_err(ExecuteError::GetHostAddress)?; assert!(origin_ptr.len() >= data_len); - let origin_ptr = origin_ptr.ptr_guard(); + let origin_ptr = origin_ptr.ptr_guard_mut(); // O_DIRECT requires buffer addresses to be aligned to the // backend device's logical block size. In case it's not properly @@ -299,7 +299,7 @@ impl Request { // alignment, and a copy from/to the origin buffer is performed, // depending on the type of operation. let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(alignment) { - origin_ptr.as_ptr() as *mut libc::c_void + origin_ptr.as_ptr().cast() } else { let layout = Layout::from_size_align(data_len, alignment as usize).unwrap(); // SAFETY: layout has non-zero size @@ -327,7 +327,7 @@ impl Request { layout, }); - aligned_ptr as *mut libc::c_void + aligned_ptr.cast() }; let iovec = libc::iovec { diff --git a/block/src/vhdx/vhdx_header.rs b/block/src/vhdx/vhdx_header.rs index 73ecbc8f84..5c8c3e37d8 100644 --- a/block/src/vhdx/vhdx_header.rs +++ b/block/src/vhdx/vhdx_header.rs @@ -135,7 +135,7 @@ impl Header { .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 = unsafe { *(buffer.as_ptr().cast()) }; if header.signature != HEADER_SIGN { return Err(VhdxHeaderError::InvalidHeaderSign); } @@ -151,9 +151,8 @@ impl 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) - }; + let reference = + unsafe { std::slice::from_raw_parts((&raw const *self).cast(), HEADER_SIZE as usize) }; *buffer = reference.try_into().unwrap(); } @@ -222,7 +221,7 @@ impl RegionTableHeader { .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 = unsafe { *(buffer.as_ptr().cast()) }; if region_table_header.signature != REGION_SIGN { return Err(VhdxHeaderError::InvalidRegionSign); } @@ -340,7 +339,7 @@ impl RegionTableEntry { pub fn new(buffer: &[u8]) -> Result { assert!(buffer.len() == std::mem::size_of::()); // SAFETY: the assertion above makes sure the buffer size is correct. - let mut region_table_entry = unsafe { *(buffer.as_ptr() as *mut RegionTableEntry) }; + let mut region_table_entry: RegionTableEntry = unsafe { *(buffer.as_ptr().cast()) }; let uuid = crate::vhdx::uuid_from_guid(buffer); region_table_entry.guid = uuid; diff --git a/block/src/vhdx/vhdx_metadata.rs b/block/src/vhdx/vhdx_metadata.rs index 47cc2ff68d..0410d9af93 100644 --- a/block/src/vhdx/vhdx_metadata.rs +++ b/block/src/vhdx/vhdx_metadata.rs @@ -280,7 +280,7 @@ impl MetadataTableHeader { pub fn new(buffer: &[u8]) -> Result { assert!(buffer.len() == std::mem::size_of::()); // SAFETY: the assertion above makes sure the buffer size is correct. - let metadata_table_header = unsafe { *(buffer.as_ptr() as *mut MetadataTableHeader) }; + let metadata_table_header: MetadataTableHeader = unsafe { *(buffer.as_ptr().cast()) }; if metadata_table_header.signature != METADATA_SIGN { return Err(VhdxMetadataError::InvalidMetadataSign); @@ -313,7 +313,7 @@ impl MetadataTableEntry { fn new(buffer: &[u8]) -> Result { assert!(buffer.len() == std::mem::size_of::()); // SAFETY: the assertion above makes sure the buffer size is correct. - let mut metadata_table_entry = unsafe { *(buffer.as_ptr() as *mut MetadataTableEntry) }; + let mut metadata_table_entry: MetadataTableEntry = unsafe { *(buffer.as_ptr().cast()) }; let uuid = crate::vhdx::uuid_from_guid(buffer); metadata_table_entry.item_id = uuid; From 5f360abdc795ad7467699cc7dddd5b3a5558dcdf Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1474/1893] arch: replace `as ` casts with safer alternatives `as` casts can change mutability, which quickly leads to undefined behavior. Signed-off-by: Julian Schindel --- arch/src/x86_64/mptable.rs | 3 ++- arch/src/x86_64/smbios.rs | 3 ++- arch/src/x86_64/tdx/mod.rs | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/arch/src/x86_64/mptable.rs b/arch/src/x86_64/mptable.rs index c31c9e4117..203d55fa27 100644 --- a/arch/src/x86_64/mptable.rs +++ b/arch/src/x86_64/mptable.rs @@ -101,8 +101,9 @@ const CPU_FEATURE_APIC: u32 = 0x200; const CPU_FEATURE_FPU: u32 = 0x001; fn compute_checksum(v: &T) -> u8 { + let v: *const T = v; // 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::()) }; + let v_slice = unsafe { slice::from_raw_parts(v.cast(), mem::size_of::()) }; let mut checksum: u8 = 0; for i in v_slice.iter() { checksum = checksum.wrapping_add(*i); diff --git a/arch/src/x86_64/smbios.rs b/arch/src/x86_64/smbios.rs index b9db0fcb96..6f1139888b 100644 --- a/arch/src/x86_64/smbios.rs +++ b/arch/src/x86_64/smbios.rs @@ -49,8 +49,9 @@ const PCI_SUPPORTED: u64 = 1 << 7; const IS_VIRTUAL_MACHINE: u8 = 1 << 4; fn compute_checksum(v: &T) -> u8 { + let v: *const T = v; // 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::()) }; + let v_slice = unsafe { slice::from_raw_parts(v.cast(), mem::size_of::()) }; let mut checksum: u8 = 0; for i in v_slice.iter() { checksum = checksum.wrapping_add(*i); diff --git a/arch/src/x86_64/tdx/mod.rs b/arch/src/x86_64/tdx/mod.rs index a6ce96aae9..53e004c5ba 100644 --- a/arch/src/x86_64/tdx/mod.rs +++ b/arch/src/x86_64/tdx/mod.rs @@ -163,7 +163,7 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec, bool), // 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, + (&raw mut descriptor).cast(), std::mem::size_of::(), ) }) @@ -190,7 +190,7 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec, 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, + sections.as_mut_ptr().cast(), descriptor.num_sections as usize * std::mem::size_of::(), ) }) From 9a0fb1b06a235c915c1b44d574b1bac7e29889af Mon Sep 17 00:00:00 2001 From: Julian Schindel Date: Wed, 29 Apr 2026 21:55:06 +0200 Subject: [PATCH 1475/1893] 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 --- vmm/src/igvm/igvm_loader.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index 4113436a84..53f3588550 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -387,6 +387,11 @@ pub fn load_igvm( data_type, data, } => { + // With the "mshv" feature enabled, `data` is modified via pointer, so `data` needs + // to be mutable. + #[cfg(feature = "mshv")] + let mut data = data.clone(); + debug_assert!((data.len() as u64).is_multiple_of(HV_PAGE_SIZE)); // TODO: only 4k or empty page data supported right now @@ -428,8 +433,7 @@ pub fn load_igvm( if hypervisor_type == HypervisorType::Mshv { // SAFETY: CPUID is readonly unsafe { - let cpuid_page_p: *mut hv_psp_cpuid_page = - data.as_ptr() as *mut hv_psp_cpuid_page; // as *mut hv_psp_cpuid_page; + let cpuid_page_p = data.as_mut_ptr().cast(); let cpuid_page: &mut hv_psp_cpuid_page = &mut *cpuid_page_p; for i in 0..cpuid_page.count { let leaf = cpuid_page.cpuid_leaf_info[i as usize]; @@ -557,7 +561,7 @@ pub fn load_igvm( } if !imported_page { loader - .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, data) + .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, data.as_ref()) .map_err(Error::Loader)?; } } From 80c56f728e065e7ab59975a09dd98bace9f45836 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 1 May 2026 17:23:44 +0100 Subject: [PATCH 1476/1893] 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 --- vmm/src/config.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 3d37420b40..8e16cfc7e6 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -404,6 +404,9 @@ pub enum ValidationError { /// The supplied PCI ID is reserved #[error("Given PCI device ID ({0}) is reserved")] ReservedPciDeviceId(u8), + /// Invalid to set both 'mergeable' and 'shared' for memory + #[error("Invalid to set both 'mergeable' and 'shared' for memory")] + InvalidSharedMemoryWithMergeable, } type ValidationResult = std::result::Result; @@ -3016,6 +3019,18 @@ impl VmConfig { } } + if self.memory.shared && self.memory.mergeable { + return Err(ValidationError::InvalidSharedMemoryWithMergeable); + } + + if let Some(zones) = &self.memory.zones { + for zone in zones { + if zone.shared && zone.mergeable { + return Err(ValidationError::InvalidSharedMemoryWithMergeable); + } + } + } + if let Some(user_devices) = &self.user_devices { if !user_devices.is_empty() && !self.backed_by_shared_memory() { return Err(ValidationError::UserDevicesRequireSharedMemory); @@ -5225,6 +5240,29 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" Err(ValidationError::InvalidHugePageSize(3 << 20)) ); + // Test mergeable and shared validation for global memory + let mut invalid_config = valid_config.clone(); + invalid_config.memory.shared = true; + invalid_config.memory.mergeable = true; + assert_eq!( + invalid_config.validate(), + Err(ValidationError::InvalidSharedMemoryWithMergeable) + ); + + // Test mergeable and shared validation for memory zones + let mut invalid_config = valid_config.clone(); + invalid_config.memory.zones = Some(vec![MemoryZoneConfig { + id: "mem0".to_string(), + size: 1 << 30, + shared: true, + mergeable: true, + ..Default::default() + }]); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::InvalidSharedMemoryWithMergeable) + ); + let mut still_valid_config = valid_config.clone(); still_valid_config.platform = Some(platform_fixture()); still_valid_config.validate().unwrap(); From afeeeee49472e5b72d6b24e46f020d28681ec814 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 23 Apr 2026 12:21:22 -0700 Subject: [PATCH 1477/1893] 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 --- scripts/dev_cli.sh | 118 +++++++++++---------------------------------- 1 file changed, 28 insertions(+), 90 deletions(-) diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 21d6362805..5a0bfd951b 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -470,18 +470,23 @@ cmd_tests() { rustflags="$rustflags -C link-args=-Wl,-Bstatic -C link-args=-lc" fi + # Common base runtime args shared by all test container runs. + common_args=( + --name "$CLH_CTR_NAME" + --workdir "$CTR_CLH_ROOT_DIR" + --rm + --security-opt seccomp=unconfined + --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" + ${exported_volumes:+$exported_volumes} + ) + if [[ "$unit" = true ]]; then say "Running unit tests for $target..." run_container "$DOCKER_RUNTIME" run \ - --name "$CLH_CTR_NAME" \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ + "${common_args[@]}" \ --device $exported_device \ --device /dev/net/tun \ --cap-add net_admin \ - --security-opt seccomp=unconfined \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+$exported_volumes} \ --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ --env TARGET_CC="$target_cc" \ @@ -490,21 +495,20 @@ cmd_tests() { ./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $? fi + # Extend common_args with integration-specific runtime settings. + common_args+=( + --privileged + --ipc=host + --net="$CTR_CLH_NET" + "--mount" "type=tmpfs,destination=/tmp" + --volume /dev:/dev + --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" + ) + if [ "$integration" = true ]; then say "Running integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ - --name "$CLH_CTR_NAME" \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ - --privileged \ - --security-opt seccomp=unconfined \ - --ipc=host \ - --net="$CTR_CLH_NET" \ - --mount type=tmpfs,destination=/tmp \ - --volume /dev:/dev \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+$exported_volumes} \ - --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ + "${common_args[@]}" \ --env USER="root" \ --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ @@ -522,19 +526,8 @@ cmd_tests() { copy_igvm_files "$SRC_IGVM_FILES_PATH" "$DEST_IGVM_FILES_PATH" say "Running CVM integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ - --name "$CLH_CTR_NAME" \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ - --privileged \ - --security-opt seccomp=unconfined \ - --ipc=host \ - --net="$CTR_CLH_NET" \ - --mount type=tmpfs,destination=/tmp \ - --volume /dev:/dev \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ + "${common_args[@]}" \ --volume "$DEST_IGVM_FILES_PATH:$CTR_IGVM_FILES_PATH" \ - ${exported_volumes:+"$exported_volumes"} \ - --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ --env USER="root" \ --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ @@ -548,18 +541,7 @@ cmd_tests() { if [ "$integration_vfio" = true ]; then say "Running VFIO integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ - --name "$CLH_CTR_NAME" \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ - --privileged \ - --security-opt seccomp=unconfined \ - --ipc=host \ - --net="$CTR_CLH_NET" \ - --mount type=tmpfs,destination=/tmp \ - --volume /dev:/dev \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+$exported_volumes} \ - --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ + "${common_args[@]}" \ --env USER="root" \ --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ @@ -572,18 +554,7 @@ cmd_tests() { if [ "$integration_windows" = true ]; then say "Running Windows integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ - --name "$CLH_CTR_NAME" \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ - --privileged \ - --security-opt seccomp=unconfined \ - --ipc=host \ - --net="$CTR_CLH_NET" \ - --mount type=tmpfs,destination=/tmp \ - --volume /dev:/dev \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+$exported_volumes} \ - --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ + "${common_args[@]}" \ --env USER="root" \ --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ @@ -596,18 +567,7 @@ cmd_tests() { if [ "$integration_rate_limiter" = true ]; then say "Running 'rate limiter' integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ - --name "$CLH_CTR_NAME" \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ - --privileged \ - --security-opt seccomp=unconfined \ - --ipc=host \ - --net="$CTR_CLH_NET" \ - --mount type=tmpfs,destination=/tmp \ - --volume /dev:/dev \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+$exported_volumes} \ - --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ + "${common_args[@]}" \ --env USER="root" \ --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ @@ -620,18 +580,7 @@ cmd_tests() { if [ "$metrics" = true ]; then say "Generating performance metrics for $target..." run_container "$DOCKER_RUNTIME" run \ - --name "$CLH_CTR_NAME" \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ - --privileged \ - --security-opt seccomp=unconfined \ - --ipc=host \ - --net="$CTR_CLH_NET" \ - --mount type=tmpfs,destination=/tmp \ - --volume /dev:/dev \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+$exported_volumes} \ - --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ + "${common_args[@]}" \ --env USER="root" \ --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ @@ -645,18 +594,7 @@ cmd_tests() { if [ "$coverage" = true ]; then say "Generating code coverage information for $target..." run_container "$DOCKER_RUNTIME" run \ - --name "$CLH_CTR_NAME" \ - --workdir "$CTR_CLH_ROOT_DIR" \ - --rm \ - --privileged \ - --security-opt seccomp=unconfined \ - --ipc=host \ - --net="$CTR_CLH_NET" \ - --mount type=tmpfs,destination=/tmp \ - --volume /dev:/dev \ - --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ - ${exported_volumes:+$exported_volumes} \ - --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ + "${common_args[@]}" \ --env USER="root" \ --env BUILD_TARGET="$target" \ --env RUSTFLAGS="$rustflags" \ From 339d0a84f98de7d36a2ed5345ce173e4800507df Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 23 Apr 2026 12:22:15 -0700 Subject: [PATCH 1478/1893] 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 --- scripts/dev_cli.sh | 59 +++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 5a0bfd951b..d06a8ac604 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -480,6 +480,13 @@ cmd_tests() { ${exported_volumes:+$exported_volumes} ) + # Common base environment variables shared by all test container runs. + common_env_args=( + --env BUILD_TARGET="$target" + --env RUSTFLAGS="$rustflags" + --env TARGET_CC="$target_cc" + ) + if [[ "$unit" = true ]]; then say "Running unit tests for $target..." run_container "$DOCKER_RUNTIME" run \ @@ -487,9 +494,7 @@ cmd_tests() { --device $exported_device \ --device /dev/net/tun \ --cap-add net_admin \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ + "${common_env_args[@]}" \ --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ "$CTR_IMAGE" \ ./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $? @@ -505,16 +510,18 @@ cmd_tests() { --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" ) + # Extend common_env_args with integration-specific settings. + common_env_args+=( + --env USER="root" + --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" + ) + if [ "$integration" = true ]; then say "Running integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ "${common_args[@]}" \ - --env USER="root" \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ + "${common_env_args[@]}" \ --env PARALLEL_INTEGRATION_TESTS_NUM="${PARALLEL_INTEGRATION_TESTS_NUM:-}" \ - --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ --env MIGRATABLE_VERSION="$MIGRATABLE_VERSION" \ "$CTR_IMAGE" \ @@ -528,11 +535,7 @@ cmd_tests() { run_container "$DOCKER_RUNTIME" run \ "${common_args[@]}" \ --volume "$DEST_IGVM_FILES_PATH:$CTR_IGVM_FILES_PATH" \ - --env USER="root" \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ - --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ + "${common_env_args[@]}" \ --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ "$CTR_IMAGE" \ ./scripts/run_integration_tests_cvm.sh "$@" || fix_dir_perms $? || exit $? @@ -542,11 +545,7 @@ cmd_tests() { say "Running VFIO integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ "${common_args[@]}" \ - --env USER="root" \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ - --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ + "${common_env_args[@]}" \ "$CTR_IMAGE" \ ./scripts/run_integration_tests_vfio.sh "$@" || fix_dir_perms $? || exit $? fi @@ -555,11 +554,7 @@ cmd_tests() { say "Running Windows integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ "${common_args[@]}" \ - --env USER="root" \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ - --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ + "${common_env_args[@]}" \ "$CTR_IMAGE" \ ./scripts/run_integration_tests_windows_"$(uname -m)".sh "$@" || fix_dir_perms $? || exit $? fi @@ -568,11 +563,7 @@ cmd_tests() { say "Running 'rate limiter' integration tests for $target..." run_container "$DOCKER_RUNTIME" run \ "${common_args[@]}" \ - --env USER="root" \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ - --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ + "${common_env_args[@]}" \ "$CTR_IMAGE" \ ./scripts/run_integration_tests_rate_limiter.sh "$@" || fix_dir_perms $? || exit $? fi @@ -581,12 +572,8 @@ cmd_tests() { say "Generating performance metrics for $target..." run_container "$DOCKER_RUNTIME" run \ "${common_args[@]}" \ - --env USER="root" \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ + "${common_env_args[@]}" \ --env RUST_BACKTRACE="${RUST_BACKTRACE}" \ - --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ "$CTR_IMAGE" \ ./scripts/run_metrics.sh "$@" || fix_dir_perms $? || exit $? fi @@ -595,11 +582,7 @@ cmd_tests() { say "Generating code coverage information for $target..." run_container "$DOCKER_RUNTIME" run \ "${common_args[@]}" \ - --env USER="root" \ - --env BUILD_TARGET="$target" \ - --env RUSTFLAGS="$rustflags" \ - --env TARGET_CC="$target_cc" \ - --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ + "${common_env_args[@]}" \ "$CTR_IMAGE" \ dbus-run-session ./scripts/run_coverage.sh "$@" || fix_dir_perms $? || exit $? fi From 38bc97dd4dbdb41dde84e0807d1808deb2d4c879 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 23 Apr 2026 14:11:03 -0700 Subject: [PATCH 1479/1893] 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 --- scripts/dev_cli.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index d06a8ac604..5396e4b12c 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -500,6 +500,37 @@ cmd_tests() { ./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $? fi + # Copy custom kernel/firmware into the workloads directory on the + # host so they are visible inside the container at the default + # paths. Each variable is independent; only set vars are copied. + if [ -n "$CH_CUSTOM_KERNEL" ]; then + say "Copying custom kernel from $CH_CUSTOM_KERNEL" + if [ "$(uname -m)" = "aarch64" ]; then + cp "$CH_CUSTOM_KERNEL" "$CLH_INTEGRATION_WORKLOADS/Image-arm64" + else + cp "$CH_CUSTOM_KERNEL" "$CLH_INTEGRATION_WORKLOADS/vmlinux-x86_64" + fi + fi + if [ -n "$CH_CUSTOM_BZIMAGE" ]; then + say "Copying custom bzImage from $CH_CUSTOM_BZIMAGE" + if [ "$(uname -m)" = "x86_64" ]; then + cp "$CH_CUSTOM_BZIMAGE" "$CLH_INTEGRATION_WORKLOADS/bzImage-x86_64" + fi + fi + + if [ -n "$CH_CUSTOM_FIRMWARE" ]; then + say "Copying custom firmware from $CH_CUSTOM_FIRMWARE" + cp "$CH_CUSTOM_FIRMWARE" "$CLH_INTEGRATION_WORKLOADS/hypervisor-fw" + fi + if [ -n "$CH_CUSTOM_OVMF" ]; then + say "Copying custom OVMF from $CH_CUSTOM_OVMF" + if [ "$(uname -m)" = "aarch64" ]; then + cp "$CH_CUSTOM_OVMF" "$CLH_INTEGRATION_WORKLOADS/CLOUDHV_EFI.fd" + else + cp "$CH_CUSTOM_OVMF" "$CLH_INTEGRATION_WORKLOADS/CLOUDHV.fd" + fi + fi + # Extend common_args with integration-specific runtime settings. common_args+=( --privileged @@ -514,6 +545,10 @@ cmd_tests() { common_env_args+=( --env USER="root" --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" + --env CH_CUSTOM_KERNEL="$CH_CUSTOM_KERNEL" + --env CH_CUSTOM_BZIMAGE="$CH_CUSTOM_BZIMAGE" + --env CH_CUSTOM_FIRMWARE="$CH_CUSTOM_FIRMWARE" + --env CH_CUSTOM_OVMF="$CH_CUSTOM_OVMF" ) if [ "$integration" = true ]; then From 00bee62653a2948bab294fecbf7acf3629052068 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 23 Apr 2026 15:02:03 -0700 Subject: [PATCH 1480/1893] 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 --- scripts/run_integration_tests_aarch64.sh | 14 +++++++++++--- scripts/run_integration_tests_x86_64.sh | 18 +++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 320536b922..941b717b53 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -141,9 +141,17 @@ update_workloads() { pushd "$WORKLOADS_DIR" || exit - if ! sha1sum sha1sums-aarch64-common --check; then - echo "sha1sum validation of images failed, remove invalid images to fix the issue." - exit 1 + # Skip checksum verification for custom-provided workloads + if [ -n "$CH_CUSTOM_OVMF" ]; then + if ! grep -v "CLOUDHV_EFI.fd" sha1sums-aarch64-common | sha1sum --check; then + echo "sha1sum validation of images failed, remove invalid images to fix the issue." + exit 1 + fi + else + if ! sha1sum sha1sums-aarch64-common --check; then + echo "sha1sum validation of images failed, remove invalid images to fix the issue." + exit 1 + fi fi popd || exit diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index f847cd57bc..df080c717a 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -116,9 +116,21 @@ if [ ! -f "$ALPINE_INITRAMFS_IMAGE" ]; then fi pushd "$WORKLOADS_DIR" || exit -if ! sha1sum sha1sums-x86_64 sha1sums-x86_64-common --check; then - echo "sha1sum validation of images failed, remove invalid images to fix the issue." - exit 1 +# Skip checksum verification for custom-provided workloads +sha1_exclude="" +[ -n "$CH_CUSTOM_FIRMWARE" ] && sha1_exclude="${sha1_exclude}|hypervisor-fw" +[ -n "$CH_CUSTOM_OVMF" ] && sha1_exclude="${sha1_exclude}|CLOUDHV\\.fd" +sha1_exclude="${sha1_exclude#|}" +if [ -n "$sha1_exclude" ]; then + if ! cat sha1sums-x86_64 sha1sums-x86_64-common | grep -Ev "$sha1_exclude" | sha1sum --check; then + echo "sha1sum validation of images failed, remove invalid images to fix the issue." + exit 1 + fi +else + if ! sha1sum sha1sums-x86_64 sha1sums-x86_64-common --check; then + echo "sha1sum validation of images failed, remove invalid images to fix the issue." + exit 1 + fi fi popd || exit From c5e90a37e66f4107c897d4b32ff0a53c3110a056 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 23 Apr 2026 15:02:11 -0700 Subject: [PATCH 1481/1893] 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 --- scripts/run_integration_tests_aarch64.sh | 4 +++- scripts/run_integration_tests_vfio.sh | 4 +++- scripts/run_integration_tests_windows_aarch64.sh | 4 +++- scripts/run_integration_tests_windows_x86_64.sh | 5 ++++- scripts/test-util.sh | 11 +++++++++++ 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/scripts/run_integration_tests_aarch64.sh b/scripts/run_integration_tests_aarch64.sh index 941b717b53..29d6de9715 100755 --- a/scripts/run_integration_tests_aarch64.sh +++ b/scripts/run_integration_tests_aarch64.sh @@ -137,7 +137,9 @@ update_workloads() { fi # Download aarch64 ovmf - download_aarch64_ovmf + if [ ! -f "$WORKLOADS_DIR/CLOUDHV_EFI.fd" ]; then + download_aarch64_ovmf + fi pushd "$WORKLOADS_DIR" || exit diff --git a/scripts/run_integration_tests_vfio.sh b/scripts/run_integration_tests_vfio.sh index 3f1b267787..02f3434fee 100755 --- a/scripts/run_integration_tests_vfio.sh +++ b/scripts/run_integration_tests_vfio.sh @@ -16,7 +16,9 @@ process_common_args "$@" WORKLOADS_DIR="$HOME/workloads" -download_hypervisor_fw +if [ ! -f "$WORKLOADS_DIR/hypervisor-fw" ]; then + download_hypervisor_fw +fi CFLAGS="" if [[ "${BUILD_TARGET}" == "x86_64-unknown-linux-musl" ]]; then diff --git a/scripts/run_integration_tests_windows_aarch64.sh b/scripts/run_integration_tests_windows_aarch64.sh index 69537d7769..1f94e088eb 100755 --- a/scripts/run_integration_tests_windows_aarch64.sh +++ b/scripts/run_integration_tests_windows_aarch64.sh @@ -20,7 +20,9 @@ WIN_IMAGE_FILE="$WORKLOADS_DIR/$WIN_IMAGE_BASENAME" # Download aarch64 OVMF OVMF_FW="$WORKLOADS_DIR/CLOUDHV_EFI.fd" -download_aarch64_ovmf +if [ ! -f "$OVMF_FW" ]; then + download_aarch64_ovmf +fi # Check if the images are present if [[ ! -f ${WIN_IMAGE_FILE} || ! -f ${OVMF_FW} ]]; then diff --git a/scripts/run_integration_tests_windows_x86_64.sh b/scripts/run_integration_tests_windows_x86_64.sh index 6b358c5c28..29953bc244 100755 --- a/scripts/run_integration_tests_windows_x86_64.sh +++ b/scripts/run_integration_tests_windows_x86_64.sh @@ -16,9 +16,12 @@ fi WIN_IMAGE_FILE="/root/workloads/windows-server-2025-amd64-1.raw" WORKLOADS_DIR="/root/workloads" +OVMF_FW="$WORKLOADS_DIR/CLOUDHV.fd" # Download amd64 ovmf -download_amd64_ovmf +if [ ! -f "$OVMF_FW" ]; then + download_amd64_ovmf +fi CFLAGS="" if [[ "${BUILD_TARGET}" == "x86_64-unknown-linux-musl" ]]; then diff --git a/scripts/test-util.sh b/scripts/test-util.sh index 3ba2474a49..3b17ee4c96 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -187,6 +187,17 @@ download_linux() { } prepare_linux() { + if [ "$(uname -m)" = "aarch64" ]; then + KERNEL_FILE="$WORKLOADS_DIR/Image-arm64" + else + KERNEL_FILE="$WORKLOADS_DIR/vmlinux-x86_64" + BZIMAGE_FILE="$WORKLOADS_DIR/bzImage-x86_64" + fi + if [[ -f "$KERNEL_FILE" && -f "$BZIMAGE_FILE" ]]; then + echo "Kernel already present at $KERNEL_FILE, skipping" + return + fi + if [ "$build_kernel" = true ]; then echo "Building kernel from source" build_custom_linux From bb8bbb29619cb15b644328375d161317f8158ca5 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 23 Apr 2026 14:29:10 -0700 Subject: [PATCH 1482/1893] 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 --- docs/testing.md | 403 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 docs/testing.md diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000000..74478c4948 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,403 @@ +# Testing + +- [Testing](#testing) + - [Overview](#overview) + - [Prerequisites](#prerequisites) + - [The dev\_cli.sh entry point](#the-dev_clish-entry-point) + - [Global flags](#global-flags) + - [Building](#building) + - [Running tests](#running-tests) + - [Argument passthrough](#argument-passthrough) + - [Custom kernel and firmware](#custom-kernel-and-firmware) + - [Unit tests](#unit-tests) + - [Integration tests](#integration-tests) + - [x86\_64](#x86_64) + - [ARM64](#arm64) + - [VFIO](#vfio) + - [Windows guests](#windows-guests) + - [Rate limiter](#rate-limiter) + - [Confidential VMs](#confidential-vms) + - [Performance metrics](#performance-metrics) + - [Code coverage](#code-coverage) + - [CI workflows](#ci-workflows) + +## Overview + +All Cloud Hypervisor builds and tests run inside a Docker container to +provide a reproducible environment. The main entry point is +`scripts/dev_cli.sh`, which manages the container lifecycle and +forwards arguments to the appropriate test scripts. + +The container image is published at +`ghcr.io/cloud-hypervisor/cloud-hypervisor` and is automatically +pulled on first use. A local build of the container can be triggered +with `scripts/dev_cli.sh build-container` or by passing the `--local` +flag. + +Test workloads (guest images, kernels, firmware) are stored on the host +under `$HOME/workloads` and bind-mounted into the container at +`/root/workloads`. Most test scripts download missing workloads +automatically on first run. + +## Prerequisites + +A working Docker (or Podman) installation and access to `/dev/kvm` +(or `/dev/mshv` for Microsoft Hypervisor tests) are required. The +host must be running Linux on x86_64 or aarch64. + +```shell +# Verify KVM is available +ls -l /dev/kvm +``` + +The container image bundles all build dependencies. No Rust toolchain +is needed on the host. + +## The dev_cli.sh entry point + +``` +scripts/dev_cli.sh [flags] [] +``` + +### Global flags + +| Flag | Description | +|-----------|--------------------------------------------------| +| `--local` | Build and use a local container image instead of pulling from the registry. | + +### Building + +```shell +scripts/dev_cli.sh build [--debug|--release] [--libc musl|gnu] \ + [--hypervisor kvm|mshv] [--features ] \ + [--volumes /host:/ctr#...] [-- ] +``` + +| Flag | Default | Description | +|----------------|---------|------------------------------------------| +| `--debug` | yes | Build debug binaries. | +| `--release` | | Build release binaries. | +| `--libc` | `gnu` | C library to link against (`musl`/`gnu`).| +| `--hypervisor` | `kvm` | Hypervisor backend (`kvm`/`mshv`). | +| `--features` | | Additional cargo features. | +| `--volumes` | | Extra host volumes (`/a:/a#/b:/b`). | +| `--runtime` | `docker`| Container runtime (`docker`/`podman`). | + +Arguments after `--` are forwarded directly to `cargo build`. + +### Running tests + +```shell +scripts/dev_cli.sh tests [] [--libc musl|gnu] \ + [--hypervisor kvm|mshv] [--volumes /host:/ctr#...] \ + [--